fix(board): cut the rationale before extracting route traits, not after

route_cmd_for()'s four extractions searched the WHOLE route line with a
greedy `.*trait=`, so a rationale that names a trait won the match over the
field itself. route.sh --help asks for a rationale per score, so a real
rationale names the traits routinely - this is the ordinary shape of the
data, not an edge case. route.sh then correctly refused the prose, --plan
printed command_missing=, and the operator read a broken PARSER as "that
repo has no route line": Verifiseringsloven ansikt 4 aimed at our own tooling.

Measured by .claude on four live STATE.md files before the order was written,
then re-measured here rather than quoted: A/B-ing the pre-fix script against
this one over the real tree at the same moment gives 25 tabs, command_missing
7 -> 2, and the five repos that gained a command (app-creator, catalog,
claude-code-llm-wiki, llm-security-commons, portfolio-optimiser-commons) are
exactly the five the order named. The two that remain are real and belong to
their own repos: okr writes scope=multi-module, outside the vocabulary, and
llm-security has no route line at all.

The cut keys on the FIRST `rationale=`, not on `; rationale=`: everything from
that token on is free text by definition, and the separator form would miss a
line written without the space. F4's [^;>]* class is untouched - the two answer
different questions and neither replaces the other.

The board line needs no equivalent change, and that was measured rather than
assumed: `grep -n 's/\.\*' scripts/board.sh` is the denominator, and the board
line's grammar carries no free-text field.

Selftest section 23 (7 checks, 252 -> 259) pins both failure directions,
including the sharp inverse where the FIELD is invalid (known2) while the
rationale holds a VALID value - before the fix that emitted a confident
command= built on prose the STATE line never declared. Two known-positive
controls prove the cut does not break the path that already worked.

Closes order 20260820T204409Z-8157821110-from-.claude

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZa4oEsa93ac6pZQFEXqBF
This commit is contained in:
Kjell Tore Guttormsen 2026-08-20 23:06:09 +02:00
commit b70786db46
3 changed files with 174 additions and 6 deletions

View file

@ -347,7 +347,7 @@ marketplace plugin. Three components, one boundary:
- **Board (`scripts/board.sh`):** cross-repo attention board. Reads STATE.md
next-step blocks + board lines, `git status`, and mailbox pending counts, and
prints one line per repo. Read-only by construction: it writes to no repo, no
STATE.md and no mailbox. Pinned by `board-selftest.sh` (252 checks).
STATE.md and no mailbox. Pinned by `board-selftest.sh` (259 checks).
**It lives here because the mailbox is one of its three inputs, and it carries
the same axis distinction the mailbox does.** A pending count means *others
@ -691,6 +691,52 @@ marketplace plugin. Three components, one boundary:
it was exactly the unmeasured side effect the order flagged, and nothing
in `~/repos` currently needs it (`.claude` has no repo children today).
**Ordre 20260820T204409Z (2026-08-20): `route_cmd_for()`'s four trait
extractions searched the WHOLE route line, and `.*trait=` is greedy, so a
rationale that names a trait won the match over the field itself.**
`route.sh --help` asks for a rationale per score, so a real rationale names
the traits routinely - this is the ordinary shape of the data, not an edge
case. Measured by `.claude` on four live STATE.md files before the order was
written: catalog's `path` read `known: notatet som skal skrives...`,
app-creator's read `known. Ingen maskin sjekker...`, claude-code-llm-wiki's
read `undetermined), det er en prosa/beslutnings...`; okr's read `known`
correctly, because its rationale happens to name no trait - and that
contrast is what isolates the parser from the data. `route.sh` then refuses
the prose (exactly as it should - it was handed garbage, and it is not
changed by this fix), `--plan` prints `command_missing=route-linjen kunne
ikke tolkes`, and the operator reads a broken PARSER as "that repo has no
route line": Verifiseringsloven ansikt 4 turned on our own tooling. The
denominator was measured, not estimated - and then re-measured here rather
than quoted: A/B-ing the pre-fix script against the fixed one over the real
tree at the same moment gives 25 tabs, `command_missing` 7 -> 2, and the five
repos that gained a command (app-creator, catalog, claude-code-llm-wiki,
llm-security-commons, portfolio-optimiser-commons) are exactly the five the
order named. The two that remain are real and belong to their own repos:
`okr` writes `scope=multi-module`, outside the vocabulary, and `llm-security`
has no route line at all.
**The fix cuts the line at the first `rationale=` before extracting, and
keeps the `[^;>]*` class exactly as F4 left it.** The two are answers to
different questions and neither replaces the other: F4 stopped a valid-
looking token from being TRUNCATED into something `route.sh` would accept
(`known2` -> `known`), this stops the search space from containing prose at
all. The cut keys on `rationale=` rather than `; rationale=` because
everything from that token on is free text by definition and the separator
form would miss a line written without the space. A route line that placed a
trait AFTER the rationale would lose it and degrade to `command_missing=` -
the safe direction, and the one this fix exists to restore.
The board line needs no equivalent change, and that was measured rather than
assumed: `grep -n 's/\.\*' scripts/board.sh` is the denominator, and the
board line's grammar (`status` / `blocked-on` / `next-cost`) carries no
free-text field, so no trait token can occur on it twice. Selftest section
23 pins both failure directions - the greedy repo that must now yield a
command, and the sharp inverse where the FIELD is invalid (`known2`) while
the rationale holds a VALID value: before the fix that emitted a confident
`command=` built on prose the STATE line never declared, which is worse than
the missing command it usually caused. Two known-positive controls
(a trait-free rationale, and a route line with no `rationale=` at all) prove
the cut does not break the path that already worked.
- **Route (`scripts/route.sh`):** pure calculator for the next session's model
and effort. Takes four scored traits of the next task plus a required
rationale, and prints one block of `key=value` lines: the rubric row, the rule
@ -813,7 +859,7 @@ obligations in another repo.
builtins only in hook and tests.
- TDD: no behavior change without a failing selftest check first.
`bash scripts/coord-selftest.sh` must exit 0 (230/230),
`bash scripts/board-selftest.sh` must exit 0 (252/252),
`bash scripts/board-selftest.sh` must exit 0 (259/259),
`bash scripts/route-selftest.sh` must exit 0 (69/69),
`bash scripts/orders-selftest.sh` must exit 0 (110/110) and
`bash scripts/state-line-guard-selftest.sh` must exit 0 (40/40).