Module: OKF::Pro::State
- Defined in:
- lib/okf/pro/state.rb
Overview
What is on the board, in one call.
THE MEASUREMENT THIS EXISTS FOR. A session adding one task to a seeded
bundle spent 40.9% of its tool output rediscovering state — the board, the
log, the project and journal indexes, read raw, one cat per question —
and called no okf pro verb at all. Nine calls answered what the gates
already compute on every Stop. So there is no new logic here: every
aggregation below already existed and was consumed only by a refusal.
CHEAP BY DEFAULT, and that is a contract rather than an optimisation. The
default payload reads board.md, log.md and two directory globs, and
parses no concept — the same sources the session banner uses, so a reader
can trust the two to agree. --full is where Bundle::Reader.read lives:
the attestation report, the pairing invariants, and the live unverified
count, behind ONE parse shared by all three.
A contract that names its sources is a contract to check before adding
one. The friction log was added here and did not belong: it is telemetry
about the tooling rather than state of the bundle, it is append-only with
nothing pruning it, and render never printed the field — so the cheap
default paid for an unbounded read only --json could see.
okf pro friction is the verb that answers it.
Shell, not core: it reads the disk. The arithmetic it reports is Board's, Log's, Snapshot's and Pairing's, all of it pure and all of it already tested against text rather than against a fixture.
Class Method Summary collapse
-
.board_line(board, label) ⇒ Object
── human rendering ──────────────────────────────────────────────────.
- .board_state(vis, today) ⇒ Object
- .call(root, today: Date.today, full: false) ⇒ Object
-
.full_state(root, board) ⇒ Object
The one parse, shared by all three.
-
.last_snapshot(log) ⇒ Object
Labelled by the day it was logged under, never as live.
- .log_state(root, log, today) ⇒ Object
- .render(payload) ⇒ Object
- .render_full(full) ⇒ Object
- .render_snapshot(snap) ⇒ Object
Class Method Details
.board_line(board, label) ⇒ Object
── human rendering ──────────────────────────────────────────────────
The default, because the consumer is also a person doing QA — and
because a human line an agent reads costs the same tokens as JSON it
has to re-serialise into prose anyway.
The board's counters as one line, and the ONE place they are formatted.
okf pro state and the session banner both print it, and the banner's
whole promise is that it says what the verb would say — two renderings
of the same eleven numbers would drift into a reader having to check
which one they were looking at.
114 115 116 117 118 119 |
# File 'lib/okf/pro/state.rb', line 114 def board_line(board, label) "#{label} in flight #{board["in flight"]}/#{board["cap"] || "?"} · backlog #{board["backlog"]} · " \ "waiting #{board["waiting"]} (#{board["past chase"]} past chase) · inbox #{board["inbox"]} " \ "(oldest #{board["oldest"]}d) · to read #{board["to read"]} · deadlines #{board["deadlines"]} · " \ "conflicts open #{board["conflicts open"]}" end |
.board_state(vis, today) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/okf/pro/state.rb', line 53 def board_state(vis, today) budget = Board.budget(vis) inbox = Board.visible_section_lines(vis, "Inbox") waiting = Board.visible_section_lines(vis, "Waiting") ages = inbox.map { |l| Board.line_date(l) }.compact.map { |d| (today - d).to_i } { "in flight" => Board.visible_section_lines(vis, "In flight").size, "cap" => budget&.cap, "declared" => budget&.declared, "backlog" => Board.visible_section_lines(vis, "Backlog").size, "waiting" => waiting.size, "past chase" => waiting.map { |l| Board.chase_date(l) }.compact.count { |d| d < today }, "inbox" => inbox.size, "oldest" => ages.max || 0, "to read" => Board.visible_section_lines(vis, "To read").size, "deadlines" => Board.visible_section_lines(vis, "Deadlines").size, "conflicts open" => Snapshot.conflicts(vis) } end |
.call(root, today: Date.today, full: false) ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/okf/pro/state.rb', line 34 def call(root, today: Date.today, full: false) board = Pro.read_text(File.join(root, "board.md")) log_path = File.join(root, "log.md") log = File.exist?(log_path) ? Pro.read_text(log_path) : "" vis = Board.visible(board) payload = { "root" => root, "as of" => today.to_s, "board" => board_state(vis, today), "deadlines at risk" => Snapshot.looming_deadlines(vis, today).map(&:strip), "log" => log_state(root, log, today), "projects" => { "open" => Pairing.open_projects(root).sort }, "last snapshot" => last_snapshot(log) } payload["full"] = full_state(root, board) if full payload end |
.full_state(root, board) ⇒ Object
The one parse, shared by all three. Ordered so the reader meets the invariants before the listing: a pairing failure is a thing to fix, and an unverified concept is the truth about what is owed.
95 96 97 98 99 100 101 102 |
# File 'lib/okf/pro/state.rb', line 95 def full_state(root, board) concepts = ::OKF::Bundle::Reader.read(root).concepts { "pairing" => Pairing.failures(root, board: board, concepts: concepts).map { |m| m.sub(/\A— /, "") }, "unverified" => Attestation.report(root, concepts: concepts).map(&:strip), "unverified briefings" => Pairing.unverified_ids(concepts).size } end |
.last_snapshot(log) ⇒ Object
Labelled by the day it was logged under, never as live. The counters
here are free — the banner already prints the line, and parsing it costs
a regex — but two of them (unverified briefings, projects with 0 concepts) can only be recomputed by parsing the bundle, so a reader
who is told them without the date would read a stale number as current.
85 86 87 88 89 90 |
# File 'lib/okf/pro/state.rb', line 85 def last_snapshot(log) day, line = Log.latest_snapshot_entry(log) return nil if line.nil? { "day" => day, "line" => line.strip, "counters" => Snapshot.parse(line) } end |
.log_state(root, log, today) ⇒ Object
73 74 75 76 77 78 |
# File 'lib/okf/pro/state.rb', line 73 def log_state(root, log, today) { "newest day" => Log.newest_day(log), "journal today" => File.file?(File.join(root, "journal", "#{today}.md")) } end |
.render(payload) ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/okf/pro/state.rb', line 121 def render(payload) b = payload["board"] lines = [ board_line(b, "Board —") ] if b["declared"] && b["declared"] != b["in flight"] lines << " header declares #{b["declared"]} in flight and the section holds " \ "#{b["in flight"]} — fix the header." end risk = payload["deadlines at risk"] unless risk.empty? lines << "Deadlines within 7d with nothing in flight against them:" risk.each { |line| lines << " #{line}" } end log = payload["log"] lines << "Log — newest day #{log["newest day"] || "none yet"} · journal for " \ "#{payload["as of"]} #{log["journal today"] ? "open" : "not opened"}" open_projects = payload["projects"]["open"] lines << "Open projects (#{open_projects.size})#{": #{open_projects.join(", ")}" unless open_projects.empty?}" lines.concat(render_snapshot(payload["last snapshot"])) lines.concat(render_full(payload["full"])) if payload["full"] lines end |
.render_full(full) ⇒ Object
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/okf/pro/state.rb', line 156 def render_full(full) lines = [ "Live from the corpus — unverified briefings #{full["unverified briefings"]}" ] if full["pairing"].empty? lines << "Pairing — the board and the work are in step." else lines << "Pairing — #{full["pairing"].size} finding(s):" full["pairing"].each { |m| lines << " #{m}" } end if full["unverified"].empty? lines << "Awaiting the owner's read — nothing." else lines << "Awaiting the owner's read (#{full["unverified"].size}):" full["unverified"].each { |m| lines << " #{m}" } end lines end |
.render_snapshot(snap) ⇒ Object
147 148 149 150 151 152 153 154 |
# File 'lib/okf/pro/state.rb', line 147 def render_snapshot(snap) return [ "Last snapshot: none yet" ] if snap.nil? counters = snap["counters"] [ "As of #{snap["day"]}, the last logged snapshot — unverified briefings " \ "#{counters["unverified briefings"] || "?"} · projects with 0 concepts " \ "#{counters["projects with 0 concepts"] || "?"} (`--full` recomputes these live)" ] end |