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

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