Module: OKF::Pro::Snapshot
- Defined in:
- lib/okf/pro/snapshot.rb
Overview
Rule 2's counters, derived. This is derivation returned as a checker: it computes the mechanical line and verifies the one a person appended — it never writes one. Agent Drift killed the generator (a regenerated view drops a task silently); a checker that disagrees out loud has no way to drop anything silently, which is why it gets to exist first.
Constant Summary collapse
- PATTERNS =
The line's vocabulary, in the order the line reads. Parse patterns are anchored on the field names so a hand-typed line with extra spacing still parses, and a line missing a field says so as nil rather than borrowing a neighbour's number. "in flight" requires the slash so the phrase inside "deadlines within 7d not in flight" cannot satisfy it.
oldesttakes an optional sign, and it is the only field that can: every other counter is a count, andoldestis a SUBTRACTION. A capture dated in the future — one mistyped year — gives a negative age,renderwroteoldest -365d, and a pattern demanding a digit straight afteroldestread that as nothing at all.verifythen disagreed with the linerenderhad just produced, so the stop gate refused and offered as the fix the very line it was refusing. Pasting it refused again: a deadlock with no self-service exit.The age is NOT clamped to zero, deliberately. Clamping would make the gate self-consistent by making the counter lie, and
oldest 0don a line nobody can explain is the quiet-wrong-number failure this whole module exists to refuse. A negative age is the honest reading of a future-dated capture, and it is visible in the log where a person will ask about it. { "inbox" => /inbox (\d+)/, "oldest" => /oldest (-?\d+)d/, "in flight" => %r{in flight (\d+)/\d+}, "cap" => %r{in flight \d+/(\d+)}, "waiting" => /waiting (\d+)/, "past chase" => /\((\d+) past chase\)/, "backlog" => /backlog (\d+)/, "to read" => /to read (\d+)/, "unverified briefings" => /unverified briefings (\d+)/, "conflicts open" => /conflicts open (\d+)/, "deadlines within 7d not in flight" => /deadlines within 7d not in flight (\d+)/, "projects with 0 concepts" => /projects with 0 concepts (\d+)/ }.freeze
Class Method Summary collapse
-
.conflicts(board) ⇒ Object
Conflict lines are dated captures wherever they sit — Inbox by rule, but a line that migrated to Backlog is still an open conflict.
-
.counters(root, today: Date.today, board: nil, concepts: nil) ⇒ Object
board:andconcepts:are the same courtesy Pairing.failures extends: a caller that already paid for the read hands it over. -
.empty_projects(root) ⇒ Object
An open project holding nothing but its own index: work happening with no knowledge landing — the blind spot the structure itself created.
- .line(root, today: Date.today, board: nil, concepts: nil) ⇒ Object
-
.looming_deadlines(board, today, window: 7) ⇒ Object
Crack 2's confession: deadlines due within the window — or already due — that share no project link with any in-flight line.
- .parse(line) ⇒ Object
- .project_slugs(line) ⇒ Object
- .render(c) ⇒ Object
-
.verify(root, line, today: Date.today, board: nil, concepts: nil) ⇒ Object
The appended line against the bundle it summarises.
Class Method Details
.conflicts(board) ⇒ Object
Conflict lines are dated captures wherever they sit — Inbox by rule, but a line that migrated to Backlog is still an open conflict.
120 121 122 123 124 |
# File 'lib/okf/pro/snapshot.rb', line 120 def conflicts(board) # visible() is a fixpoint, so a caller handing over already-visible # text (counters does) costs one no-op scan, not a second strip. Board.visible(board).each_line.count { |l| l.start_with?("- ") && l.include?("Resolve:") } end |
.counters(root, today: Date.today, board: nil, concepts: nil) ⇒ Object
board: and concepts: are the same courtesy Pairing.failures
extends: a caller that already paid for the read hands it over. The
stop gate reads the board and parses the bundle once for all of its
checks; without the seam it paid twice per Stop.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/okf/pro/snapshot.rb', line 53 def counters(root, today: Date.today, board: nil, concepts: nil) board ||= Pro.read_text(File.join(root, "board.md")) concepts ||= ::OKF::Bundle::Reader.read(root).concepts # One strip for all twelve counters — every Board.count call was # re-running the whole comment strip, ~10 times per Stop. vis = Board.visible(board) 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 } chases = waiting.map { |l| Board.chase_date(l) }.compact { "inbox" => inbox.size, "oldest" => ages.max || 0, "in flight" => Board.visible_section_lines(vis, "In flight").size, "cap" => budget ? budget.cap : 0, "waiting" => waiting.size, "past chase" => chases.count { |d| d < today }, "backlog" => Board.visible_section_lines(vis, "Backlog").size, "to read" => Board.visible_section_lines(vis, "To read").size, "unverified briefings" => Pairing.unverified_ids(concepts).size, "conflicts open" => conflicts(vis), "deadlines within 7d not in flight" => looming_deadlines(vis, today).size, "projects with 0 concepts" => empty_projects(root).size } end |
.empty_projects(root) ⇒ Object
An open project holding nothing but its own index: work happening with no knowledge landing — the blind spot the structure itself created.
147 148 149 150 151 152 153 |
# File 'lib/okf/pro/snapshot.rb', line 147 def empty_projects(root) Pairing.open_projects(root).select do |name| Dir.glob(File.join(root, "projects", name, "**", "*.md")) .reject { |f| File.basename(f) == "index.md" } .empty? end end |
.line(root, today: Date.today, board: nil, concepts: nil) ⇒ Object
82 83 84 |
# File 'lib/okf/pro/snapshot.rb', line 82 def line(root, today: Date.today, board: nil, concepts: nil) render(counters(root, today: today, board: board, concepts: concepts)) end |
.looming_deadlines(board, today, window: 7) ⇒ Object
Crack 2's confession: deadlines due within the window — or already due — that share no project link with any in-flight line. A deadline line with no link cannot be paired, so it counts: the board cannot show anyone is on it, and "probably someone is" is not a counter.
130 131 132 133 134 135 136 137 138 139 |
# File 'lib/okf/pro/snapshot.rb', line 130 def looming_deadlines(board, today, window: 7) vis = Board.visible(board) covered = Board.visible_section_lines(vis, "In flight").flat_map { |l| project_slugs(l) } Board.visible_section_lines(vis, "Deadlines").select do |line| date = Board.line_date(line) next false unless date && date <= today + window (project_slugs(line) & covered).empty? end end |
.parse(line) ⇒ Object
97 98 99 100 101 102 |
# File 'lib/okf/pro/snapshot.rb', line 97 def parse(line) PATTERNS.transform_values do |pattern| match = line.to_s.match(pattern) match && match[1].to_i end end |
.project_slugs(line) ⇒ Object
141 142 143 |
# File 'lib/okf/pro/snapshot.rb', line 141 def project_slugs(line) Board.targets(line).map { |t| t[%r{\A/projects/([^/\s]+)}i, 1]&.downcase }.compact end |
.render(c) ⇒ Object
86 87 88 89 90 91 92 93 94 95 |
# File 'lib/okf/pro/snapshot.rb', line 86 def render(c) "* **Snapshot**: inbox #{c["inbox"]} (oldest #{c["oldest"]}d) " \ "· in flight #{c["in flight"]}/#{c["cap"]} " \ "· waiting #{c["waiting"]} (#{c["past chase"]} past chase) " \ "· backlog #{c["backlog"]} · to read #{c["to read"]} " \ "· unverified briefings #{c["unverified briefings"]} " \ "· conflicts open #{c["conflicts open"]} " \ "· deadlines within 7d not in flight #{c["deadlines within 7d not in flight"]} " \ "· projects with 0 concepts #{c["projects with 0 concepts"]}" end |
.verify(root, line, today: Date.today, board: nil, concepts: nil) ⇒ Object
The appended line against the bundle it summarises. Field by field, so the refusal names what drifted instead of waving at the whole line — and the recomputed line is in the message, because the fix is a paste.
107 108 109 110 111 112 113 114 115 116 |
# File 'lib/okf/pro/snapshot.rb', line 107 def verify(root, line, today: Date.today, board: nil, concepts: nil) want = counters(root, today: today, board: board, concepts: concepts) got = parse(line) wrong = want.reject { |key, value| got[key] == value } return [] if wrong.empty? detail = wrong.map { |key, value| "#{key} is #{value}, the line says #{got[key] || "nothing"}" } [ "— the Snapshot line disagrees with the bundle it summarises: #{detail.join("; ")}.\n " \ "The mechanical line, recomputed:\n #{render(want)}" ] end |