feat(board): rank --plan on one score, with transitive chain-root credit
blocked-on was scanned and then used for a single display string; it never
reached the ordering. A blocked chain's root could therefore rank below the
repos waiting on it - measured on the real tree, the root opened at tab 14
while the two repos it held up opened at tabs 5 and 8.
--plan now ranks on one score instead of four hard buckets, because four
groups cannot express "this repo owes one message and releases two others":
40 x repos released transitively + 15 x inbox
+10 in-progress / +5 planned / +2 undeclared, +3 for a Sonnet row
Chain-root credit follows blocked-on transitively to the first repo that is
not itself blocked and credits only that root. Opening a blocked repo
releases nobody - its own next step is by definition waiting. A cycle, a
blocked-on naming an unscanned repo, and a blocked repo with no target all
credit NOBODY: inventing a root there yields a plan that looks correct and
sends the operator to the wrong repo.
Debt is deliberately uncapped. A cap was proposed alongside the credit and
declined: owing a reply is the other axis from a repo's own next step, and
answering is often what unblocks a chain. The weight carries that decision -
15 per message exceeds the sum of every other bonus (13), so one message
more still outranks any combination of status and cost. At the originally
proposed 12 a tiebreak could flip the debt order, which is the cap arriving
through the back door.
blocked-on becomes a record field, inserted BEFORE neste so no existing field
index moves: neste is free prose lifted from a STATE.md and may carry a
literal '|' from a markdown table, so it stays last.
why= now names the dominant term rather than the first rule that matched, so
a block says what opening it would release instead of repeating inbox:N.
Format is otherwise unchanged for both declared consumers: tab=, repo=, dir=,
why=, status=, neste=, command=/command_missing=, paste= and the fokus_*
header all keep their names and positions.
board-selftest 118 -> 134 (chain, cycle, dangling-edge and format fixtures
written first, all failing before the change).
This commit is contained in:
parent
917a902172
commit
e5e9cbe847
10 changed files with 404 additions and 51 deletions
45
CHANGELOG.md
45
CHANGELOG.md
|
|
@ -5,6 +5,51 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.19.0] - 2026-08-02
|
||||
|
||||
### Added
|
||||
|
||||
- **Chain-root credit in `board.sh --plan`.** `blocked-on` was scanned and then
|
||||
used for one display string (`blocked>X`); it never reached the ordering. For
|
||||
every `blocked` repo the edge is now followed **transitively** to the root of
|
||||
the chain — the first repo not itself blocked — and only that root is
|
||||
credited with the repos it holds up. Opening a blocked repo releases nobody;
|
||||
opening the root releases everything behind it. Measured on the real tree: the
|
||||
root of the one live chain moved from tab 14 to tab 1, ahead of the two repos
|
||||
waiting on it.
|
||||
- **`why=unblocks:N`.** `why=` now names the *dominant* scoring term rather than
|
||||
the first rule that matched, so a block says what opening it would release.
|
||||
Previously every planned block read `inbox:N` — the same sentence repeated.
|
||||
- **`blocked-on` is a record field**, inserted before `neste` so no existing
|
||||
field index moves. `neste` is free prose lifted from a STATE.md and may carry
|
||||
a literal `|` from a markdown table, so it stays last.
|
||||
|
||||
### Changed
|
||||
|
||||
- **`--plan` ranks on one score, not four hard buckets.** Four groups could not
|
||||
express "this repo owes one message and releases two others", which is exactly
|
||||
how a chain's root ended up below the repos waiting on it:
|
||||
|
||||
40 x repos released transitively + 15 x inbox
|
||||
+10 in-progress / +5 planned / +2 undeclared, +3 for a Sonnet row
|
||||
|
||||
Every term is a lookup over a field the scan already read, so the order stays
|
||||
reproducible and costs no model call.
|
||||
- **`--plan` header lines** now state the score and that a blocked repo never
|
||||
receives credit. The format is otherwise **unchanged**: `tab=`, `repo=`,
|
||||
`dir=`, `why=`, `status=`, `neste=`, `command=`/`command_missing=`, `paste=`
|
||||
and the `fokus_*` header all keep their names and positions.
|
||||
|
||||
### Not done, deliberately
|
||||
|
||||
- **No cap on inbox debt.** Proposed alongside the credit and declined by the
|
||||
operator: owing a reply is the other axis from a repo's own next step, and
|
||||
answering is often precisely what unblocks a chain. The debt weight is 15 —
|
||||
above the sum of every other bonus (13) — so one message more still outranks
|
||||
any combination of status and cost, and the bonuses only separate repos that
|
||||
owe the same. At the proposed 12 they could have flipped it, which would have
|
||||
weakened the debt order through a tiebreak.
|
||||
|
||||
## [0.18.0] - 2026-08-02
|
||||
|
||||
### Added
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue