Enforce the intentional Claude-to-Grok runtime skill-loader clone relationship across verification lanes. - Add an exact rename-diff validator with fixture and live-loader coverage. - Run the validator in pretest, fast verification, and static merge-gate checks. - Document the loader duplication contract and expanded static-validator inventory. Files changed: AGENTS.md | 2 + docs/testing.md | 4 +- package.json | 7 +- .../check-runtime-skill-loader-drift.test.mjs | 113 +++++++++++++++++++++ scripts/__tests__/run-static-gate-checks.test.mjs | 1 + scripts/__tests__/verify-fast.test.mjs | 1 + scripts/check-runtime-skill-loader-drift.mjs | 113 +++++++++++++++++++++ 7 files changed, 236 insertions(+), 5 deletions(-) Fusion-Task-Id: FN-8991 Fusion-Task-Lineage: a3b67752-0043-4336-9a2a-ff391672b31f Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
114 lines
4.2 KiB
JavaScript
114 lines
4.2 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { main } from "../check-runtime-skill-loader-drift.mjs";
|
|
|
|
function loader(name, { sentinel = true, extra = "" } = {}) {
|
|
const sessionName = sentinel ? `stage${name}SessionSkills` : "stageSessionSkills";
|
|
return [
|
|
`export function ${sessionName}() { return "${name}"; }`,
|
|
`const runtime = "${name.toLowerCase()}";`,
|
|
...Array.from({ length: 100 }, (_, index) => `// ${name} loader line ${index + 1}`),
|
|
extra,
|
|
].join("\n");
|
|
}
|
|
|
|
function fixture(claude, grok) {
|
|
const root = mkdtempSync(join(tmpdir(), "runtime-skill-loader-drift-"));
|
|
const claudePath = join(root, "claude.ts");
|
|
const grokPath = join(root, "grok.ts");
|
|
writeFileSync(claudePath, claude);
|
|
writeFileSync(grokPath, grok);
|
|
return { root, claudePath, grokPath };
|
|
}
|
|
|
|
function captureErrors(callback) {
|
|
const errors = [];
|
|
const original = console.error;
|
|
console.error = (message) => errors.push(String(message));
|
|
try {
|
|
return { result: callback(), errors };
|
|
} finally {
|
|
console.error = original;
|
|
}
|
|
}
|
|
|
|
function withFixture(claude, grok, assertion) {
|
|
const files = fixture(claude, grok);
|
|
try {
|
|
assertion(files);
|
|
} finally {
|
|
rmSync(files.root, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
test("clean Claude-to-Grok rename pair passes", () => {
|
|
const claude = loader("Claude");
|
|
withFixture(claude, claude.replace(/Claude/g, "Grok").replace(/claude/g, "grok"), ({ claudePath, grokPath }) => {
|
|
assert.equal(main([], { claudePath, grokPath }), 0);
|
|
});
|
|
});
|
|
|
|
test("one-sided edit reports its differing line", () => {
|
|
const claude = loader("Claude");
|
|
const grok = `${claude.replace(/Claude/g, "Grok").replace(/claude/g, "grok")}\nif (process.platform === "darwin") stageGrokSessionSkills();`;
|
|
withFixture(claude, grok, ({ claudePath, grokPath }) => {
|
|
const { result, errors } = captureErrors(() => main([], { claudePath, grokPath }));
|
|
assert.equal(result, 1);
|
|
assert.match(errors.join("\n"), /first difference: line 104/);
|
|
});
|
|
});
|
|
|
|
test("raw Grok token in Claude copy fails the cross-name guard", () => {
|
|
const claude = `${loader("Claude")}\nconst accidental = "Grok";`;
|
|
withFixture(claude, loader("Grok"), ({ claudePath, grokPath }) => {
|
|
const { result, errors } = captureErrors(() => main([], { claudePath, grokPath }));
|
|
assert.equal(result, 1);
|
|
assert.match(errors.join("\n"), /raw Grok\/grok token/);
|
|
});
|
|
});
|
|
|
|
test("missing input refuses to report success", () => {
|
|
const files = fixture(loader("Claude"), loader("Grok"));
|
|
try {
|
|
const { result, errors } = captureErrors(() => main([], { claudePath: join(files.root, "missing.ts"), grokPath: files.grokPath }));
|
|
assert.equal(result, 1);
|
|
assert.match(errors.join("\n"), /refusing to report success: unable to read Claude loader/);
|
|
} finally {
|
|
rmSync(files.root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("empty or below-floor input refuses to report success", () => {
|
|
withFixture("", loader("Grok"), ({ claudePath, grokPath }) => {
|
|
const { result, errors } = captureErrors(() => main([], { claudePath, grokPath }));
|
|
assert.equal(result, 1);
|
|
assert.match(errors.join("\n"), /non-empty and contain at least 100 lines/);
|
|
});
|
|
});
|
|
|
|
test("zero-substitution Claude fixture refuses to report success", () => {
|
|
const generic = loader("Generic", { sentinel: false });
|
|
withFixture(generic, generic, ({ claudePath, grokPath }) => {
|
|
const { result, errors } = captureErrors(() => main([], { claudePath, grokPath }));
|
|
assert.equal(result, 1);
|
|
assert.match(errors.join("\n"), /canonicalization made zero Claude\/claude substitutions/);
|
|
});
|
|
});
|
|
|
|
test("missing sentinel export refuses to report success", () => {
|
|
const claude = loader("Claude", { sentinel: false });
|
|
withFixture(claude, claude.replace(/Claude/g, "Grok").replace(/claude/g, "grok"), ({ claudePath, grokPath }) => {
|
|
const { result, errors } = captureErrors(() => main([], { claudePath, grokPath }));
|
|
assert.equal(result, 1);
|
|
assert.match(errors.join("\n"), /expected sentinel exports/);
|
|
});
|
|
});
|
|
|
|
test("real runtime loaders remain a clean rename pair", () => {
|
|
assert.equal(main(), 0);
|
|
});
|