Skip to content

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.

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.

  1. 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.
  2. 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.
  3. 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.
  4. Confirm the check is quiet on an unmodified checkout, then confirm it fails on a deliberate one-file drift.
AreaNote
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.ymlWhere the pre-commit invocation is registered.
.github/workflows/Where continuous integration runs the same check.
  • 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.json is 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.
  • 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.
  • none

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.


← Back to Tasks