Action required
Run specflow upgrade in every repo that has specflow installed, and update the binary. This
release changes the CLI and four managed files: the claim-batch, finish-batch, and
prune-ledgers procedures, and their skill triggers.
curl -fsSL https://raw.githubusercontent.com/MatanKoby/specflow/main/install.sh | sh
specflow upgrade
specflow status # `stale` should read `none` afterwards
One thing upgrade deliberately will not do for you: add the new pointer markers to your
BUILD_QUEUE.md. Your queue is a seed file that no verb rewrites, so that edit is yours, once. See
Wrap your pick-order pointer, below. A queue with no markers keeps working and is simply not
measured separately.
Four batches, one subject: the queue preamble
| Batch | What |
|---|---|
| QD | the preamble warning says what is wrong, not just how long: an address, a staleness split, a stale claimable line, a near-miss heading |
| PD | prune-ledgers section 3 greps before it judges, and asks once instead of once per paragraph |
| FL | a finish leaves the queue smaller: the queue edit is subtractive, and the pick-order pointer is a bounded, replace-only block |
| OD | a batch can wait on an outcome, not just on a finish: the new Blocked on: line |
All four came out of one downstream install whose BUILD_QUEUE.md held 446 lines of prose above
its first batch, in a file whose own header says it lists un-done batches only. The 0.1.8 cap
reported that as a number and stopped there. QD makes the report diagnostic, PD makes the cleanup
affordable, and FL and OD close the two inlets that filled it: about 9 lines per finish, which puts
a freshly pruned 45-line preamble back over its cap inside five batches.
The preamble warning is now a diagnosis
specflow next used to print BUILD_QUEUE.md preamble is 467 lines, over its 45-line cap, and the
user who saw it read it as a bug in specflow. It was not, but the message gave a number with no
anchor. Four read-only diagnostics, all in Weigh, so verify reports them too.
- The count carries an address. Preamble lines are attributed to the nearest heading above them:
467 lines above the first "## Batch" heading (446 of them under "## Un-done batches"). - The staleness split is the signal the length never was. The distinct batch ids a preamble
names, split by whether each is still in the queue or already archived inBUILD_QUEUE_DONE.md.
The install above named 43 archived against 2 live. That fires on a short rotten preamble and
stays quiet on a long current one, which is exactly what a line count cannot do. The floor is 5
archived, and archived must outnumber live, so an honest pointer naming a shipped dependency stays
silent. - A stale
claimableline is named with its line number. It is the one preamble defect that
misdirects rather than merely reading old: the field case saidClaimable now: 50while the queue
held 49, 51 and 58 and Batch 50 had shipped weeks earlier. The check reads the batch named in the
sentence the word sits in, so "Claimable: nothing. QD shipped" claims nothing and reports one. - A near-miss heading is named, and this one guards the count itself. A heading that misses the
declared shape (an h3, a lower-case "batch", a colon after it) is not a batch anywhere: it inflates
the preamble count and vanishes fromnextat the same time.
Over-counting is noisy but visible, so the boundary was deliberately not loosened to accept a
near-miss heading: under-counting is silent.
prune-ledgers section 3 greps before it judges
Section 3 asked the agent to sort every preamble paragraph into keep / relocate / delete and put the
three piles to the user. At the 59 lines this repo dogfooded it on, that is right. At 446 lines and
73 paragraphs it produced two findings.
The relocate pile came back empty. Every durable fact in that preamble was already carried by
BUILD_QUEUE_DONE.md or spec/, many of them citing their own destination in their own text. The
expensive half of the section was answering a question a grep had already settled 73 times over.
And 73 asks is an audit that gets abandoned.
So 3a greps first: take a distinctive phrase from each paragraph and look for a home in
specflow/history/, CLAIMS.md, and spec/. A paragraph with a citation is a delete, mechanically,
with the file and line as the evidence, and no ask. Only uncited paragraphs reach the three piles,
and the stop-and-ask is put once, as piles with counts. 3b prescribes the report: one row
per paragraph (line range, opening words, pile, evidence or destination, reason), the content that
lives nowhere else, and the projected line count. Relocation is explicitly verbatim, since a
paragraph reworded on its way into spec/ is no longer the thing that was reviewed.
The section also states the ordering QD's four signals imply: length is the weakest of them. A
40-line preamble naming a dozen shipped batches is rotten; a 60-line one that is all live pick-order
may be fine.
A finish leaves the queue smaller
The finish-time queue edit is subtractive. finish-batch.md step 4 now says so outright: delete
the batch's section, rewrite the pick-order pointer, append nothing. The batch's narrative already
has a home in specflow/history/BUILD_QUEUE_DONE.md and specflow finish already files it there,
so a status paragraph added to the top of the queue is a second copy of prose that already exists,
sitting in the file every future agent reads, that no retention rule ever reaches.
The pick-order pointer is bounded. It is the one region of the preamble a finish is expected
to rewrite, which is exactly why it is the region that grows a line per batch when a finish appends
instead. It is now a marker-delimited block capped at 20 lines of content, measured by Weigh
and reported beside the preamble count:
ledger weight: BUILD_QUEUE.md 125 lines (preamble 34/45, pointer 8/20, ...) · CLAIMS.md 142 lines (5 completed)
Over the cap, the warning names the rule rather than just the number: it is replace-only, rewrite
it, do not append a line per batch. 20 is set where an honest pointer (what is claimable, what it
waits on, what is not ready) stays silent, while one status paragraph per batch clears it
immediately.
specflow finish now prints the ledger weight on its way out. This is the last moment the agent
can still fix a preamble that grew: the meta: complete commit is the next thing it does, and after
that the growth is someone else's to find.
Wrap your pick-order pointer
Once, by hand, in each repo's BUILD_QUEUE.md:
<!-- specflow:pointer:start - replace-only, 20 lines max: rewrite this block, never append to it. -->
> **Pick-order pointer.** ... your existing pointer, unchanged ...
<!-- specflow:pointer:end -->
The markers use the same specflow:<tag>:start shape as the render regions and deliberately not the
specflow:start token the managed-file check matches, so wrapping your pointer does not turn
your queue into a managed region: upgrade and verify go on leaving it alone. A start marker with
no end is reported rather than guessed at. Absent markers mean no separate measurement, not an
error.
Blocked on: gates a batch on an outcome
Depends on: takes batch ids and means completed, so there was no way to say a batch waits on
what an earlier batch found. Downstream, two batches declared they depended on "Batch 46 having
come back answers"; Batch 46 shipped, reported package presence only, and specflow next went on
offering both as claimable. The only thing standing between an agent and a wrong claim was a prose
caveat in the queue preamble: the format could not express the fact, so it got parked where nothing
reads it and nothing prunes it.
A batch section now takes an optional free-text line:
## Batch 47 - the thing that waits
**Blocked on:** Batch 46 having come back with the package answer, which it did not.
- It is a hard gate.
nextnever offers the batch, andclaimrefuses it and writes nothing. - The reason printed is the author's own text, not a gloss over it, folded to the terminal
width. Every other reasonnextprints is a string the CLI wrote and kept short; this one is
whatever you typed. - It coexists with a tag, whose canned gloss the stated reason replaces:
[NOT READY] blocked on Batch 46 having come back with the package answer. The tag is the
category, the line is the specific why, and printing both would say the same thing twice. - Deleting the line clears it, and that is for whoever can answer it, not for an agent on the
way past.Blocked on: noneis accepted too, so an agent that writes the word does not strand a
batch, but the procedures say delete: a leftover "none" is a line that states nothing and that
nothing ever prunes. - An empty
Blocked on:line still blocks, with "no reason given" as the reason. A gate nobody
can act on is a defect in the queue, not a claimable batch, and reading it as absent would let a
truncated line quietly re-open a batch someone meant to close.
Depends on: is untouched: a batch id still means completed, and the two gates are evaluated
separately, so a batch can be waiting on both.
Behavior changes
nextandverifyreport apointer N/20count when the queue carries the markers, and warn
past the cap or on an unclosed block.finishprints the ledger weight after it writes, which it did not before.nextapplies a new blocked-on check, prints aBlocked on:reason as its author wrote it, and
folds a long one to 72 columns.claimrefuses a blocked batch.next --jsoncarriesblockedandblockedOnper item, beside the renderedreason, and
weightcarriespointerBlock,pointerLines, andpointerLimit.templates/base/BUILD_QUEUE.mddeclares both: the preamble namesBlocked on:in the batch
shape, and a freshinitseeds a pointer already wrapped in its markers.- The preamble warning from 0.1.8 is reworded and now comes with up to four companions. If you parse
nextoutput, the preamble line changed shape. prune-ledgers.mdsection 3 and theprune-ledgersskill changed substantially; re-read them
before the next audit.
Install
curl -fsSL https://raw.githubusercontent.com/MatanKoby/specflow/main/install.sh | sh
# or
go install github.com/MatanKoby/specflow/cmd/specflow@v0.1.10
Full diff: v0.1.9...v0.1.10