v1.7.0: a stranger installs the plugin and finishes without the author in the room

Released 2026-07-23. The coherence review of the release that had shipped half an hour earlier, nine findings, all nine resolved, and every one of them a seam that only shows when you are not the person who wrote it.

What we were trying to fix

v1.6.0 had tripled the kit’s skill count that morning, from two to six, by importing four generic issue and pull request skills alongside the migration pipeline. A codebase does not absorb three thousand lines without seams, and this release is the review that went looking for them under one question: could somebody who installs this plugin from the marketplace, having never met the author, run /migrate to the end?

The answer was no, in several independent ways.

The scripts resolved their own paths from the current working directory. That is invisible while you are the author standing in the repository, because the current working directory is always the repository. From a marketplace install it is whatever the user happened to be in, and the kit could not find its own templates. One skill, get-repo-profile, had already been anchoring correctly; the others had not, and nothing made them agree.

The documentation contradicted itself about its own scope. The /migrate command described phases 1 through 6. Hard rule 8 said a migration is delivered when it is in production, which is phase 7. Both statements shipped, in the same plugin, and a reader had no way to know which one was the promise.

requirements.json contradicted itself more literally still. A dependency carried a level field saying recommended and a when field saying required. Both were true, for different skills, and the file had no way to say so.

The distributed surface was bilingual in the worst way: not translated, but switching language part way down a file.

And repo-profile.sh had a contract it could not keep. Its promise was that an undetectable field yields a TODO line. Its implementation was a pipeline ending in grep ... | head || echo TODO, and head exits 0 on empty input, so the fallback could never fire. The script had been shipping a promise the code was structurally incapable of keeping, and it was the only script in the kit with no golden test.

What we decided

Anchor everything to the kit rather than to the caller. Every script and template now resolves kit paths from <skill-dir>/../.., the way get-repo-profile already did, and never from the working directory. The interesting part is not the fix, it is the lock: CI gained a foreign working directory step that runs the preflight, the inventory, the follow-ups aggregator and the profile generator from a directory that has nothing to do with the repository. A path bug of this kind is undetectable from inside the repository it is about, so the test had to be run from outside it.

The scope contradiction was decided rather than softened. /migrate officially covers phases 1 through 7, the “six-phase” branding was corrected to “seven-phase” in the README, the plugin manifest and the skill description, and an application with no production target now closes phase 7 with a recorded owner decision. Documented, never silent, is the whole of it: the previous behaviour was not that such an app failed, it was that nobody could tell what had happened.

Requiredness became per skill. requirements.json grew a requiredBy field, so the gh CLI is hard required by the three lifecycle skills and merely recommended elsewhere, and the preflight prints [hard-required by: ...] and emits the field in its JSON. The contradiction disappeared because the file could finally express the thing that was actually true.

The preflight also stopped assembling its JSON by hand. It had been building output with printf and a pipe separator, which is fine until a hint contains a pipe. It now emits through python3, with real escaping, and its statuses were unified in English.

And repo-profile.sh got both halves of its fix at once: every probe routed through a single emit_or_todo() helper, the second convention deleted, and the golden test that would have caught the dead fallback in the first place. Those two are one change. A contract with no test is a comment.

What got cut

The French that stayed, stayed on purpose. The CHANGELOG, the case studies, the output of the follow-ups aggregator (which feeds French reports) and four references of the migration skill were left untranslated, recorded as backlog debt with a trigger rather than translated in the same release. Translating the distributed surface was the finding; translating everything was scope the review had not asked for.

The trigger lists got an honesty note rather than a mechanism. The six per-skill trigger files gained a line saying plainly what CI actually guards, which is their presence, and what remains manual, which is the bench itself. That is not a fix. It is a refusal to let a green check imply something it does not prove, filed as backlog debt with its own trigger.

One deletion is worth naming: create-issue stopped preparing a commit identity it never used, and the plugin manifest stopped enumerating the pipeline phases. A marketing string that repeats the README is a string that drifts from the README.

What bit us

The || echo TODO bug is the one to remember, and not because the mistake is exotic. It is a plain shell mistake anybody makes. What made it survive is that it failed silently in the safe direction: the fallback never fired, so no TODO line was ever wrong, so nothing ever looked broken. A bug that only ever manifests as an absence is invisible to every form of review except a test that asks for the absent thing by name.

Worth flagging for anyone reading this article against today’s tree: two of the names here have since changed. skills/get-repo-profile/ is now skills/profile-repo/, renamed when the kit adopted its two naming rules, and the requiredBy entries that this release wrote for the superpowers plugin are gone, because the kit later brought that doctrine in house under skills/_shared/. This release is where the dependency was written down precisely; a later one is where it was removed. Both were right at the time.