T-46BK-gate-the-pack-tree-mirror-so-a-lib-edit-cannot-silently
State: planning/proposed · Impact: medium · Complexity: small
Four built plugin trees under solutions/ontological/ each carry a
byte-identical copy of 213 source files. Nothing checks that the copies match
their sources, so editing one source file and forgetting its copies leaves the
repo inconsistent and merges without complaint. A mechanical check closes the
hole.
.agents/packs/sdlc/, .claude-plugin/packs/sdlc/, .cursor-plugin/packs/sdlc/,
and .pi/packs/sdlc/ hold the built plugin surface. For every file in a pack
that has a same-path counterpart under solutions/ontological/, the two are
byte-identical: 213 pairs per tree, 852 in all. The rest of each pack is built
or generated output with no source counterpart, between 63 and 98 files per
tree, such as the compiled CLI bundle and the marketplace manifests.
One pair is a deliberate exception. .pi/packs/sdlc/package.json shares a path
with the solution’s own package.json but is a generated pack manifest, and no
other tree carries a file at that path. A check has to allow it rather than
treat it as drift.
The copies are refreshed in bulk by an occasional rebuild commit that also
rewrites the compiled bundle. Between rebuilds nothing holds the invariant. A
template edit under lib/model/entities/ recently shipped without its copies,
the drift reached main, and a separate change was needed to bring the copies
back. The repair then missed the fourth tree, which arrived from another branch
while the repair was in flight, so the same stale line survived in .pi and
needed a third pass. Automated review passed over all of it, each time excluding
the pack trees as generated noise, which is exactly why the drift stayed
invisible.
Proposed
Section titled “Proposed”A check reports every pack file that has a source counterpart and does not match it, naming each path, and passes on a tree where all 852 pairs agree. It runs where a contributor meets it before pushing, and again in continuous integration. Pack files with no counterpart are ignored, so built output never trips it, and a new pack tree is picked up without editing the check.
Approach
Section titled “Approach”- Write the comparison as a repo script: discover the pack trees rather than
listing them, map each file back to its path under
solutions/ontological/, skip the ones with no counterpart, honour a small exception list for the generated pack manifest, and compare bytes. Exit non-zero with the drifted paths listed. - Decide whether the script also offers a fix mode that copies sources over their stale copies, or whether it only reports and leaves the bulk rebuild as the only writer. Reporting alone is the smaller change.
- Wire it into the pre-commit hook so it runs on a commit that touches any mirrored path, and into continuous integration so a push cannot land drift.
- Confirm the check is quiet on an unmodified checkout, then confirm it fails on a deliberate one-file drift.
| Area | Note |
|---|---|
scripts/ | Where the check belongs. It enforces this repo’s own build layout, so it is repo tooling rather than an sdlc gate. |
solutions/ontological/.agents/packs/sdlc/ | One of the four mirrored trees the check reads. |
solutions/ontological/.claude-plugin/packs/sdlc/ | Second mirrored tree; it carries fewer generated files than the other two. |
solutions/ontological/.cursor-plugin/packs/sdlc/ | Third mirrored tree. |
solutions/ontological/.pi/packs/sdlc/ | Fourth mirrored tree, added after the other three. It is the one that carries the generated package.json exception. |
lefthook.yml | Where the pre-commit invocation is registered. |
.github/workflows/ | Where continuous integration runs the same check. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: The check fails when any mirrored pair differs, and its output names every drifted path.
- AC-2: The check passes on an unmodified checkout of
main. - AC-3: Editing one mirrored source file without updating its copies fails the check; copying the file across every tree makes it pass again.
- AC-4: Pack files with no source counterpart are ignored, so the compiled bundle and
the manifests never cause a failure, and
.pi/packs/sdlc/package.jsonis allowed. - AC-6: Adding a fifth pack tree needs no edit to the check; it is discovered.
- AC-5: The check runs on pre-commit for a commit touching a mirrored path, and in continuous integration on every push.
Out of scope
Section titled “Out of scope”- Changing when or how the bulk rebuild runs.
- Generating the packs as part of the check, or making the check a writer.
- The built files that have no source counterpart.
- Putting the check in the sdlc gate service. The rule describes this repo’s own layout, so it stays in repo tooling.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Found while correcting the milestone body template. That change edited a
mirrored source file and left its copies behind; the drift merged and needed
#2488 to repair. Three automated
review passes on #2267 excluded the
pack trees from their diff comparison as generated output, so none of them saw
it. The repair itself then missed .pi, whose tree landed from
#2482 while the repair was open, so
the same stale line reached main a second time. Three chances to catch one
edit, all missed, is the argument for a mechanical check.