ALDER carried two readings depending on which branch a repo fell into: STATE.md
mtime for repos that had one, last-commit age for repos that did not. The
consequence was one-sided -- a repo WITH a STATE.md reported only how recently
its plan was touched, so one that had not committed in a year looked exactly as
alive as one worked on this morning, and no column contradicted it.
Now each column means one thing. ALDER is the plan's age and is blank where
there is no plan; SISTE is the last commit, read for every repo, and blank where
there are no commits -- a day count there would be a fabricated reading, which
is what the old -1d was.
The four ranking rules stay four: the new column is evidence, never sort input.
The sort key was already a separate field from the displayed age, so bucket 5
keeps ordering by commit age while showing nothing in ALDER.
The fixture tree was missing the only case that discriminates -- a repo with a
STATE.md AND an old last commit -- so the checks land with it, plus its mirror
image (old plan, fresh commit) to pin that the two ages read independently and
that neither became the sort key. Backdating sets GIT_COMMITTER_DATE, not
--date=: %ct is the committer date, and an author-only backdate would have left
the red test failing for the wrong reason.
board-selftest: 30 -> 36 checks.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QS6gYYgYgJAz7VjcFHEzbb
The board line's next-cost field had a reader (board.sh) and no writer, so
its value was retyped by hand every session and drifted into several
competing spellings. Cleaning the data could not fix that: the cause was the
missing write path.
route.sh is that writer. Four scored traits of the next task -- path,
verification, reversibility, scope -- plus a required rationale, run through
the operator's model rubric moved here as the single copy. The row table is a
closed set of six values, so a seventh spelling cannot enter circulation, and
route-selftest.sh section 6 runs the round trip (route emits -> board parses)
inside one repo rather than across two.
Two spellings of one decision come out of one table: the rubric name for the
board line, the CLI alias for the command the operator pastes. Effort levels
are the set pinned in this marketplace; model aliases are gated against the
installed claude rather than hardcoded.
Three things worth naming:
- Escalation is asymmetric. Any single trait escalates; the cheapest row needs
all four at the cheap end. Underkill costs one session, overkill costs quota
every session -- but a wrong architecture call costs more than either.
- The Fable rows fire only from an explicit judgement flag, never inferred
from the last-session record. "The session did not finish" also covers
context exhaustion and operator interrupts, which say nothing about the
model, and Fable runs without an advisor.
- The trait block is a single-line HTML comment because board.sh's NESTE
extractor skips only lines that START with '<!--'. Measured first: a YAML
block or a multi-line comment silently replaces the repo's next step on the
board with "next_task:". Pinned by section 7.
board.sh is untouched as a program; its header now points at route.sh for the
value set so this does not reopen the two-specs defect 305f168 closed.
Selftests: coord 136, board 30, route 47 (new), node 7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017peNgsxVt1BR4BTuMwiPoX
The board line's cost field had two specifications and one of them was this
script. `--help` prescribed a versionless `sonnet/xhigh`; the global model
rubric it points at spells the model `Sonnet 5 / xhigh`. A session following
either was correct by its own source, so the field drifted with nobody making
a mistake. Measured across a real 44-repo tree: 14 board lines carrying eight
distinct strings, and not one of them the form documented here - the only
spelling nobody used was ours.
The help now prescribes the rubric's, which is also what most lines already
carry. The parser is untouched and stays lenient: it reads to `;` or `-->` so
the rubric's spacing survives, and the fixtures still vary the spelling on
purpose to pin that. Read-side leniency was never the defect; it is what makes
a documentation-only fix sufficient.
No normalization at read, deliberately. Board lines are rewritten every session
by convention, so an unambiguous spec self-heals the existing lines in about one
session per active repo - mapping variants would be permanent code migrating
data that migrates itself. Nothing compares the field mechanically today either:
KOST is a display column and the board sorts on a numeric bucket key, so the
defect was real but latent. Two checks (28 -> 30) pin the documented form,
red first, because cleaning the data cannot fix a spec that regenerates the
divergence every session. Validation at write has no write path to attach to:
nothing in this repo emits a board line outside the fixtures.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U6EixQo6hpoRCVtiAXdnFs
The v0.9.0 comments explained the attention-vs-topic axis by pointing at the
operator's private cross-repo register by path, naming an internal incident
code, and quoting a measured count of the operator's own repo population.
This repo mirrors to a public remote, so operational metadata about a private
machine does not belong in it - the reasoning does, the referents do not.
Rewritten self-contained: the topic-axis contrast now stands on its own terms
without citing a file a public reader cannot see, the defect class is named
rather than coded, and the worktree rationale keeps WHY .git is tested with
-e (a worktree or submodule has .git as a FILE) while dropping the census
that motivated measuring it. Illustrative paths stay; they carry no fact.
Comments only, no behavior change: 136 + 28 selftest checks and 6/6 node
tests unchanged. No version bump, so the v0.9.0 tag and the catalog ref stay
where they are - and with them the pre-scrub text, which history and that tag
still carry. Un-publishing that is a history rewrite and the operator's call,
not a side effect of a comment edit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U6EixQo6hpoRCVtiAXdnFs
The mailbox answers "who wrote to me"; it never answered "which repo
deserves the next session". board.sh scans every discovered repo and reads
three sources each: the STATE.md next-step block with its optional board
line, git status, and that repo's pending mailbox count. Read-only by
construction, pinned by board-selftest.sh (28 checks).
It ships here rather than as a personal script because the mailbox is one
of its three inputs and the two carry the same axis distinction: a pending
count means others are waiting on that repo, while who a repo waits ON comes
only from its board line, since the message format has no reply-to field.
Splitting the board from the mailbox would put that distinction in two
places. It also lets the skill resolve the engine through CLAUDE_PLUGIN_ROOT
like every other script here, instead of depending on a file that exists
only in ~/.claude/scripts (a directory with no remote and no backup).
The skill is a ranking, not a report: re-runs the board every invocation
because counts drift, ranks by what unblocks the most and what is cheapest
to move, then names one repo, the rule that fired, and the real next action
read from that repo's STATE.md. Never the table. Not wired into session
start, which would spend context on repos the session is not in.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MubwdTi88yu4hVLAFG1LbM