fix(board): plan/brief use actual debt, not raw pending mail

board.sh --plan group 2 and --brief conflated every unhandled inbox
message with an obligation to reply, including ones the sender
declared reply-expected: no (a notice, not a request). Reported by
morning-driver (2026-08-11), independently reproduced against the live
mailbox on 2026-08-13: 27 of 72 pending messages were notices. Both
paths now join against coord-count.sh's owed column instead, so a
done/deferred/blocked repo whose only mail is FYI no longer gets a
plan tab, and --brief no longer counts a notice as an obligation. The
table's raw INN column is unchanged by design.

While extending that join with a second lookup file, found and fixed
a more severe, independent defect: the existing $UNBLOCKS/$RECORDS
join used the NR==FNR awk idiom, which silently empties the entire
plan whenever the first file is empty -- i.e. whenever the repo tree
has zero blocked repos, a common, ordinary state, not an edge case.
Verified against the shipped 0.21.0 script. Fixed by matching on
FILENAME instead of cumulative line counts, for both lookup files.

Also repoints the README's governance link at repo-standard's
canonical GOVERNANCE.md (was pointing at the marketplace's copy),
per org-ops D11.

board-selftest.sh: 142 -> 150 checks. Version 0.21.0 -> 0.22.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGWMPskXBsTjMrrQ2GofFx
This commit is contained in:
Kjell Tore Guttormsen 2026-08-13 20:54:55 +02:00
commit 03e712a423
10 changed files with 263 additions and 27 deletions

View file

@ -1,6 +1,6 @@
{
"name": "repo-mailbox",
"version": "0.21.0",
"version": "0.22.0",
"description": "Local mailbox for coordination between Claude Code sessions in different repositories. Directed messages and broadcasts as plain Markdown files on your own disk, injected as context at session start. Local, private, no network.",
"author": {
"name": "Kjell Tore Guttormsen"

View file

@ -5,6 +5,35 @@ 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.22.0] - 2026-08-13
### Fixed
- **`board.sh --plan` and `--brief` conflated raw pending mail with debt.**
Group 2's `keep`/`mag`/`why=inbox:N` and the whole of `--brief` were computed
from every unhandled message in an inbox, including ones the sender declared
`reply-expected: no` (a notice, not a request). Reported by morning-driver
(2026-08-11), independently reproduced against the live mailbox on
2026-08-13 (27 of 72 pending messages were notices, not requests). Both
paths now join against `coord-count.sh`'s `owed` column instead: a
`done`/`deferred`/`blocked` repo whose only mail is FYI no longer gets a
plan tab, and `--brief` no longer counts a notice as an obligation. The
table's `INN` column is unchanged - it means raw pending by design. See
CLAUDE.md's board.sh section for the full reasoning, including why this
narrows rather than reopens the "debt is never excluded or capped" rule.
- **`board.sh --plan` silently emptied itself whenever the repo tree had zero
`blocked` repos.** The `$UNBLOCKS`/`$RECORDS` join used the `NR==FNR` awk
idiom, which breaks silently when the FIRST file is empty: `FNR` stays equal
to `NR` for the entire NEXT file too, not just its first line, misrouting
every record into the wrong branch and dropping it via `next`. Found while
adding the debt join above (a second lookup file made the same fragility
hit `--brief` too, whenever there was neither chain-root credit nor debt on
a given day). Verified against the shipped 0.21.0 script with a minimal
reproduction. Fixed by matching on `FILENAME` instead of line counts.
`board-selftest.sh`: 142 -> 150 checks (fixtures for FYI-only mail,
mixed owed/pending mail, and the zero-blocked-repos case).
## [0.21.0] - 2026-08-09
### Changed

View file

@ -54,7 +54,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` (142 checks).
STATE.md and no mailbox. Pinned by `board-selftest.sh` (150 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
@ -118,6 +118,53 @@ marketplace plugin. Three components, one boundary:
section 12 pins both halves - the root outranking four owed messages, and the
debtor keeping everything it had.
**"Debt" means OWED, never raw pending, since 0.22.0 - and this NARROWS what
counts as debt, it does not reopen the paragraph above.** The paragraph above
settles a different question: once a repo has debt, is it ever excluded or
capped (no). This one settles what counts as debt in the first place. Through
0.21.0, group 2's `keep`/`mag`/`why=inbox:N` and `--brief`'s whole "repo som
skylder et svar" listing were computed from the raw pending-file count - every
unhandled message in the inbox, including ones the sender declared
`reply-expected: no`. That is a notice, not a request, and 0.11.0 gave
`coord-count.sh` a second column (`owed`) for exactly this distinction - but
`board.sh` never read it. Reported by morning-driver (2026-08-11) and
independently reproduced against the live mailbox 2026-08-13: 27 of 72
pending messages (37.5%) were notices. The fix joins `--plan` and `--brief`
against `coord-count.sh`'s `owed` column by repo name (same technique as the
chain-root `$UNBLOCKS` join below), so a `done`/`deferred`/`blocked` repo
whose only mail is FYI no longer gets a tab, and `--brief` no longer counts a
notice as an obligation. This reverses a decision from session 41
(2026-08-10) that declined to build this filter, on the premise that "the
arrival of the request IS the admission signal" - a premise that assumed
group 2 already meant requests. It didn't; the code computed pending, the
comments already said "owed" throughout, and the plan's own printed header
("Utelatt naar repoet verken skylder svar...") already claimed the exclusion
was debt-based. The fix makes the code match what its own comments and
header already promised. Pinned by board-selftest.sh section 8/12 fixtures
`repo-done-fyi` (pending 2, owed 0 - excluded) and `repo-blocked-mixed`
(pending 3, owed 2 - planned on 2, not 3). The TABLE's `INN` column and the
raw scan (`RECORDS` field 6) are UNCHANGED - they answer "what is the state
of every repo," not "who is waiting on you," and stay on raw pending by
design.
**The `$UNBLOCKS`/`$RECORDS` join used `NR==FNR` through 0.21.0, and that
idiom silently drops the entire plan whenever the FIRST file is empty - fixed
to `FILENAME==` comparison in 0.22.0, found while adding the `$OWED` join
above.** Verified against the shipped 0.21.0 script: one in-progress repo
with an unhandled inbox message, zero blocked repos anywhere in the tree
(so `$UNBLOCKS` is empty, which is a common, ordinary tree state, not an
edge case) - `--plan` printed "0 tabber". `NR==FNR` is only true for the
FIRST file's own lines; when that file is empty, `FNR` and `NR` stay equal
for the ENTIRE next file too (not just its first line - verified with a
minimal awk reproduction), so every record in it is misrouted into the
`ub[]` branch and dropped via `next`. This was invisible to
board-selftest.sh because the fixture tree has carried at least one
`blocked` repo since the chain-root feature shipped, and it was invisible
on the real tree because `~/repos` currently always has one too - neither
is a guarantee. `FILENAME==UBF`/`FILENAME==OWF` compares the exact path,
never line counts, so an empty lookup file degrades to "nothing matched,"
never to "everything after it is misrouted."
**Chain-root credit lands on the ROOT and nowhere else.** For every `blocked`
repo the `blocked-on` edge is followed transitively to the first repo that is
not itself blocked. Crediting a blocked repo would open a tab that cannot move;
@ -300,7 +347,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 (191/191),
`bash scripts/board-selftest.sh` must exit 0 (142/142) and
`bash scripts/board-selftest.sh` must exit 0 (150/150) and
`bash scripts/route-selftest.sh` must exit 0 (69/69).
- English for all code, docs, and commit messages (public repo). Norwegian
trigger aliases in the skill description are deliberate.

View file

@ -6,15 +6,15 @@ Session A in repo X leaves a message for repo Y; the next session in repo Y gets
> Formerly `coord` (renamed in v0.3.0). The plugin is now `repo-mailbox`; the CLI (`coord-send`, `coord-inbox`, `coord-done`), the mailbox root `~/.claude/coord/` and `CLAUDE_COORD_DIR` keep their names — they are the transport protocol, not the product.
> **Solo-maintained, fork-and-own.** This plugin is a starting point, not a vendor product. Issues are welcome as signals; pull requests are not accepted. See the [marketplace governance](https://git.fromaitochitta.com/open/ktg-plugin-marketplace/src/branch/main/GOVERNANCE.md) for the full model.
> **Solo-maintained, fork-and-own.** This plugin is a starting point, not a vendor product. Issues are welcome as signals; pull requests are not accepted. See the [organisation governance](https://git.fromaitochitta.com/open/repo-standard/src/branch/main/GOVERNANCE.md) for the full model.
*AI-generated: all code produced by Claude Code through dialog-driven development.*
![Version](https://img.shields.io/badge/version-0.21.0-blue)
![Version](https://img.shields.io/badge/version-0.22.0-blue)
![Hooks](https://img.shields.io/badge/hooks-1-green)
![Skills](https://img.shields.io/badge/skills-3-orange)
![CLI scripts](https://img.shields.io/badge/CLI_scripts-8-blue)
![Selftest checks](https://img.shields.io/badge/selftest_checks-402-blue)
![Selftest checks](https://img.shields.io/badge/selftest_checks-410-blue)
---
@ -162,7 +162,7 @@ Note that raising the inbox's priority (Rule 7) deliberately does **not** widen
## Development
bash scripts/coord-selftest.sh # 191 checks against a throwaway mailbox
bash scripts/board-selftest.sh # 142 checks against a throwaway repo tree
bash scripts/board-selftest.sh # 150 checks against a throwaway repo tree
bash scripts/route-selftest.sh # 69 checks, incl. the route->board round trip
npm test # all three selftests via node --test

View file

@ -1,6 +1,6 @@
{
"name": "repo-mailbox",
"version": "0.21.0",
"version": "0.22.0",
"private": true,
"type": "module",
"engines": {

View file

@ -611,6 +611,67 @@ mkrepo "$ROOT/repo-done-owes"
mkdir -p "$CLAUDE_COORD_DIR/repo-done-owes/inbox"
echo "msg" > "$CLAUDE_COORD_DIR/repo-done-owes/inbox/2026-msg1-from-w.md"
# repo-done-fyi: done, with pending mail, but every message declares
# reply-expected: no (a notice, not a request). Every OTHER fixture message in
# this suite is written with no frontmatter at all, which owes_reply() in
# coord-count.sh treats as owing by default - so pending and owed have been
# equal in every prior fixture, and a raw-pending-as-debt bug is invisible to
# this suite without a genuine FYI-only mailbox. Measured on the live mailbox
# 2026-08-11 by morning-driver: 14 of 22 unhandled messages were notices.
mkrepo "$ROOT/repo-done-fyi"
{
echo "# STATE - repo-done-fyi"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=done; blocked-on=-; next-cost=Sonnet 5/high -->"
printf 'Ferdig %s to varsler ligger i innboksen, ingen skylder svar.\n' "$EMDASH"
} > "$ROOT/repo-done-fyi/STATE.md"
mkdir -p "$CLAUDE_COORD_DIR/repo-done-fyi/inbox"
for n in 1 2; do
{
echo "---"
echo "from: w"
echo "to: repo-done-fyi"
echo "reply-expected: no"
echo "---"
echo "fyi only"
} > "$CLAUDE_COORD_DIR/repo-done-fyi/inbox/2026-msg$n-from-w.md"
done
# repo-blocked-mixed: blocked, 3 pending messages but only 2 owe a reply - the
# third declares reply-expected: no. repo-blocked-owes (above) cannot prove
# ranking uses OWED rather than raw pending, because every one of its messages
# happens to owe; this fixture separates the two counts (3 pending, 2 owed).
mkrepo "$ROOT/repo-blocked-mixed"
{
echo "# STATE - repo-blocked-mixed"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=blocked; blocked-on=-; next-cost=Opus 5/high -->"
printf 'Blokkert %s tre meldinger, bare to skylder svar.\n' "$EMDASH"
} > "$ROOT/repo-blocked-mixed/STATE.md"
mkdir -p "$CLAUDE_COORD_DIR/repo-blocked-mixed/inbox"
echo "msg" > "$CLAUDE_COORD_DIR/repo-blocked-mixed/inbox/2026-msg1-from-w.md"
echo "msg" > "$CLAUDE_COORD_DIR/repo-blocked-mixed/inbox/2026-msg2-from-w.md"
{
echo "---"
echo "from: w"
echo "to: repo-blocked-mixed"
echo "reply-expected: no"
echo "---"
echo "fyi only"
} > "$CLAUDE_COORD_DIR/repo-blocked-mixed/inbox/2026-msg3-from-w.md"
# A fresh --brief, taken after repo-done-fyi and repo-blocked-mixed exist:
# BRIEF (above) predates both fixtures, so it cannot exercise them.
BRIEF3="$("$BOARD" --roots "$ROOT" --brief 2>/dev/null)"
printf '%s' "$BRIEF3" | grep -q 'repo-done-fyi'; [ $? -ne 0 ]
check "brief excludes a repo whose pending mail is all FYI (no debt)" $?
printf '%s' "$BRIEF3" | grep -q 'repo-blocked-mixed'
check "brief includes a repo with mixed mail (it has 2 owed)" $?
printf '%s' "$BRIEF3" | grep -qE 'repo-blocked-mixed[[:space:]]+INN[[:space:]]+2([[:space:]]|$)'
check "brief's INN count is owed messages, not raw pending (3 pending, 2 owed)" $?
PLAN="$("$BOARD" --roots "$ROOT" --plan 2>/dev/null)"; rc=$?
check "--plan exits 0" "$rc"
@ -695,6 +756,22 @@ printf '%s\n' "$PLAN" | grep -A4 '^repo=repo-done-owes$' | grep -q '^status=done
check "a done repo in the plan still reports its real status" $?
printf '%s\n' "$PLAN" | grep -m1 '^# Utelatt' | grep -q 'skylder svar'
check "the header states the exclusions are conditional on owing nothing" $?
# The header above already says "skylder svar" (owes a reply) - a done repo
# whose pending mail is 100% notices does not owe a reply, so it is excluded
# on the SAME rule as repo-wt, not a new one. Pending (2) must not stand in
# for owed (0).
printf '%s' "$PLAN" | grep -q '^repo=repo-done-fyi$'; [ $? -ne 0 ]
check "plan excludes a done repo whose pending mail is all FYI (owed 0, pending 2)" $?
# blocked-with-debt (repo-blocked-owes, above) stays planned on its debt; this
# fixture proves the COUNT that seats and ranks it is the owed count, not the
# raw pending count - 3 pending, 2 owed.
printf '%s' "$PLAN" | grep -q '^repo=repo-blocked-mixed$'
check "a blocked repo with mixed mail is still planned on its owed messages" $?
printf '%s\n' "$PLAN" | grep -A4 '^repo=repo-blocked-mixed$' | grep -q '^why=inbox:2$'
check "why=inbox:N counts owed messages, not raw pending (3 pending, 2 owed)" $?
printf '%s' "$PLAN" | grep -q '^repo=repo-d$'; [ $? -ne 0 ]
check "plan excludes a deferred repo" $?
printf '%s' "$PLAN" | grep -q '^repo=plug-x$'; [ $? -ne 0 ]
@ -1144,6 +1221,35 @@ check "--brief still exits 0 after the ranking change" $?
[ "$n2_tab" -ge "$n_tab" ]
check "the grouped plan hides nothing an earlier ordering showed" $?
# --- 13. Zero blocked repos does not empty the plan (NR==FNR regression) ---
# Isolated root, not the shared fixture tree above: that tree has carried a
# blocked repo (repo-blocked-owes, plug-x, ...) since the chain-root feature
# shipped, which is exactly the state that kept this bug invisible to every
# check above it. $UNBLOCKS is empty only when NO repo in the scan is blocked
# - an ordinary tree state, reproduced here with a root that has none at all.
# Verified against the shipped 0.21.0 script before this fix existed: this
# exact fixture produced "0 tabber" - solo-repo, with an unhandled inbox
# message and nothing blocking anything, vanished from its own plan.
NOBLOCK_ROOT="$(mktemp -d)"
NOBLOCK_COORD="$(mktemp -d)"
mkrepo "$NOBLOCK_ROOT/solo-repo"
{
echo "# STATE - solo-repo"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=Sonnet 5/high -->"
echo "Ordinary work, nothing blocked anywhere in this tree."
} > "$NOBLOCK_ROOT/solo-repo/STATE.md"
mkdir -p "$NOBLOCK_COORD/solo-repo/inbox"
echo "msg" > "$NOBLOCK_COORD/solo-repo/inbox/2026-msg1-from-w.md"
NOBLOCK_PLAN="$(CLAUDE_COORD_DIR="$NOBLOCK_COORD" "$BOARD" --roots "$NOBLOCK_ROOT" --plan 2>/dev/null)"
printf '%s' "$NOBLOCK_PLAN" | grep -q '^repo=solo-repo$'
check "zero blocked repos in the tree does not empty the plan (NR==FNR regression)" $?
printf '%s\n' "$NOBLOCK_PLAN" | grep -A3 '^repo=solo-repo$' | grep -q '^why=inbox:1$'
check "the surviving repo still ranks correctly with an empty chain-root lookup" $?
/bin/rm -rf "$NOBLOCK_ROOT" "$NOBLOCK_COORD" 2>/dev/null
echo ""
echo "board-selftest: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] || exit 1

View file

@ -327,7 +327,32 @@ done > "${TMPDIR:-/tmp}/board.$$"
RECORDS="${TMPDIR:-/tmp}/board.$$"
UNBLOCKS="${TMPDIR:-/tmp}/board-unblocks.$$"
trap '/bin/rm -f "$RECORDS" "$UNBLOCKS" 2>/dev/null' EXIT
OWED="${TMPDIR:-/tmp}/board-owed.$$"
trap '/bin/rm -f "$RECORDS" "$UNBLOCKS" "$OWED" 2>/dev/null' EXIT
# --- Owed lookup (name -> messages that actually owe a reply) --------------
# `inbox` (field 6 of RECORDS) is raw pending-file count and stays that way -
# it is what the TABLE means by INN, "what is the state of every repo". Group
# 2 of --plan and the whole of --brief mean something narrower, "who is
# waiting on you", and a pending message is not automatically that: a sender
# can declare reply-expected: no. Measured on the live mailbox 2026-08-13: 27
# of 72 pending messages (37.5%) are notices, not requests - conflating the two
# admitted done/deferred/blocked repos into --plan on FYI mail alone and
# mislabeled --brief's own "repo som skylder svar" line with the same number.
# coord-count.sh already separates them (pending, then debt) for exactly this
# reason; this is that value, joined by name like chain-root credit above.
#
# Built only for --brief and --plan, the only two consumers: the plain table
# view scans a repo tree and prints raw INN by design, never touching the
# mailbox beyond what RECORDS already read, and paying an extra coord-count.sh
# subprocess on every invocation would tax that path for a lookup it never
# uses.
: > "$OWED"
if { [ "$BRIEF" -eq 1 ] || [ "$PLAN" -eq 1 ]; } && [ -f "$SELFDIR/coord-count.sh" ]; then
ow_tab="$(printf '\t')"
bash "$SELFDIR/coord-count.sh" 2>/dev/null \
| awk -F"$ow_tab" -v OFS='|' '{print $1, $3}' > "$OWED"
fi
# --- Chain-root credit -----------------------------------------------------
# For every blocked repo, walk `blocked-on` up to the ROOT of the chain - the
@ -449,8 +474,21 @@ brief_orphans() {
}
brief() {
n_owe="$(awk -F'|' '$6+0 > 0' "$RECORDS" | wc -l | tr -d ' ')"
tot_msg="$(awk -F'|' '$6+0 > 0 {s+=$6} END{print s+0}' "$RECORDS")"
# OWED, not raw pending (field 6): this briefing is titled "repo som skylder
# et svar" (repos that owe a reply), and a pending message that declared
# reply-expected: no is not that - it is a notice. Joined by FILENAME, never
# NR==FNR (see plan()'s comment on the same join): $OWED can legitimately be
# empty on a debt-free day, and NR==FNR misroutes an entire subsequent file
# when the file before it was empty.
n_owe="$(awk -F'|' -v OWF="$OWED" '
FILENAME==OWF { ow[$1] = $2 + 0; next }
{ name = $3; if ((name in ow) && ow[name] > 0) print }
' "$OWED" "$RECORDS" | wc -l | tr -d ' ')"
tot_msg="$(awk -F'|' -v OWF="$OWED" '
FILENAME==OWF { ow[$1] = $2 + 0; next }
{ name = $3; if ((name in ow) && ow[name] > 0) s += ow[name] }
END { print s+0 }
' "$OWED" "$RECORDS")"
echo "BRIEFING $(date '+%Y-%m-%d %H:%M') - repo som skylder et svar"
echo "Kilder: STATE.md (NESTE + route-linje), git, coord-innboks. 0 modellkall."
@ -469,9 +507,12 @@ brief() {
fi
# Most-owed first: the repo holding up the most other sessions is read first.
awk -F'|' '$6+0 > 0' "$RECORDS" | sort -t'|' -k6,6nr | \
while IFS='|' read -r bucket sortkey name status cost inbox dirty age last dir blockedon neste; do
printf ' %-32s INN %-4s %s\n' "$name" "$inbox" "$status"
awk -F'|' -v OFS='|' -v OWF="$OWED" '
FILENAME==OWF { ow[$1] = $2 + 0; next }
{ name = $3; owed = (name in ow) ? ow[name] : 0; if (owed > 0) print owed, $0 }
' "$OWED" "$RECORDS" | sort -t'|' -k1,1nr | \
while IFS='|' read -r owed bucket sortkey name status cost inbox dirty age last dir blockedon neste; do
printf ' %-32s INN %-4s %s\n' "$name" "$owed" "$status"
# Wrapped, not cut - the whole line is the point, but a 500-character one
# is unreadable in a file nobody watched being written. Locale is set for
# the same reason trunc sets it: under LC_ALL=C fold counts BYTES and can
@ -699,10 +740,23 @@ plan() {
# anything added after it could be swallowed by a stray separator.
pf="${TMPDIR:-/tmp}/board-plan.$$"
: > "$pf"
awk -F'|' -v OFS='|' '
NR==FNR { ub[$1] = $2 + 0; next }
# Both lookup files are matched by FILENAME, never by NR==FNR: that idiom
# silently breaks when the FIRST file is empty (zero blocked repos is a real,
# common tree state) - FNR then stays equal to NR for the entire NEXT file
# too, not just its first line, so every record in it is misrouted into the
# ub[] branch and dropped via `next`. Measured against 0.21.0: one in-progress
# repo with an unhandled inbox message, zero blocked repos anywhere, produced
# "0 tabber". FILENAME comparison never depends on how many lines a prior
# file contributed, empty or not.
awk -F'|' -v OFS='|' -v UBF="$UNBLOCKS" -v OWF="$OWED" '
FILENAME==UBF { ub[$1] = $2 + 0; next }
FILENAME==OWF { ow[$1] = $2 + 0; next }
{
name = $3; status = $4; cost = $5; inbox = $6 + 0
name = $3; status = $4; cost = $5
# OWED, not raw pending (field 6): a message can decline a reply
# (reply-expected: no), and that declaration is what group 2 is FOR - an
# obligation to another session, not merely a file in the inbox.
owed = (name in ow) ? ow[name] : 0
# A blocked repo is credited with nothing even if something waits on it:
# it cannot move its own next step, so opening it releases no one. The
# credit already went to the root of its chain.
@ -712,7 +766,7 @@ plan() {
# Membership is unchanged: a repo that releases someone is planned even
# when it owes nothing and its own status would otherwise exclude it, and
# done, deferred, blocked and no-STATE repos with no debt stay out.
keep = (inbox > 0) || (u > 0) || (status == "in-progress") || \
keep = (owed > 0) || (u > 0) || (status == "in-progress") || \
(status == "planned") || (status == "?") || (status ~ /^MALFORMED:/)
if (!keep) next
@ -720,18 +774,18 @@ plan() {
# `mag` orders within a group by the quantity that group is about - released
# in group 1, messages owed in group 2 - and is 0 where the group has no
# quantity, leaving those to the tiebreaks below.
if (u > 0) { grp = 1; mag = u; why = "unblocks:" u }
else if (inbox > 0) { grp = 2; mag = inbox; why = "inbox:" inbox }
else if (status == "planned") { grp = 3; mag = 0; why = "planned" }
else if (status == "in-progress") { grp = 4; mag = 0; why = "in-progress" }
else { grp = 5; mag = 0; why = "uavklart" }
if (u > 0) { grp = 1; mag = u; why = "unblocks:" u }
else if (owed > 0) { grp = 2; mag = owed; why = "inbox:" owed }
else if (status == "planned") { grp = 3; mag = 0; why = "planned" }
else if (status == "in-progress") { grp = 4; mag = 0; why = "in-progress" }
else { grp = 5; mag = 0; why = "uavklart" }
# Cheap-to-move first under quota pressure. Sorted ascending, so 0 = cheap.
cheap = (cost ~ /[Ss]onnet/) ? 0 : 1
print grp, mag, cheap, why, $0
}
' "$UNBLOCKS" "$RECORDS" \
' "$UNBLOCKS" "$OWED" "$RECORDS" \
| sort -t'|' -k1,1n -k2,2nr -k3,3n -k6,6n \
| cut -d'|' -f4- >> "$pf"

View file

@ -20,7 +20,7 @@ description: >-
user names no repo and no tool — choosing *between* repos is this skill. Not for
"where were we" inside the current repo: that is this repo's own STATE.md,
already injected at session start.
version: "0.21.0"
version: "0.22.0"
---
# board — which repo deserves the next session

View file

@ -15,7 +15,7 @@ description: >-
covers retiring a broadcast that has become wrong or obsolete: "retract that
broadcast", "that announcement is outdated, pull it", "trekk tilbake kringkastingen",
"den broadcasten er utdatert".
version: "0.21.0"
version: "0.22.0"
---
# coord-send — natural-language front door for inter-repo messages

View file

@ -14,7 +14,7 @@ description: >-
the operator names no model and no tool — choosing the model for the next
session IS this skill. Not for choosing which REPO gets the next session:
that is the `board` skill.
version: "0.21.0"
version: "0.22.0"
---
# route — what the next session should run with