feat(board): nested repos enter on a STATE.md, and the scan reports its nevner

Order 20260903T190201Z-238406410-from-.claude (.claude, operator decision
2026-09-03). A git repo nested under a depth-1 REPO was invisible: discovery
adds a depth-1 repo and stops, and the else-branch container scan - the only
place children are ever looked at - is unreachable for an entry that is itself
a repo. Measured before writing anything: 12 nested repos across the real tree,
exactly 1 with a STATE.md (from-ai-to-chitta/content-sadhguru), which had been
running work and reporting to nobody.

Admission is criterion (a) and nothing wider. add_nested_repos() sits beside
add_dot_repos(), never a widening of the `*` loops - the same argument ordre
20260818T124828Z made for dot repos, and the order made it again before this
repo wrote a line: routing a depth-1 repo into the container branch would admit
every vendored clone under claude-code-100x/. One level only; depth 3 is pinned
as NOT admitted. A dot-prefixed depth-1 repo gets the same nested scan, since
nothing in the criterion distinguishes it.

A nested repo now carries TWO names. The board KEY is <parent>/<child> as the
order specifies. The MAILBOX name is not that key: a mailbox is addressed by
basename(git toplevel), so $COORD/<parent>/<child>/inbox finds no directory and
INN/ORDRE/FLY would print 0 for a repo that may have mail - a failed
measurement wearing the reassuring value, in three columns at once. The record
loop carries `mbox` beside `name`; --voyage's order lookup takes
basename($vy_dir) for the same reason. Which dirs are nested is RECORDED by
discovery (NESTED_LIST), not re-derived from "is my parent a repo?", which
would prefix every depth-1 repo if a scan root were ever a checkout.

The denominator line is independent of all of that and went in regardless:
"undersoekt: N katalog(er) depth 1, M polyrepo-container(e), K nestede repo
(J med STATE.md tatt med)". The header count answers how many were found and
nothing about how many were looked at, so a criterion excluding 11 of 12 was
invisible at the surface built to show it. Real tree 2026-09-04: 43 / 5 / 12
(1 tatt med). A non-repo dot-dir counts in N and never in M - it is a
denominator, not a partition.

Its wording broke an existing check: "polyrepo container itself is not listed
as a repo" grepped the whole output for `polyrepo` and matched the footer's own
`polyrepo-container(e)` - the same class as a grep reading a comment that
EXPLAINS a pattern as an instance of it. Now anchored at column 1, which is
what it always meant.

Verified with board.sh, not from memory: from-ai-to-chitta/content-sadhguru
in-progress, Sonnet 5/high, and --plan gives it a real tab with paste=.
Mutation-verified: reverting the eight $COORD/$mbox reads turns exactly the
three mailbox checks red with the rest of the section green.

Bounded gaps, stated rather than closed: the mailbox-keyed JOINS ($OWED,
brief_orphans, --inbox-plan) still key on the board name - neither created nor
worsened here, since the repo was previously absent from RECORDS entirely; a
34-char key overflows the table's %-32s REPO column (same class as Fable
5.1/xhigh in KOST, and widening moves three cut -c89- helpers); a dot-prefixed
NESTED repo is not looked for.

board-selftest 368 -> 393. Suites: coord 257, board 393, route 73, orders 116,
guard 54 = 893. npm test 11/11.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-04 21:35:55 +02:00
commit cde185979c
10 changed files with 428 additions and 23 deletions

View file

@ -10,7 +10,7 @@ Session A in repo X leaves a message for repo Y; the next session in repo Y gets
*AI-generated: all code produced by Claude Code through dialog-driven development.*
![Version](https://img.shields.io/badge/version-0.33.0-blue)
![Version](https://img.shields.io/badge/version-0.33.1-blue)
![Hooks](https://img.shields.io/badge/hooks-1-green)
![Skills](https://img.shields.io/badge/skills-4-orange)
![CLI scripts](https://img.shields.io/badge/CLI_scripts-8-blue)
@ -86,7 +86,7 @@ The plugin ships empty: your mailbox is created lazily on first send, on your ma
Since v0.8.0 it closes with one aggregate line about mail pending in *other* mailboxes, so an empty inbox no longer reads as "all clear" while messages sit unanswered elsewhere. Two integers, never a roster: naming the other mailboxes would put their situation inside your repo's injection, and the line explicitly disclaims the obligation it sits beneath — those counts are not yours to handle, and counting them delivered nothing.
**Choosing between repos (the `board` skill).** "What should I work on?", "who is waiting on me?", "what unblocks the most?" — `board.sh` scans every repo it can find and reads three sources per repo: the STATE.md next-step block and its optional board line, `git status`, and the pending count in that repo's mailbox. The skill runs it, ranks by leverage (what unblocks the most, cheapest first) and answers with one repo and the rule that fired, never the table. Read-only: it writes to no repo, no STATE.md, and no mailbox. The board is deliberately *not* wired into session start — it runs when asked.
**Choosing between repos (the `board` skill).** "What should I work on?", "who is waiting on me?", "what unblocks the most?" — `board.sh` scans every repo it can find and reads three sources per repo: the STATE.md next-step block and its optional board line, `git status`, and the pending count in that repo's mailbox. The skill runs it, ranks by leverage (what unblocks the most, cheapest first) and answers with one repo and the rule that fired, never the table. Read-only: it writes to no repo, no STATE.md, and no mailbox. The board is deliberately *not* wired into session start — it runs when asked. Discovery takes git repos at depth 1, the children of a polyrepo container, and — since 0.33.1 — a repo nested under a depth-1 repo **only when it carries a `STATE.md`**: measured on the real tree, 12 such checkouts existed and exactly 1 had one, so admitting all of them would have buried the board in vendored clones. Every run prints its own denominator (`undersoekt: N katalog(er) depth 1, M polyrepo-container(e), K nestede repo (J med STATE.md tatt med)`), because a repo count alone says how many were found and nothing about how many were looked at.
The mailbox is one of its three inputs, which is why the board lives here. Note the axis: a repo's pending count means *others are waiting on it*, an obligation it owes outward. Who a repo waits *on* comes only from its own board line, because the message format has no reply-to field.