Files
Charles Wiltgen 73bcab39b0 fix(scripts): regenerate Codex on version bump and close the version-parity class
set-version.js regenerated the Cursor variant but never Codex, so
axiom-codex/.codex-plugin/plugin.json kept the PREVIOUS version through a
version bump — invisibly. Gate 12f compares skills/ and agents/ mtimes against
that manifest and a pure version bump touches neither, while the version-parity
gate (check 8) covered Cursor's manifest but not Codex's. Confirmed by reverting
the manifest to the prior version and watching Phase 1 pass green.

Root package.json had the identical hole: written by set-version.js, but read by
pre-deploy only for the `pi` manifest block, never into the parity map. Also
confirmed empirically. `pi install` resolves against that file.

- set-version.js now runs build-cursor.ts AND build-codex.ts
- --tag preflight absolves axiom-codex/ output via a new codex-output.js,
  mirroring cursor-output.js (regression the fix itself introduced: the
  preflight would have refused on ~350 files the same run had just written)
- check 8 reads every version carrier from one table; parse wrapped in
  try/catch because build-codex rmSync's and rebuilds in place with no staging
- version-parity.test.ts fails if a carrier is added without a matching read
- methodology-leak.test.ts keeps behavioral-test methodology out of
  .claude/rules/skill-development.md, which the harness appends to every
  skill-file read and therefore reaches a test's GREEN arm but never its control

Parity gate now reports 9 files, up from 7. Unit suite 488/488 (was 478).
Reviewed with code-reviewer; the root package.json hole was its finding.
2026-09-05 10:49:43 -07:00

55 lines
1.9 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { isCodexGeneratedPath } from "./codex-output.js";
import { isCursorGeneratedPath } from "./cursor-output.js";
test("recognises every path the Codex build regenerates", () => {
for (const generated of [
"axiom-codex/README.md",
"axiom-codex/.codex-plugin/plugin.json",
"axiom-codex/skills/axiom-swiftui/SKILL.md",
"axiom-codex/skills/axiom-media/skills/music-library.md",
"axiom-codex/hooks/user-prompt-submit.py",
]) {
assert.equal(isCodexGeneratedPath(generated), true, generated);
}
});
test("does not absolve unrelated working-tree changes", () => {
// The --tag preflight exists to refuse tagging a dirty tree. Widening it for
// generated output must not turn it into a blanket pass.
for (const unrelated of [
"scripts/set-version.js",
"docs/start/codex-install.md",
".claude-plugin/plugins/axiom/agents/build-fixer.md",
"axiom-codex-notes.md",
// build-codex.ts does NOT write the Codex marketplace manifest — it is
// version-free and hand-maintained (see .claude/rules/version-management.md).
".agents/plugins/marketplace.json",
"",
]) {
assert.equal(isCodexGeneratedPath(unrelated), false, unrelated);
}
});
test("normalises Windows separators", () => {
assert.equal(isCodexGeneratedPath("axiom-codex\\README.md"), true);
});
test("the two generated-output predicates are disjoint", () => {
// set-version's --tag preflight ORs these. If they ever overlapped, a path
// could be absolved by the wrong owner and the narrowing intent would rot.
for (const p of [
"axiom-cursor/.cursor-plugin/plugin.json",
".cursor-plugin/marketplace.json",
"axiom-codex/.codex-plugin/plugin.json",
"axiom-codex/skills/axiom-media/SKILL.md",
]) {
assert.equal(
isCursorGeneratedPath(p) && isCodexGeneratedPath(p),
false,
`${p} claimed by both predicates`,
);
}
});