v1.5.0: the pipeline accounts for its own queue

Released 2026-07-23. One skill, one script, one rule: the outstanding work of every migration is consolidated for reading and updated only at its source.

What we were trying to fix

Every migration ends with things not done. Some are owner decisions the pipeline is not allowed to make. Some are tasks that were deliberately deferred. They were being recorded properly, in next_steps and deferred inside each migrated repository’s migration/report.json, plus the kit’s own docs/backlog.md.

Recorded properly, and unreadable in aggregate. Answering “what is still open across everything we have migrated” meant opening every repository in turn and reading a JSON file in each. By the fourth migrated application that had stopped being reasonable, and this release went out on the morning after the fourth one shipped.

The obvious fix is the wrong one, which is why it needed a skill rather than a script. Consolidating a list means writing the list down somewhere, and a consolidated list is a second copy of the truth. It is accurate on the day it is generated and quietly wrong a week later, at which point there are two answers to the same question and no way to tell which is stale.

What we decided

Split the reading from the writing, and let only one of them be centralised.

scripts/followups.py is the aggregator and it is read only. It walks the migrated repositories’ report files and the kit backlog and prints a sorted markdown view, or JSON with a flag. It never edits anything. Ordering is deliberate: owner decisions first, because those are the ones blocking somebody, then tasks by increasing effort, because that is the order they will actually get done in.

The skill is where the writing lives, and its content is three update protocols, all of which act at the source rather than on the consolidated view. “Done” removes the entry from the target repository’s report, ticks it, regenerates that repository’s dashboard and commits. “Closed by decision” moves it into deferred with a date, so a thing that will not be done is recorded as decided rather than as forgotten. And an item can be added after the fact, in the same place. Never a parallel list, which is the rule the whole design exists to enforce.

Phase 7 of the migration pipeline now ends with a follow-ups pass, so the queue is reconciled as part of finishing a migration rather than whenever somebody remembers.

The skill was measured before it shipped, on the skill-creator bench: three cases (consolidate, mark one done, close one by decision), double blind, with and without the skill. Sixteen of sixteen assertions passed with it against twelve of sixteen without. What is useful is not the score but the shape of the four failures, because they are what the skill is actually for. Without it, the baseline invented a closed array that is not in the schema, forgot to regenerate the dashboard, and reinvented the sort order. None of those is a mistake about the task. All three are a model filling in a convention it was never told, plausibly, and differently each time. Then it was run for real, read only, against winrt-sokoban-blazor.

What got cut

The aggregator stayed read only, and that was the design decision rather than a limitation. It would have been a small amount of extra code to let it write back, and it would have made the consolidated view authoritative, which is precisely the thing this release refused to build.

The consolidated list itself is never persisted. It is printed. There is no file for it to become stale in.

What bit us

Two of this release’s three commits are pure delivery friction, both worth recording because they are the kind of thing that never reaches a changelog.

The test fixtures were not actually committed on the first attempt, so the golden test passed locally, where the untracked files existed, and had nothing to run against in CI. And then the push that fixed it did not trigger a CI run at all, so the branch had to be poked a second time to get a result. The feature itself is one commit of ten files; the two after it exist only because the delivery machinery did not do what it appeared to have done.

One note for anyone reading this article against today’s tree: this shipped as skills/followups/ and the directory is now skills/review-followups/. It was renamed when the kit adopted its two naming rules, which require either a verb and an object or a family and a role, and a bare noun is neither. The skill is the same one; only the identifier moved.

This release went out in the morning, between the release that closed the migration portfolio just after midnight and the four review-driven releases that filled the rest of the day. It is the last piece of the migration pipeline’s own story, shipped before the attention turned to the kit’s workflow, and it is one of only two releases that day that came from using the tool rather than from reviewing it.