Files
Charles Wiltgen 7482a0273d fix(codex): ship the suites Codex skills already point at, and fix the pointers
Three suites (axiom-tools, axiom-shipping, axiom-apple-docs) were excluded from
the Codex build. That was not a decision — the original list (2b5d6adb) was a
blanket "Router skills — Codex has native progressive disclosure, so these are
unnecessary". The premise was abandoned and the list curated, but these three
survived: two still annotated "Codex fit not yet assessed", and axiom-tools
carried a reason written when it was named axiom-using-axiom and held ONLY
discipline text. It then absorbed six sub-skills, five of them harness-neutral
tool references, all inheriting an exclusion that predated them.

Measured cost: 41 dangling "see X" pointers plus ~94 name citations across ~40
emitted Codex skills, aimed at files the build never wrote.

- CODEX_EXCLUDED_SUITES is now empty; per-FILE exclusion replaces it, holding one
  entry — getting-started.md, which drives AskUserQuestion and cites 15 /axiom:
  commands, and has zero inbound references.
- dropExcludedSubSkillRows strips a router's table row for a withheld sub-skill,
  so per-file exclusion cannot recreate the defect it exists to avoid.
- rewriteInlineAuditorBlockForCodex translates auditor file paths to Codex skill
  names; that block previously told Codex users to read files this build skips.
- rewriteAgentInvokesForCodex fixes the layer under that: routers said "Launch
  `build-fixer` agent or `/axiom:fix-build`" while `axiom-fix-build` — the thing
  that exists there — appeared zero times. Neither agents nor /axiom: commands
  exist on Codex, and the mapping is not derivable (spm-conflict-resolver →
  axiom-resolve-spm). Applied to routers, agent-skills, and sub-skills, since a
  verbatim copy reintroduces it. Two commands have no Codex equivalent at all
  (console, compare-traces) and are labelled Claude Code-only rather than
  redirected somewhere that does a different job.
- agentToSkillName moved into the shared module so the pre-deploy gate derives
  the agent-skill set instead of guessing from names — a name heuristic
  misclassifies both ways (axiom-health and axiom-testing are routers that read
  verb-first; axiom-swift-simplifier is an agent-skill that does not).
- The count test now derives the agent total from the agents directory; the
  hardcoded 41/68 had drifted from a real 42/69 and passed anyway by being
  self-consistent.
2026-08-15 12:30:00 -07:00

130 lines
7.3 KiB
JavaScript

// Single source of truth for the Codex variant's excluded suites AND the
// fidelity-count math. Imported by BOTH scripts/build-codex.ts (the builder) and
// scripts/pre-deploy.ts (the fidelity gate) so the two cannot drift — that drift
// is what made the pre-deploy count check dead for so long (axiom-altb). Mirrors
// the scripts/version-regex.js precedent: a .js module imported with an explicit
// `.js` extension, because root scripts run under bare-node type-stripping.
//
// CURATED exclusion list, NOT "all routers" — most router suites DO ship to Codex.
// Annotate each entry with WHY it's withheld. History worth keeping:
// - axiom-xcode-mcp was removed 2026-06-13 (axiom-pkek): Codex is a first-class
// consumer (it documents `codex mcp add xcode -- xcrun mcpbridge`), so it ships.
// - 6 stale pre-v3.0 axiom-ios-* names were pruned the same day (axiom-u5c0):
// they matched no current suite and only inflated the excluded count.
// - The last 3 suite entries were removed 2026-08-15 (Axiom-ky1) — see below.
//
// WHY THE SUITE LIST IS NOW EMPTY. The original list (9e9c389c, 2026-03-29) was a
// blanket policy: "Router skills — Codex has native progressive disclosure, so
// these are unnecessary." That premise was abandoned, but three names survived the
// conversion to a curated list: axiom-apple-docs and axiom-shipping (both annotated
// "Codex fit not yet assessed" — a placeholder, never a judgment) and axiom-tools.
//
// axiom-tools was the worst case. Its entry began as `axiom-using-axiom`, excluded
// for "Claude Code-specific discipline injection" when the suite held ONLY that
// discipline text. It was then renamed and absorbed six sub-skills (2026-04-11 →
// 2026-07-04), five of them harness-neutral tool references, all of which inherited
// an exclusion written before they existed.
//
// Measured cost at removal: 41 dangling "see X for the full reference" pointers and
// ~94 name citations across ~40 emitted Codex skills, aimed at files the build never
// wrote. The stated blocker — harness-flavored router prose — does not distinguish
// these suites: axiom-build ships to Codex with 17 `/axiom:` references and 6 agent
// launches, mitigated by the AXIOM_AUDITOR_INLINE block that axiom-tools/SKILL.md
// already carries. Re-adding a suite here needs evidence about THAT suite, not a
// policy inherited from a different one.
export const CODEX_EXCLUDED_SUITES = [];
// Per-FILE exclusion, for the rare sub-skill that cannot function on Codex even
// though its suite ships. Keys are `<suite>/<file>.md`. Suite-level exclusion is a
// blunt instrument — it withholds a whole reference set to suppress one file — so
// prefer this. Any router table row linking an excluded sub-skill is dropped from
// the Codex copy automatically (see dropExcludedSubSkillRows), so a removal here
// never leaves a dangling row behind.
export const CODEX_EXCLUDED_SUBSKILLS = new Set([
// Drives AskUserQuestion (a Claude Code tool with no Codex equivalent) and cites
// 15 `/axiom:` slash commands that do not exist there. Zero inbound references
// from any other skill, so withholding it dangles nothing.
'axiom-tools/getting-started.md',
]);
// True when a suite's sub-skill file is withheld from the Codex build.
export function isExcludedSubSkill(suiteName, fileName, excludeSet = CODEX_EXCLUDED_SUBSKILLS) {
return excludeSet.has(`${suiteName}/${fileName}`);
}
// Strip router table rows that link a withheld sub-skill. A router's routing table
// is its only structural reference to its own sub-skills, so dropping the row is
// what keeps a per-file exclusion from producing the exact defect this whole change
// removes: a pointer to a file that ships nowhere.
export function dropExcludedSubSkillRows(content, suiteName, excludeSet = CODEX_EXCLUDED_SUBSKILLS) {
const withheld = [...excludeSet]
.filter((key) => key.startsWith(`${suiteName}/`))
.map((key) => key.slice(suiteName.length + 1));
if (withheld.length === 0) return content;
return content
.split('\n')
.filter((line) => {
if (!line.trimStart().startsWith('|')) return true;
return !withheld.some((file) => line.includes(`skills/${file}`));
})
.join('\n');
}
// Router suites that actually ship to Codex = source routers minus the excludes
// that match a real source suite. Stale exclude entries (no matching suite) are
// IGNORED, not subtracted — mirroring build-codex's name-filtered traversal, so a
// dead exclude name can't silently lower the expected count.
export function shippedRouterCount(sourceRouterNames, excludeList = CODEX_EXCLUDED_SUITES) {
const exclude = new Set(excludeList);
return sourceRouterNames.filter((name) => !exclude.has(name)).length;
}
// Agent name → Codex skill name. Shared with pre-deploy.ts for the same reason the
// exclude list is: the gate has to classify an emitted directory as router-vs-agent-
// skill, and a name-shaped heuristic gets that wrong in both directions — `axiom-health`
// and `axiom-testing` are routers that look verb-first, `axiom-swift-simplifier` is an
// agent-skill that doesn't. Derive the set, never guess it.
const AGENT_NAME_MAP = {
'build-fixer': 'axiom-fix-build',
'build-optimizer': 'axiom-optimize-build',
'crash-analyzer': 'axiom-analyze-crash',
'health-check': 'axiom-health-check',
'iap-implementation': 'axiom-implement-iap',
'modernization-helper': 'axiom-modernize',
'performance-profiler': 'axiom-profile-performance',
'screenshot-validator': 'axiom-validate-screenshots',
'simulator-tester': 'axiom-test-simulator',
'spm-conflict-resolver': 'axiom-resolve-spm',
'test-debugger': 'axiom-debug-tests',
'test-failure-analyzer': 'axiom-analyze-test-failures',
'test-runner': 'axiom-run-tests',
};
// Default: *-auditor → axiom-audit-*, *-analyzer → axiom-analyze-*, *-scanner → axiom-scan-*
export function agentToSkillName(agentName) {
if (AGENT_NAME_MAP[agentName]) return AGENT_NAME_MAP[agentName];
if (agentName.endsWith('-auditor')) return `axiom-audit-${agentName.replace(/-auditor$/, '')}`;
if (agentName.endsWith('-analyzer')) return `axiom-analyze-${agentName.replace(/-analyzer$/, '')}`;
if (agentName.endsWith('-scanner')) return `axiom-scan-${agentName.replace(/-scanner$/, '')}`;
return `axiom-${agentName}`;
}
// True when an agent's parsed frontmatter has what build-codex requires to emit a
// Codex skill: BOTH `name` and `description`. build-codex skips agents that fail
// this, so the pre-deploy gate must count agents the same way — otherwise a
// description-less agent is counted-but-not-emitted and the fidelity check fails a
// correct build (a false positive). Shared so the emit condition can't drift
// between the builder and the gate, the same way the exclude list is shared.
export function isEmittableAgent(frontmatter) {
return Boolean(frontmatter && frontmatter.name && frontmatter.description);
}
// Total skill dirs the Codex build emits under axiom-codex/skills/ = shipped
// router suites + one generated skill per source agent. The pre-deploy gate must
// span this SAME universe; comparing all codex dirs (routers + agents) against a
// router-only expected count is the bug that made the check unable to ever match.
export function expectedCodexSkillCount(sourceRouterNames, sourceAgentCount, excludeList = CODEX_EXCLUDED_SUITES) {
return shippedRouterCount(sourceRouterNames, excludeList) + sourceAgentCount;
}