diff --git a/docs/plans/2026-09-12_control-board-mvp.md b/docs/plans/2026-09-12_control-board-mvp.md index 826f8093..5cf8a42d 100644 --- a/docs/plans/2026-09-12_control-board-mvp.md +++ b/docs/plans/2026-09-12_control-board-mvp.md @@ -165,6 +165,79 @@ message reflects the reply. One real exchange is the pass. finding (ms-communications rule), reported on the page and in the issue. - Fleet seats are out: they are not registered (Jason, 2026-09-12). +## Piece 3: ledger (numbers for the rails) + +Approved by Jason 2026-09-12 ("go for it") after Gate C. Purpose: replace +"it feels like the rails work" with numbers read from evidence that already +exists. Same shape: one package, one gate, tests, small commits on `refactor`. +Darkwing opens the issue and updates `CURRENT.md` when he picks it up. + +### What it is + +`node packages/ledger/src/cli.mjs --since YYYY-MM-DD [--until YYYY-MM-DD] +[--json] [--no-issues]`. Read-only. Prints two tables and a totals line to +stdout; `--json` prints the same as one object. Sources, in this order: + +1. **Local git log on `refactor`** (no network): commits in range whose + subject carries `#N`. Nothing else in the message is parsed. +2. **Gitea issues** through `scripts/gitea-api.sh` (`repos/mosaicstack/stack`): + number, title, `created_at`, `closed_at`. Missing or unreadable + credentials: exit 2 with the reason, unless `--no-issues`, which prints the + issue table with hours-open as "unknown". +3. **Session logs** under `.pi/state//sessions/*.jsonl` for the seats + in `agents/` (repo layout only, no fleet sources): entries with + `type: "message"` and `message.role: "user"` whose timestamp falls in range. + +Table 1, one row per issue touched in range (a commit or a close in range): +issue, title (truncated), opened, hours open (or "open"), commits, +follow-ups (commits after the first one for that issue), seats (from the +session logs that mention `#N` in a user message; blank when none). + +Table 2, one row per seat: user messages in range split three ways by the +first line of the text: **board** (preamble source session `control-board`), +**agent** (any other `[a:b -> c:d]` preamble), **human** (no preamble). + +Totals line: issues closed, median hours open, commits, follow-ups per issue +(one decimal), human messages per closed issue (one decimal). + +### What it does not claim + +- Agent versus human authorship. Every commit and every issue carries + Jason's identity; the ledger does not guess. +- PR metrics. This work is issue-tagged commits on `refactor`; a PR count + would read zero and mean nothing. +- Gate results. Those are written by hand in this log. +- Quality. A follow-up commit is a count, not a verdict; the reader decides. + +### Gate D (yes/no) + +Jason runs the ledger for 2026-09-06 to 2026-09-12, picks the one number he +wants to move next week, and writes that sentence with the number into +`CURRENT.md`. Pass: the sentence exists, and its number came from the +ledger's output, not from memory. + +### Tests + +- Fixture git repository built in a temp dir with commits tagged `#1`, + `#2` (two commits, so one follow-up) and one untagged commit; table 1 + counts match. +- Fixture session logs with one message of each kind (board, agent, human) + and one out of range; table 2 counts match. +- Fake `gitea-api.sh` on PATH returning fixture JSON; hours open computed + from `created_at`/`closed_at`; an open issue prints "open". +- Missing credentials: exit 2 with a message; with `--no-issues`, exit 0 and + "unknown" in the hours column. +- Empty range: both tables with zero rows, totals of zero, exit 0. +- `--json` output parses and carries the same numbers as the tables. + +### Boundaries + +- Read-only. No writes under the data root, no board changes, no new root + files, no schedule, no charts, no `packages/mosaic` changes. +- No fleet sources (`~/.mosaic`) and no network beyond the one Gitea call. +- If a number cannot be computed from evidence it prints "unknown", never a + guess. + ## How the scanner decides state The scanner reads the newest pi session log for each agent, plus whether