fix(build): guard the generated manifests and stop tests mutating the tree - #42
Conversation
… tree The drift guard added in theam#40 covers the skill and instruction trees but not the manifest pair, so its own claim — builder and validator cannot disagree — did not hold end to end. `plugins/fullstack-dev-kit/plugin.json` is dual-emitted from `.codex-plugin/plugin.json`, and the validator only checked it structurally: editing the Codex manifest and skipping the build left the portable copy stale while validation still exited 0. The derivation moves into `scripts/lib/bundle-sources.mjs` as `portableManifestFrom()` + `serializeManifest()`. The builder writes through it and the validator re-derives and byte-compares, so the manifests are held to the same rule as the trees. Also from the theam#41 review: - `skills/` and `codex/skills/` copy into one bundle directory on purpose, but the same skill name in both was second-wins at build time and unfixable drift after. `skillNameCollisions()` reports it and both scripts exit non-zero at the cause. - The guard tests edited `instructions/stacks/php.md` and restored it in `finally`. A run killed in between left the tree dirty, which then failed both the in-sync test and CONTRIBUTING's `git diff --exit-code` step. They now run against a disposable copy and never touch a tracked file. - Removed the dead `DST_INSTR` left by the bundle-sources refactor. Tooling only; no plugin version bump. Refs theam#41. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@ophiocus is attempting to deploy a commit to the Monkeys' projects Team on Vercel. A member of the Team first needs to authorize it. |
atamanvega
left a comment
There was a problem hiding this comment.
Thanks @ophiocus — I reviewed this with the kit's own pr-reviewer and it's a clean, correct fix. Build + validate pass, 8/8 tests, git diff --exit-code clean, no leaked tmp dirs. The manifest derive/compare is deterministic and false-positive-safe (same portableManifestFrom + serializeManifest on both sides, fixed key order), the collision guard fails in the builder before it copies anything (so the tree never enters the second-wins state), and the drift tests run against a mkdtempSync copy so nothing tracked is ever opened for writing.
Full disclosure: I'd independently written a near-identical fix (#43) this morning before spotting yours. Yours predates it and is the better of the two — it fails the collision at the cause in the builder, and it adds no new env var to the shipping scripts. So I'm closing #43 in favor of this one.
Two optional, non-blocking notes:
- On a collision, the validator still prints a derivative
bundle copy of "…" differs from the sourceline alongside the clear collision error. Consider suppressing the per-file drift/orphan lines whenskillNameCollisions()is non-empty, so the root cause leads. (That's the one nicety #43 had.) - The
cwd: rootoption in the testrun()helper is a no-op — the scripts self-locate viaimport.meta.url, notcwd. Harmless, just slightly misleading.
Neither blocks. Approving — nice work closing the gap end-to-end. 🚀
…rift it causes (#53) Both follow-ups from the #42 review, taken from the implementation in the closed #43 rather than reinvented. When two skill sources share a name the losing copy also reads as drift, so the per-file missing/drifted/orphan lines are derivative noise on top of the real cause. They are now suppressed while any collision is present. The manifest pair is orthogonal to a name collision, so that check stays unconditional — a tree with both problems still reports both. Also drops `cwd: root` from the test `run()` helper. It never did anything: the sandbox carries its own `scripts/`, and each script self-locates through `import.meta.url`, so invoking the copy is what targets the copy. Not adopted from #43: the `DEVKIT_ROOT` env var. Its absence was one of the two reasons given for keeping #42, so the shipping scripts stay free of a test-only knob. Two tests: a collision with divergent contents reports the cause and nothing derived from it, and a tree with both a collision and manifest drift still reports both. Refs #41, #42, #43. Co-authored-by: Carlos Santana <1540596+ophiocus@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
What & why
Closes #41 — all four items.
The guard in #40 covers the skill and instruction trees but not the manifest pair, so its own headline claim (builder and validator cannot disagree) did not hold end to end.
plugins/fullstack-dev-kit/plugin.jsonis dual-emitted from.codex-plugin/plugin.json, and the validator only checked it structurally —$schema, the name pattern, the forbidden keys. Structure cannot tell you a generated file is current.Confirming your reproduction on
56cd315:description, skip the build56cd315(currentmain)portable … differs from the source it is generated fromThe derivation moves into
scripts/lib/bundle-sources.mjsasportableManifestFrom()+serializeManifest(). The builder writes through it; the validator re-derives from the source and byte-compares. Same rule the trees are held to, same shared-map reason it cannot drift.The other three:
skillNameCollisions()reports a skill name present in bothskills/andcodex/skills/. Both scripts now exit non-zero — the builder before it copies anything, so it fails at the cause rather than as unfixable drift afterwards.mkdtempSync()copy. Nothing tracked is opened for writing at all, so aSIGKILLmid-run cannot leave the tree dirty and break the next run's in-sync test andgit diff --exit-code.DST_INSTR(item 3). Removed.Type
instructions/stacks/…)Verification
Tests go 5 → 8: manifest drift, the committed manifest being a byte-exact derivation of its source, and the collision guard.
Integrity checks on the tests themselves
Passing tests prove nothing until they fail without their fix, so I mutated each one out and checked that exactly the intended test died:
That pass also found a false-pass hole in my own tests, now closed: the sandbox copy is asserted valid before each test mutates it. Without that, a tree missing from the copy list would make the sandbox invalid for an unrelated reason and every expect exit 1 assertion would pass for the wrong one. Proof it fires — dropping
.agentsfrom the copied trees turns 5 silent false passes into 5 loud failures.Also checked, all clean: building twice is a no-op; a missing or malformed
.codex-plugin/plugin.jsonproduces one clear error rather than a crash or a confusing second one; bumping.claude-plugin/plugin.jsonwithout rebuilding is still caught by the existing version check and is not masked by the new one; and a fresh clone of this branch builds, validates and passes 8/8 with a clean tree.One thing I noticed and deliberately did not sweep: the test assertions matched
/instructions\/stacks\/php\.md/while the validator builds those labels withpath.join, which is backslashed on Windows. I normalised separators in the assertions I touched, but the same assumption may exist elsewhere in the repo — out of scope here, happy to file it separately if useful.On the version bump
Not bumped, same reasoning as #40 and your call again:
scripts/never ships, and the bundle is byte-identical after a rebuild (git diff --exit-codeabove), so nothing users install changes. Say the word and I'll push the bump with the CHANGELOG entry moved under it.Checklist
telemetry/andhooks/are untouched.[Unreleased]. No CONTRIBUTING change needed: the contributor-facing workflow is unchanged, only what validation catches.packages/ortelemetry/, I've flagged it for extra review — it does not..claude-plugin/plugin.json— not bumped; your call. Reasoning above.