@haposoft/cafekit 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/bin/__tests__/claude-settings.test.js +123 -0
- package/bin/__tests__/codex-ask-guard.test.js +123 -0
- package/bin/__tests__/codex-ask-projection.test.js +51 -0
- package/bin/__tests__/codex-hooks-ownership.test.js +80 -1
- package/bin/__tests__/codex-hooks.test.js +938 -237
- package/bin/__tests__/codex-native-hook-boundaries.test.js +136 -0
- package/bin/__tests__/codex-native.test.js +222 -149
- package/bin/__tests__/codex-projection-parity.test.js +314 -0
- package/bin/__tests__/codex-quiet-hooks.test.js +170 -0
- package/bin/__tests__/codex-research-projection.test.js +70 -0
- package/bin/__tests__/codex-specs-projection.test.js +76 -0
- package/bin/__tests__/develop-contract.test.js +102 -1362
- package/bin/__tests__/docs-sync-on-demand.test.js +25 -20
- package/bin/__tests__/inspect-paths.test.js +183 -0
- package/bin/__tests__/installer-safety.test.js +335 -0
- package/bin/__tests__/legacy-authority-decoupled.test.js +14 -19
- package/bin/__tests__/legacy-hooks-retired.test.js +18 -8
- package/bin/__tests__/omp-bridge.test.js +66 -12
- package/bin/__tests__/omp-hooks.test.js +11 -5
- package/bin/__tests__/orca-session.test.js +7 -0
- package/bin/__tests__/package-inventory.test.js +1 -395
- package/bin/__tests__/privacy-paths.test.js +317 -0
- package/bin/__tests__/spec-narrowing.test.js +30 -134
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -372
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- package/bin/__tests__/usage-hook-retired.test.js +5 -3
- package/bin/__tests__/validator-grounding.test.js +17 -2929
- package/bin/install.js +25 -0
- package/bin/lib/codex-install.js +134 -12
- package/bin/lib/context.js +1 -1
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/claude-runtime.js +91 -25
- package/bin/phases/claude-settings.js +22 -1
- package/bin/phases/codex-hooks.js +96 -33
- package/bin/phases/codex-runtime.js +45 -49
- package/bin/phases/copy-payload.js +11 -28
- package/bin/phases/omp-runtime.js +6 -3
- package/package.json +2 -3
- package/src/claude/CLAUDE.md +4 -0
- package/src/claude/agents/code-auditor.md +9 -6
- package/src/claude/agents/debugger.md +3 -9
- package/src/claude/agents/docs-keeper.md +4 -7
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +6 -7
- package/src/claude/agents/inspector.md +2 -0
- package/src/claude/agents/project-manager.md +2 -2
- package/src/claude/agents/spec-maker.md +4 -4
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/gitignore +3 -0
- package/src/claude/hooks/__tests__/installed-root.test.js +111 -0
- package/src/claude/hooks/__tests__/precompact.test.js +206 -10
- package/src/claude/hooks/__tests__/privacy-block.test.js +27 -0
- package/src/claude/hooks/__tests__/session-writeenv.test.js +25 -0
- package/src/claude/hooks/__tests__/spec-gate.test.js +311 -340
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +230 -8
- package/src/claude/hooks/__tests__/state.test.js +207 -7
- package/src/claude/hooks/__tests__/update-check.test.js +88 -0
- package/src/claude/hooks/agent.cjs +7 -6
- package/src/claude/hooks/inspect-block.cjs +15 -63
- package/src/claude/hooks/lib/inspect-paths.cjs +304 -0
- package/src/claude/hooks/lib/parser.cjs +1 -1
- package/src/claude/hooks/lib/privacy-command-analysis.cjs +191 -17
- package/src/claude/hooks/lib/privacy-paths.cjs +313 -0
- package/src/claude/hooks/lib/project-root.cjs +15 -0
- package/src/claude/hooks/lib/rules-reservation.cjs +36 -0
- package/src/claude/hooks/lib/spec-touch.cjs +67 -0
- package/src/claude/hooks/lib/update-check.cjs +77 -0
- package/src/claude/hooks/precompact.cjs +8 -15
- package/src/claude/hooks/privacy-block.cjs +23 -205
- package/src/claude/hooks/rules.cjs +6 -28
- package/src/claude/hooks/session.cjs +34 -117
- package/src/claude/hooks/spec-gate.cjs +17 -89
- package/src/claude/hooks/spec-state.cjs +89 -96
- package/src/claude/hooks/state.cjs +96 -51
- package/src/claude/migration-manifest.json +20 -8
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +1 -2
- package/src/claude/rules/hook-protocols.md +1 -1
- package/src/claude/rules/workflow.md +1 -1
- package/src/claude/runtime.json +0 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +2 -2
- package/src/claude/scripts/provenance.cjs +14 -0
- package/src/claude/scripts/spec-receipt.cjs +0 -114
- package/src/claude/scripts/spec-resolver.cjs +41 -457
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/settings/settings.json +1 -1
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/ai-multimodal/scripts/check_setup.py +8 -68
- package/src/claude/skills/ai-multimodal/scripts/gemini_batch_process.py +6 -28
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +25 -108
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -7
- package/src/claude/skills/develop/SKILL.md +22 -70
- package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
- package/src/claude/skills/git/SKILL.md +1 -1
- package/src/claude/skills/git/references/worktree-blueprint.md +1 -1
- package/src/claude/skills/research/SKILL.md +24 -55
- package/src/claude/skills/sync/SKILL.md +12 -34
- package/src/claude/skills/sync/references/rebind-and-audit.md +2 -2
- package/src/claude/skills/test/SKILL.md +39 -123
- package/src/claude/skills/test/references/execution-strategy.md +11 -22
- package/src/claude/skills/ui-ux/SKILL.md +1 -1
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +24 -79
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- package/src/codex/AGENTS.md +8 -0
- package/src/codex/hooks/ask-guard.cjs +54 -0
- package/src/codex/hooks/inspect-block.cjs +11 -7
- package/src/codex/hooks/lib/inspect-paths.cjs +1 -199
- package/src/codex/hooks/lib/privacy-paths.cjs +1 -0
- package/src/codex/hooks/lib/privacy-state.cjs +98 -16
- package/src/codex/hooks/lib/rules-reservation.cjs +38 -0
- package/src/codex/hooks/lib/secret-keywords.cjs +1 -0
- package/src/codex/hooks/lib/spec-receipt.cjs +2 -30
- package/src/codex/hooks/lib/spec-session-touch.cjs +100 -0
- package/src/codex/hooks/lib/spec-touch.cjs +3 -0
- package/src/codex/hooks/lib/spec-utils.cjs +6 -45
- package/src/codex/hooks/lib/update-check.cjs +1 -0
- package/src/codex/hooks/precompact.cjs +39 -0
- package/src/codex/hooks/privacy-approval.cjs +28 -17
- package/src/codex/hooks/privacy-block.cjs +9 -215
- package/src/codex/hooks/rules.cjs +4 -30
- package/src/codex/hooks/secret-output-guardrail.cjs +1 -1
- package/src/codex/hooks/session.cjs +47 -11
- package/src/codex/hooks/spec-gate.cjs +32 -74
- package/src/codex/hooks/spec-state.cjs +68 -46
- package/src/codex/hooks/state.cjs +25 -4
- package/src/codex/hooks.json +39 -7
- package/src/codex/rules/hook-protocols.md +13 -6
- package/src/common/AGENTS.md +4 -10
- package/src/omp/extensions/cafekit-bridge.mjs +12 -4
- package/src/omp/hooks/privacy-block.cjs +24 -206
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- package/src/claude/hooks/lib/context.cjs +0 -527
- package/src/claude/hooks/lib/detect.cjs +0 -474
- package/src/claude/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3914
- package/src/claude/skills/code-review/references/verification-gate.md +0 -41
- package/src/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- package/src/claude/skills/specs/templates/task.md +0 -47
- package/src/codex/rules/state-sync.md +0 -60
|
@@ -182,12 +182,7 @@ const V3_SPECS_BUNDLE = [
|
|
|
182
182
|
'SKILL.md',
|
|
183
183
|
'references/review.md',
|
|
184
184
|
'references/templates.md',
|
|
185
|
-
'templates/
|
|
186
|
-
'templates/requirements-init.md',
|
|
187
|
-
'templates/requirements.md',
|
|
188
|
-
'templates/research.md',
|
|
189
|
-
'templates/spec-state.json',
|
|
190
|
-
'templates/task.md'
|
|
185
|
+
'templates/research.md'
|
|
191
186
|
];
|
|
192
187
|
const OBSOLETE_SPECS_FILES = [
|
|
193
188
|
'references/archive-workflow.md',
|
|
@@ -265,6 +260,99 @@ function parseGeneratedTomlString(content, key) {
|
|
|
265
260
|
return JSON.parse(match[1]);
|
|
266
261
|
}
|
|
267
262
|
|
|
263
|
+
test('installed Codex instructions reference all ten runtime rules', () => {
|
|
264
|
+
inTempProject(root => {
|
|
265
|
+
const result = install(root);
|
|
266
|
+
assert.equal(result.status, 0, result.stderr);
|
|
267
|
+
const instructions = fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf8');
|
|
268
|
+
const rules = ['workflow', 'ai-dev-rules', 'orchestrator', 'manage-docs', 'skill-workflow-routing', 'skill-domain-routing', 'review-audit-self-decision', 'process-management', 'state-sync', 'hook-protocols'];
|
|
269
|
+
for (const rule of rules) {
|
|
270
|
+
assert.ok(instructions.includes(`.codex/rules/${rule}.md`), rule);
|
|
271
|
+
assert.ok(fs.existsSync(path.join(root, '.codex/rules', `${rule}.md`)), rule);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('installed Codex orchestrator describes high reasoning on the session model', () => {
|
|
277
|
+
inTempProject(root => {
|
|
278
|
+
const source = fs.readFileSync(path.join(PACKAGE_ROOT, 'src/claude/rules/orchestrator.md'));
|
|
279
|
+
const result = install(root);
|
|
280
|
+
assert.equal(result.status, 0, result.stderr);
|
|
281
|
+
const content = fs.readFileSync(path.join(root, '.codex/rules/orchestrator.md'), 'utf8');
|
|
282
|
+
assert.doesNotMatch(content, /strongest|below[^\n]*fable/);
|
|
283
|
+
assert.match(content, /high reasoning effort/);
|
|
284
|
+
assert.match(content, /session model unless[^.]*configur/i);
|
|
285
|
+
assert.deepEqual(fs.readFileSync(path.join(PACKAGE_ROOT, 'src/claude/rules/orchestrator.md')), source);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('installed hook protocols share malformed-output handling and Codex receipt guidance', () => {
|
|
290
|
+
inTempProject(root => {
|
|
291
|
+
const result = installPlatforms(root, ['claude', 'codex']);
|
|
292
|
+
assert.equal(result.status, 0, result.stderr);
|
|
293
|
+
const sentence = 'If hook output is malformed, stop and report the exact failure instead of guessing.';
|
|
294
|
+
for (const runtime of ['.claude', '.codex']) {
|
|
295
|
+
const content = fs.readFileSync(path.join(root, runtime, 'rules/hook-protocols.md'), 'utf8');
|
|
296
|
+
assert.ok(content.includes(sentence), runtime);
|
|
297
|
+
}
|
|
298
|
+
assert.match(fs.readFileSync(path.join(root, '.codex/rules/hook-protocols.md'), 'utf8'), /state-sync\.md/);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('installed Claude ignore rules preserve env samples and ignore temporary files', () => {
|
|
303
|
+
inTempProject(root => {
|
|
304
|
+
const result = installPlatforms(root, ['claude']);
|
|
305
|
+
assert.equal(result.status, 0, result.stderr);
|
|
306
|
+
const lines = fs.readFileSync(path.join(root, '.claude/.gitignore'), 'utf8').split(/\r?\n/);
|
|
307
|
+
for (const line of ['!.env.sample', '!.env.template', '*.tmp']) assert.ok(lines.includes(line), line);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const READ_ONLY_ROLE_SUFFIX = '\n\n## Read-only role\n\nRead-only role: never create, never edit, and never delete files, and never run commands that write to disk; return findings in your reply.';
|
|
312
|
+
|
|
313
|
+
function assertInstalledAgentFields(content, name) {
|
|
314
|
+
const source = fs.readFileSync(path.join(PACKAGE_ROOT, 'src/claude/agents', `${name.replace(/_/g, '-')}.md`), 'utf8');
|
|
315
|
+
const { frontmatter, body } = require('../lib/codex-frontmatter').splitFrontmatter(source);
|
|
316
|
+
for (const key of ['name', 'description', 'developer_instructions']) {
|
|
317
|
+
assert.equal((content.match(new RegExp(`^${key} =`, 'gm')) || []).length, 1);
|
|
318
|
+
}
|
|
319
|
+
assert.equal(parseGeneratedTomlString(content, 'name'), name);
|
|
320
|
+
assert.equal(parseGeneratedTomlString(content, 'description'), normalizeCodexBody(frontmatter.description));
|
|
321
|
+
const suffix = ['brainstormer', 'code_auditor', 'inspector', 'researcher'].includes(name)
|
|
322
|
+
? READ_ONLY_ROLE_SUFFIX : '';
|
|
323
|
+
const expectedBody = name === 'git_ops'
|
|
324
|
+
? body.slice(0, body.indexOf('When instantiated as a team member'))
|
|
325
|
+
+ body.split('\n').find(line => line.includes('NEVER arbitrarily use `force push`')).replace(/^3\. /, '- ') + '\n'
|
|
326
|
+
: body;
|
|
327
|
+
assert.equal(parseGeneratedTomlString(content, 'developer_instructions'), normalizeCodexBody(expectedBody).trim() + suffix);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
for (const name of ['brainstormer', 'code_auditor', 'inspector', 'researcher']) {
|
|
331
|
+
test(`installed ${name} agent has exactly one read-only sandbox setting`, () => {
|
|
332
|
+
inTempProject(root => {
|
|
333
|
+
const result = install(root);
|
|
334
|
+
assert.equal(result.status, 0, result.stderr);
|
|
335
|
+
const content = fs.readFileSync(path.join(root, '.codex/agents', `${name}.toml`), 'utf8');
|
|
336
|
+
assert.deepEqual(content.match(/^sandbox_mode\s*=.*$/gm) || [], ['sandbox_mode = "read-only"']);
|
|
337
|
+
assert.ok(parseGeneratedTomlString(content, 'developer_instructions').endsWith(READ_ONLY_ROLE_SUFFIX));
|
|
338
|
+
assertInstalledAgentFields(content, name);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
test('installed implementation agents retain their sandbox defaults', () => {
|
|
344
|
+
inTempProject(root => {
|
|
345
|
+
const result = install(root);
|
|
346
|
+
assert.equal(result.status, 0, result.stderr);
|
|
347
|
+
for (const name of ['implementer', 'test_runner', 'debugger', 'git_ops']) {
|
|
348
|
+
const content = fs.readFileSync(path.join(root, '.codex/agents', `${name}.toml`), 'utf8');
|
|
349
|
+
assert.doesNotMatch(content, /^sandbox_mode\s*=/m);
|
|
350
|
+
assert.doesNotMatch(parseGeneratedTomlString(content, 'developer_instructions'), /## Read-only role/);
|
|
351
|
+
assertInstalledAgentFields(content, name);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
268
356
|
function replaceGeneratedTomlString(content, key, value) {
|
|
269
357
|
const pattern = new RegExp(`^${key} = .+$`, 'gm');
|
|
270
358
|
const matches = [...content.matchAll(pattern)];
|
|
@@ -272,64 +360,6 @@ function replaceGeneratedTomlString(content, key, value) {
|
|
|
272
360
|
return content.replace(pattern, `${key} = ${JSON.stringify(value)}`);
|
|
273
361
|
}
|
|
274
362
|
|
|
275
|
-
function renderCanonicalVerificationExample(template) {
|
|
276
|
-
const values = new Map([
|
|
277
|
-
['SUBJECT_REQ', '1'],
|
|
278
|
-
['X', '1'],
|
|
279
|
-
['PROOF_REQ', '1'],
|
|
280
|
-
['Y', '2'],
|
|
281
|
-
['exact command', 'node --test test/installed.test.js'],
|
|
282
|
-
['exact anchored target', 'src/installed.js#entry'],
|
|
283
|
-
['exact/repository/entrypoint', 'src/installed.js'],
|
|
284
|
-
['observable result', 'the subject behavior and verifier proof both pass'],
|
|
285
|
-
['concrete observable result and proof', 'the subject behavior and verifier proof both pass'],
|
|
286
|
-
['concrete rejected or recovery case', 'invalid input remains rejected and observable'],
|
|
287
|
-
['real entrypoint/caller and grounded anchor expectation', 'the installed entrypoint reaches A-D-01'],
|
|
288
|
-
]);
|
|
289
|
-
return template.replace(/\{\{([^}]+)\}\}/g, (placeholder, name) => (
|
|
290
|
-
values.has(name) ? values.get(name) : placeholder
|
|
291
|
-
));
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function assertInstalledVerificationModel(grounderPath, designTemplate) {
|
|
295
|
-
const { parseVerificationDefinitions } = require(grounderPath);
|
|
296
|
-
assert.equal(typeof parseVerificationDefinitions, 'function');
|
|
297
|
-
const concreteDesign = renderCanonicalVerificationExample(designTemplate);
|
|
298
|
-
const errors = [];
|
|
299
|
-
const definitions = parseVerificationDefinitions(concreteDesign, errors);
|
|
300
|
-
assert.deepEqual(errors, []);
|
|
301
|
-
assert.equal(definitions.size, 1);
|
|
302
|
-
const definition = definitions.get('V1');
|
|
303
|
-
assert.ok(definition);
|
|
304
|
-
assert.deepEqual(definition.subject_criteria, ['R1.1']);
|
|
305
|
-
assert.deepEqual(definition.proof_criteria, []);
|
|
306
|
-
assert.equal(definition.proof_owner, null);
|
|
307
|
-
assert.equal(definition.evidence_anchor, null);
|
|
308
|
-
assert.deepEqual(definition.decision_refs, ['D1', 'I1', 'C1']);
|
|
309
|
-
for (const field of [
|
|
310
|
-
'subject_criteria', 'subject_owner', 'decision_refs', 'method', 'expected',
|
|
311
|
-
'negative', 'reachability'
|
|
312
|
-
]) {
|
|
313
|
-
const value = definition[field];
|
|
314
|
-
assert.ok(
|
|
315
|
-
Array.isArray(value)
|
|
316
|
-
? value.length > 0
|
|
317
|
-
: (typeof value === 'string' ? value.trim() !== '' : value && Object.keys(value).length > 0)
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
for (const mutation of [
|
|
321
|
-
concreteDesign.replace('- **V1**:', '### V1 —'),
|
|
322
|
-
concreteDesign.replace('- **V1**:', '| V1 |'),
|
|
323
|
-
concreteDesign.replace('; Expected ', '\nExpected '),
|
|
324
|
-
concreteDesign.replace('Decision refs ', 'Decisions '),
|
|
325
|
-
]) {
|
|
326
|
-
const mutationErrors = [];
|
|
327
|
-
const mutated = parseVerificationDefinitions(mutation, mutationErrors);
|
|
328
|
-
assert.ok(mutationErrors.length > 0);
|
|
329
|
-
assert.equal(mutated.has('V1'), false);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
363
|
function markdownSection(content, heading) {
|
|
334
364
|
const lines = String(content).split('\n');
|
|
335
365
|
const start = lines.findIndex((line) => line.trim() === `## ${heading}`);
|
|
@@ -879,7 +909,7 @@ function brainstormProjectionIssues(files) {
|
|
|
879
909
|
}
|
|
880
910
|
if (!skill.includes('Persist only approved decisions and semantics, only with user authority')
|
|
881
911
|
|| !skill.includes('redact live secrets, credentials, private keys, access tokens, and unnecessary PII;')
|
|
882
|
-
|| !framework.includes('Do not write "user selected" unless direct user text or the
|
|
912
|
+
|| !framework.includes('Do not write "user selected" unless direct user text or the question in the turn\'s final answer confirms it.')) {
|
|
883
913
|
issues.add('approval-persistence');
|
|
884
914
|
}
|
|
885
915
|
for (const [issue, clause] of Object.entries(adaptiveClauses)) {
|
|
@@ -887,9 +917,9 @@ function brainstormProjectionIssues(files) {
|
|
|
887
917
|
}
|
|
888
918
|
const visible = `${skill}\n${framework}\n${agent}`;
|
|
889
919
|
if (/\bAskUserQuestion\b/.test(visible)
|
|
890
|
-
|| /\b(?:a|an|one|each|every|another|this|that|the|[0-9]+)\s+a
|
|
891
|
-
|| /\ban
|
|
892
|
-
|| /\ba
|
|
920
|
+
|| /\b(?:a|an|one|each|every|another|this|that|the|[0-9]+)\s+a question in the turn\'s final answer\b/i.test(visible)
|
|
921
|
+
|| /\ban question in the turn\'s final answer\b/i.test(visible)
|
|
922
|
+
|| /\ba question in the turn\'s final answer\s+(?:calls|batches)\b/i.test(visible)) {
|
|
893
923
|
issues.add('projection-grammar');
|
|
894
924
|
}
|
|
895
925
|
return [...issues].sort();
|
|
@@ -1442,26 +1472,26 @@ test('Codex payload transform emits native skill and subagent syntax', () => {
|
|
|
1442
1472
|
|
|
1443
1473
|
test('Codex payload transform keeps structured user-input grammar across determiners', () => {
|
|
1444
1474
|
const cases = [
|
|
1445
|
-
['standalone bare token', 'AskUserQuestion', 'a
|
|
1446
|
-
['standalone code token', '`AskUserQuestion`', 'a
|
|
1447
|
-
['standalone multi-backtick token', '``AskUserQuestion``', 'a
|
|
1448
|
-
['standalone padded multi-backtick token', '`` AskUserQuestion ``', 'a
|
|
1449
|
-
['one', 'one AskUserQuestion call', 'one
|
|
1450
|
-
['one multi-backtick', 'one ``AskUserQuestion`` call', 'one
|
|
1451
|
-
['one padded multi-backtick', 'one `` AskUserQuestion `` call', 'one
|
|
1452
|
-
['each', 'each AskUserQuestion call', 'each
|
|
1453
|
-
['every', 'every AskUserQuestion batch', 'every
|
|
1454
|
-
['another', 'another AskUserQuestion call', 'another
|
|
1455
|
-
['this', 'this AskUserQuestion call', 'this
|
|
1456
|
-
['that', 'that AskUserQuestion call', 'that
|
|
1457
|
-
['a', 'a AskUserQuestion call', 'a
|
|
1458
|
-
['an', 'an AskUserQuestion call', 'a
|
|
1459
|
-
['capitalized each', 'Each AskUserQuestion call', 'Each
|
|
1460
|
-
['capitalized an', 'An AskUserQuestion call', 'A
|
|
1461
|
-
['the', 'the AskUserQuestion call', 'the
|
|
1462
|
-
['numeric', '27 AskUserQuestion calls', '27
|
|
1463
|
-
['plural calls', 'AskUserQuestion calls', '
|
|
1464
|
-
['plural batches', 'AskUserQuestion batches', '
|
|
1475
|
+
['standalone bare token', 'AskUserQuestion', 'a question in the turn\'s final answer'],
|
|
1476
|
+
['standalone code token', '`AskUserQuestion`', 'a question in the turn\'s final answer'],
|
|
1477
|
+
['standalone multi-backtick token', '``AskUserQuestion``', 'a question in the turn\'s final answer'],
|
|
1478
|
+
['standalone padded multi-backtick token', '`` AskUserQuestion ``', 'a question in the turn\'s final answer'],
|
|
1479
|
+
['one', 'one AskUserQuestion call', 'one question in the turn\'s final answer call'],
|
|
1480
|
+
['one multi-backtick', 'one ``AskUserQuestion`` call', 'one question in the turn\'s final answer call'],
|
|
1481
|
+
['one padded multi-backtick', 'one `` AskUserQuestion `` call', 'one question in the turn\'s final answer call'],
|
|
1482
|
+
['each', 'each AskUserQuestion call', 'each question in the turn\'s final answer call'],
|
|
1483
|
+
['every', 'every AskUserQuestion batch', 'every question in the turn\'s final answer batch'],
|
|
1484
|
+
['another', 'another AskUserQuestion call', 'another question in the turn\'s final answer call'],
|
|
1485
|
+
['this', 'this AskUserQuestion call', 'this question in the turn\'s final answer call'],
|
|
1486
|
+
['that', 'that AskUserQuestion call', 'that question in the turn\'s final answer call'],
|
|
1487
|
+
['a', 'a AskUserQuestion call', 'a question in the turn\'s final answer call'],
|
|
1488
|
+
['an', 'an AskUserQuestion call', 'a question in the turn\'s final answer call'],
|
|
1489
|
+
['capitalized each', 'Each AskUserQuestion call', 'Each question in the turn\'s final answer call'],
|
|
1490
|
+
['capitalized an', 'An AskUserQuestion call', 'A question in the turn\'s final answer call'],
|
|
1491
|
+
['the', 'the AskUserQuestion call', 'the question in the turn\'s final answer call'],
|
|
1492
|
+
['numeric', '27 AskUserQuestion calls', '27 question in the turn\'s final answer calls'],
|
|
1493
|
+
['plural calls', 'AskUserQuestion calls', 'question in the turn\'s final answer calls'],
|
|
1494
|
+
['plural batches', 'AskUserQuestion batches', 'question in the turn\'s final answer batches']
|
|
1465
1495
|
];
|
|
1466
1496
|
|
|
1467
1497
|
for (const [name, input, expected] of cases) {
|
|
@@ -1469,27 +1499,27 @@ test('Codex payload transform keeps structured user-input grammar across determi
|
|
|
1469
1499
|
assert.equal(actual, expected, name);
|
|
1470
1500
|
assert.equal(normalizeCodexBody(actual, '/fixture/instructions.md'), expected, `${name} idempotence`);
|
|
1471
1501
|
assert.doesNotMatch(actual, /\bAskUserQuestion\b/, `${name} Claude-only token`);
|
|
1472
|
-
assert.doesNotMatch(actual, /\ban
|
|
1502
|
+
assert.doesNotMatch(actual, /\ban question in the turn\'s final answer\b/i, `${name} incompatible article`);
|
|
1473
1503
|
assert.doesNotMatch(
|
|
1474
1504
|
actual,
|
|
1475
|
-
/\b(?:a|an|one|each|every|another|this|that|the|[0-9]+)\s+a
|
|
1505
|
+
/\b(?:a|an|one|each|every|another|this|that|the|[0-9]+)\s+a question in the turn\'s final answer\b/i,
|
|
1476
1506
|
`${name} doubled article`
|
|
1477
1507
|
);
|
|
1478
1508
|
}
|
|
1479
1509
|
|
|
1480
1510
|
assert.equal(
|
|
1481
1511
|
normalizeCodexBody('one\nAskUserQuestion call', '/fixture/instructions.md'),
|
|
1482
|
-
'one\
|
|
1512
|
+
'one\nquestion in the turn\'s final answer call',
|
|
1483
1513
|
'a single Markdown soft break must preserve its determiner relationship'
|
|
1484
1514
|
);
|
|
1485
1515
|
assert.equal(
|
|
1486
1516
|
normalizeCodexBody('AskUserQuestion\nCalls', '/fixture/instructions.md'),
|
|
1487
|
-
'
|
|
1517
|
+
'question in the turn\'s final answer\nCalls',
|
|
1488
1518
|
'a single Markdown soft break must preserve a plural relationship'
|
|
1489
1519
|
);
|
|
1490
1520
|
assert.equal(
|
|
1491
1521
|
normalizeCodexBody('one\n\nAskUserQuestion call', '/fixture/instructions.md'),
|
|
1492
|
-
'one\n\na
|
|
1522
|
+
'one\n\na question in the turn\'s final answer call',
|
|
1493
1523
|
'a paragraph break must not bind a prior determiner'
|
|
1494
1524
|
);
|
|
1495
1525
|
assert.equal(
|
|
@@ -1567,9 +1597,9 @@ test('Codex structured-input corpus oracle stays differential and production-awa
|
|
|
1567
1597
|
assert.deepEqual(actualPaths, expectedPaths, 'every source occurrence needs an explicit projection oracle');
|
|
1568
1598
|
|
|
1569
1599
|
const instructionExpectedSnippets = new Map([
|
|
1570
|
-
['src/claude/skills/git/SKILL.md', 'Present options via a
|
|
1571
|
-
['src/claude/skills/scout/SKILL.md', '**Fallback to a
|
|
1572
|
-
['src/claude/skills/sync/references/rebind-and-audit.md', 'End by asking with a
|
|
1600
|
+
['src/claude/skills/git/SKILL.md', 'Present options via a question in the turn\'s final answer — header'],
|
|
1601
|
+
['src/claude/skills/scout/SKILL.md', '**Fallback to a question in the turn\'s final answer:**'],
|
|
1602
|
+
['src/claude/skills/sync/references/rebind-and-audit.md', 'End by asking with a question in the turn\'s final answer when the host has']
|
|
1573
1603
|
]);
|
|
1574
1604
|
for (const [relativePath, expectedSnippet] of instructionExpectedSnippets) {
|
|
1575
1605
|
const sourcePath = path.join(PACKAGE_ROOT, relativePath);
|
|
@@ -1578,7 +1608,7 @@ test('Codex structured-input corpus oracle stays differential and production-awa
|
|
|
1578
1608
|
const withSentinel = source.replace(/`?\bAskUserQuestion\b`?/g, sentinel);
|
|
1579
1609
|
assert.notEqual(withSentinel, source, `${relativePath} differential control must replace a token`);
|
|
1580
1610
|
const expected = normalizeCodexBody(withSentinel, sourcePath)
|
|
1581
|
-
.replaceAll(sentinel, 'a
|
|
1611
|
+
.replaceAll(sentinel, 'a question in the turn\'s final answer');
|
|
1582
1612
|
const actual = normalizeCodexBody(source, sourcePath);
|
|
1583
1613
|
assert.equal(actual, expected, `${relativePath} independent differential oracle`);
|
|
1584
1614
|
assert.match(actual, new RegExp(expectedSnippet.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
@@ -1587,7 +1617,7 @@ test('Codex structured-input corpus oracle stays differential and production-awa
|
|
|
1587
1617
|
const specMaker = fs.readFileSync(SPEC_MAKER_SOURCE_PATH, 'utf8');
|
|
1588
1618
|
const convertedAgent = convertCodexAgentContent(specMaker, path.basename(SPEC_MAKER_SOURCE_PATH));
|
|
1589
1619
|
assert.doesNotMatch(convertedAgent, /\bAskUserQuestion\b/);
|
|
1590
|
-
assert.doesNotMatch(convertedAgent, /
|
|
1620
|
+
assert.doesNotMatch(convertedAgent, /question in the turn\'s final answer/);
|
|
1591
1621
|
|
|
1592
1622
|
const sessionPath = path.join(PACKAGE_ROOT, 'src/claude/hooks/session.cjs');
|
|
1593
1623
|
const session = normalizeCodexBody(fs.readFileSync(sessionPath, 'utf8'), sessionPath);
|
|
@@ -2059,6 +2089,45 @@ function sessionLauncher(projectRoot) {
|
|
|
2059
2089
|
return { config, command: config.hooks.SessionStart[0].hooks[0].command };
|
|
2060
2090
|
}
|
|
2061
2091
|
|
|
2092
|
+
test('parity regression: moved Codex checkout rebinds both launcher fields and preserves metadata', () => {
|
|
2093
|
+
inTempProject((root) => {
|
|
2094
|
+
const oldRoot = path.join(root, "old ' quoted project"), newRoot = path.join(root, "new ' quoted project");
|
|
2095
|
+
fs.mkdirSync(oldRoot);
|
|
2096
|
+
assert.equal(install(oldRoot).status, 0);
|
|
2097
|
+
const configPath = path.join(oldRoot, '.codex/hooks.json');
|
|
2098
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
2099
|
+
config.hooks.SessionStart[0].matcher = 'custom-startup';
|
|
2100
|
+
config.hooks.SessionStart[0].hooks[0].timeout = 41;
|
|
2101
|
+
fs.writeFileSync(configPath, JSON.stringify(config));
|
|
2102
|
+
fs.renameSync(oldRoot, newRoot);
|
|
2103
|
+
const movedConfig = path.join(newRoot, '.codex/hooks.json');
|
|
2104
|
+
const before = fs.readFileSync(movedConfig, 'utf8');
|
|
2105
|
+
assert.equal(install(newRoot, ['--dry-run']).status, 0);
|
|
2106
|
+
assert.equal(fs.readFileSync(movedConfig, 'utf8'), before, 'dry run modified launchers');
|
|
2107
|
+
const upgraded = install(newRoot);
|
|
2108
|
+
assert.equal(upgraded.status, 0, `${upgraded.stdout}\n${upgraded.stderr}`);
|
|
2109
|
+
const rebound = sessionLauncher(newRoot);
|
|
2110
|
+
assert.equal(rebound.config.hooks.SessionStart[0].matcher, 'custom-startup');
|
|
2111
|
+
assert.equal(rebound.config.hooks.SessionStart[0].hooks[0].timeout, 41);
|
|
2112
|
+
const launched = runPosixLauncher(rebound.command, newRoot);
|
|
2113
|
+
assert.equal(launched.status, 0, launched.stderr);
|
|
2114
|
+
assert.match(launched.stdout, /Session startup\./);
|
|
2115
|
+
const windowsCommand = rebound.config.hooks.SessionStart[0].hooks[0].commandWindows.replace(/^node /, `"${process.execPath}" `);
|
|
2116
|
+
const launchedWindows = spawnSync(windowsCommand, { cwd: newRoot, encoding: 'utf8', input: JSON.stringify({ session_id: 'moved-windows', hook_event_name: 'SessionStart', source: 'startup', cwd: newRoot }), shell: true });
|
|
2117
|
+
assert.equal(launchedWindows.status, 0, launchedWindows.stderr);
|
|
2118
|
+
assert.match(launchedWindows.stdout, /Session startup\./);
|
|
2119
|
+
for (const { handler } of allHookLaunchers(rebound.config)) {
|
|
2120
|
+
const encoded = handler.commandWindows.match(/\s([A-Za-z0-9_-]+)$/)?.[1];
|
|
2121
|
+
assert.ok(encoded);
|
|
2122
|
+
const target = Buffer.from(encoded, 'base64url').toString('utf8');
|
|
2123
|
+
assert.equal(path.dirname(target), fs.realpathSync(path.join(newRoot, '.codex/hooks')));
|
|
2124
|
+
}
|
|
2125
|
+
const stable = fs.readFileSync(movedConfig, 'utf8');
|
|
2126
|
+
assert.equal(install(newRoot).status, 0);
|
|
2127
|
+
assert.equal(fs.readFileSync(movedConfig, 'utf8'), stable, 'reinstall changed stable hooks config');
|
|
2128
|
+
});
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2062
2131
|
test('Codex POSIX hook launchers run without Git and outside the repository root', () => {
|
|
2063
2132
|
inTempProject((root) => {
|
|
2064
2133
|
// A launcher that asks Git for its root gets nothing here and the outer root one
|
|
@@ -2160,22 +2229,12 @@ test('Codex installed Specs and spec-maker reject adaptive coverage mutations',
|
|
|
2160
2229
|
'.codex/rules/state-sync.md',
|
|
2161
2230
|
'.codex/rules/review-audit-self-decision.md',
|
|
2162
2231
|
'.codex/rules/process-management.md',
|
|
2163
|
-
'.codex/scripts/spec-ground.cjs',
|
|
2164
|
-
'.codex/scripts/validate-spec-output.cjs',
|
|
2165
2232
|
'.agents/.gitignore',
|
|
2166
2233
|
...V3_SPECS_BUNDLE.map((file) => `.agents/skills/specs/${file}`)
|
|
2167
2234
|
]) {
|
|
2168
2235
|
assert.equal(fs.existsSync(path.join(root, relative)), true, `missing ${relative}`);
|
|
2169
2236
|
}
|
|
2170
2237
|
|
|
2171
|
-
const installedDesign = fs.readFileSync(
|
|
2172
|
-
path.join(root, '.agents/skills/specs/templates/design.md'), 'utf8'
|
|
2173
|
-
);
|
|
2174
|
-
assert.equal((installedDesign.match(/^## Verification Definitions$/gm) || []).length, 1);
|
|
2175
|
-
assertInstalledVerificationModel(
|
|
2176
|
-
path.join(root, '.codex/scripts/spec-ground.cjs'),
|
|
2177
|
-
installedDesign
|
|
2178
|
-
);
|
|
2179
2238
|
const installedSpecsRoot = path.join(root, '.agents/skills/specs');
|
|
2180
2239
|
assert.throws(
|
|
2181
2240
|
() => installedAuthoringProjectionIssues(root, '.agents/skills/specs'),
|
|
@@ -2191,22 +2250,6 @@ test('Codex installed Specs and spec-maker reject adaptive coverage mutations',
|
|
|
2191
2250
|
);
|
|
2192
2251
|
assertInstalledAuthoringProjection(root, installedSpecsRoot);
|
|
2193
2252
|
|
|
2194
|
-
const installedTask = fs.readFileSync(
|
|
2195
|
-
path.join(root, '.agents/skills/specs/templates/task.md'), 'utf8'
|
|
2196
|
-
);
|
|
2197
|
-
assert.deepEqual(
|
|
2198
|
-
[...installedTask.matchAll(/^## (.+)$/gm)].map((match) => match[1]),
|
|
2199
|
-
['Outcome', 'Scope', 'Anchors and Ownership', 'Changes', 'Acceptance', 'Dependencies', 'Verification Plan']
|
|
2200
|
-
);
|
|
2201
|
-
assert.match(installedTask, /^- \*\*Task role:\*\*/m);
|
|
2202
|
-
|
|
2203
|
-
const installedState = JSON.parse(fs.readFileSync(
|
|
2204
|
-
path.join(root, '.agents/skills/specs/templates/spec-state.json'), 'utf8'
|
|
2205
|
-
));
|
|
2206
|
-
assert.deepEqual(Object.keys(installedState.workflow_policy).sort(), [
|
|
2207
|
-
'assurance_level', 'classified_minimum', 'planning_depth', 'risks', 'version'
|
|
2208
|
-
]);
|
|
2209
|
-
|
|
2210
2253
|
const agentsGitignore = fs.readFileSync(
|
|
2211
2254
|
path.join(root, '.agents', '.gitignore'),
|
|
2212
2255
|
'utf8'
|
|
@@ -2217,7 +2260,6 @@ test('Codex installed Specs and spec-maker reject adaptive coverage mutations',
|
|
|
2217
2260
|
|
|
2218
2261
|
if (process.platform !== 'win32') {
|
|
2219
2262
|
for (const [sourceRelative, installedRelative] of [
|
|
2220
|
-
['src/claude/scripts/validate-spec-output.cjs', '.codex/scripts/validate-spec-output.cjs'],
|
|
2221
2263
|
['src/claude/skills/chrome-devtools/scripts/install.sh', '.agents/skills/chrome-devtools/scripts/install.sh'],
|
|
2222
2264
|
['src/claude/skills/ai-multimodal/scripts/check_setup.py', '.agents/skills/ai-multimodal/scripts/check_setup.py']
|
|
2223
2265
|
]) {
|
|
@@ -2338,7 +2380,7 @@ test('Codex installed Specs and spec-maker reject adaptive coverage mutations',
|
|
|
2338
2380
|
const questionSkill = path.join(root, '.agents', 'skills', 'ask', 'SKILL.md');
|
|
2339
2381
|
fs.appendFileSync(questionSkill, '\nUSER-CODEX-SENTINEL\n');
|
|
2340
2382
|
// Fresh installs are exact. Refresh/upgrade intentionally documents the
|
|
2341
|
-
//
|
|
2383
|
+
// Untracked obsolete files and edited current skills both stay on refresh.
|
|
2342
2384
|
const obsoleteSpecsRule = path.join(
|
|
2343
2385
|
root, '.agents', 'skills', 'specs', 'rules', 'design-principles.md'
|
|
2344
2386
|
);
|
|
@@ -2352,13 +2394,14 @@ test('Codex installed Specs and spec-maker reject adaptive coverage mutations',
|
|
|
2352
2394
|
assert.equal(
|
|
2353
2395
|
fs.existsSync(obsoleteSpecsRule),
|
|
2354
2396
|
true,
|
|
2355
|
-
'
|
|
2397
|
+
'Codex refresh preserves untracked obsolete skill files'
|
|
2356
2398
|
);
|
|
2357
2399
|
|
|
2358
2400
|
const metadataPath = path.join(root, '.codex', 'cafekit.json');
|
|
2359
2401
|
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
2360
2402
|
metadata.version = '0.14.1';
|
|
2361
2403
|
fs.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`);
|
|
2404
|
+
fs.writeFileSync(obsoleteSpecsRule, 'LEGACY-CODEX-ORPHAN\n');
|
|
2362
2405
|
|
|
2363
2406
|
const upgrade = install(root);
|
|
2364
2407
|
assert.equal(upgrade.status, 0, `${upgrade.stdout}\n${upgrade.stderr}`);
|
|
@@ -2367,8 +2410,10 @@ test('Codex installed Specs and spec-maker reject adaptive coverage mutations',
|
|
|
2367
2410
|
assert.equal(
|
|
2368
2411
|
fs.existsSync(obsoleteSpecsRule),
|
|
2369
2412
|
true,
|
|
2370
|
-
'
|
|
2413
|
+
'Codex upgrade preserves untracked obsolete skill files'
|
|
2371
2414
|
);
|
|
2415
|
+
assert.match(sameVersion.stdout + sameVersion.stderr, /Preserved 1 obsolete runtime/);
|
|
2416
|
+
assert.match(upgrade.stdout + upgrade.stderr, /Preserved 1 obsolete runtime/);
|
|
2372
2417
|
assert.ok(fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf8').startsWith(userInstructions));
|
|
2373
2418
|
|
|
2374
2419
|
const forced = install(root, ['--force-overwrite']);
|
|
@@ -2598,7 +2643,7 @@ test('Codex installed Brainstorm skill reference and agent preserve proportional
|
|
|
2598
2643
|
{
|
|
2599
2644
|
name: 'decision-provenance-removed',
|
|
2600
2645
|
source: 'framework',
|
|
2601
|
-
from: 'Do not write "user selected" unless direct user text or the
|
|
2646
|
+
from: 'Do not write "user selected" unless direct user text or the question in the turn\'s final answer\n confirms it.',
|
|
2602
2647
|
to: 'Write "user selected" for inferred defaults.',
|
|
2603
2648
|
expected: ['approval-persistence']
|
|
2604
2649
|
},
|
|
@@ -2606,7 +2651,7 @@ test('Codex installed Brainstorm skill reference and agent preserve proportional
|
|
|
2606
2651
|
name: 'installed-grammar-corruption',
|
|
2607
2652
|
source: 'skill',
|
|
2608
2653
|
from: '## Completion bar',
|
|
2609
|
-
to: 'one a
|
|
2654
|
+
to: 'one a question in the turn\'s final answer call\n\n## Completion bar',
|
|
2610
2655
|
expected: ['projection-grammar']
|
|
2611
2656
|
}
|
|
2612
2657
|
];
|
|
@@ -3136,8 +3181,6 @@ test('Codex install on top of existing Claude installation preserves content and
|
|
|
3136
3181
|
assert.equal(fs.existsSync(path.join(root, '.codex', 'runtime.json')), true, '.codex/runtime.json should exist');
|
|
3137
3182
|
assert.equal(fs.existsSync(path.join(root, '.codex', 'hooks', 'privacy-block.cjs')), true, '.codex/hooks/privacy-block.cjs should exist');
|
|
3138
3183
|
assert.equal(fs.existsSync(path.join(root, '.codex', 'rules', 'workflow.md')), true, '.codex/rules/workflow.md should exist');
|
|
3139
|
-
assert.equal(fs.existsSync(path.join(root, '.codex', 'scripts', 'spec-ground.cjs')), true, '.codex/scripts/spec-ground.cjs should exist');
|
|
3140
|
-
assert.equal(fs.existsSync(path.join(root, '.codex', 'scripts', 'validate-spec-output.cjs')), true, '.codex/scripts/validate-spec-output.cjs should exist');
|
|
3141
3184
|
assert.equal(fs.existsSync(path.join(root, '.agents', '.gitignore')), true, '.agents/.gitignore should exist');
|
|
3142
3185
|
assert.equal(fs.existsSync(path.join(root, '.agents', 'skills', 'specs', 'SKILL.md')), true, '.agents/skills/specs/SKILL.md should exist');
|
|
3143
3186
|
|
|
@@ -3170,23 +3213,54 @@ test('Codex installed code_auditor carries no Strict attestation marker', () =>
|
|
|
3170
3213
|
});
|
|
3171
3214
|
});
|
|
3172
3215
|
|
|
3173
|
-
test('Codex
|
|
3216
|
+
test('reinstall preserves untracked Codex runtime files while omp keeps its cleanup policy', () => {
|
|
3217
|
+
for (const platform of ['codex', 'omp']) inTempProject(root => {
|
|
3218
|
+
const first = installPlatforms(root, ['codex', 'omp']);
|
|
3219
|
+
assert.equal(first.status, 0, `${first.stdout}\n${first.stderr}`);
|
|
3220
|
+
const runtime = `.${platform}`;
|
|
3221
|
+
const retired = ['change-firewall.cjs', 'spec-final-state.cjs'];
|
|
3222
|
+
for (const name of retired) fs.writeFileSync(path.join(root, runtime, 'scripts', name), 'legacy\n');
|
|
3223
|
+
const retiredSkillFiles = platform === 'codex'
|
|
3224
|
+
? ['code-review/references/verification-gate.md', 'specs/references/archive-workflow.md'] : [];
|
|
3225
|
+
for (const rel of retiredSkillFiles) {
|
|
3226
|
+
const target = path.join(root, '.agents/skills', rel);
|
|
3227
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
3228
|
+
fs.writeFileSync(target, 'legacy\n');
|
|
3229
|
+
}
|
|
3230
|
+
const again = installPlatforms(root, [platform], ['--force-overwrite']);
|
|
3231
|
+
assert.equal(again.status, 0, `${again.stdout}\n${again.stderr}`);
|
|
3232
|
+
const preserved = platform === 'codex';
|
|
3233
|
+
for (const rel of retiredSkillFiles) assert.equal(fs.existsSync(path.join(root, '.agents/skills', rel)), preserved, rel);
|
|
3234
|
+
for (const name of retired) assert.equal(fs.existsSync(path.join(root, runtime, 'scripts', name)), preserved, name);
|
|
3235
|
+
if (preserved) assert.match(again.stdout + again.stderr, /Preserved 4 obsolete runtime/);
|
|
3236
|
+
assert.equal(fs.existsSync(path.join(root, '.agents/skills/code-review/SKILL.md')), true);
|
|
3237
|
+
assert.equal(fs.existsSync(path.join(root, runtime, 'scripts/spec-resolver.cjs')), true);
|
|
3238
|
+
});
|
|
3239
|
+
});
|
|
3240
|
+
|
|
3241
|
+
test('reinstall removes the retired spec.json templates from Claude and Codex but a dry run keeps them', () => {
|
|
3174
3242
|
inTempProject((root) => {
|
|
3175
|
-
const
|
|
3176
|
-
assert.equal(
|
|
3177
|
-
const
|
|
3178
|
-
const
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
const
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3243
|
+
const first = installPlatforms(root, ['claude', 'codex']);
|
|
3244
|
+
assert.equal(first.status, 0, `${first.stdout}\n${first.stderr}`);
|
|
3245
|
+
const templateDirs = ['.claude/skills/specs/templates', '.agents/skills/specs/templates'];
|
|
3246
|
+
const retired = ['design.md', 'task.md', 'requirements.md', 'requirements-init.md', 'spec-state.json'];
|
|
3247
|
+
for (const dir of templateDirs) {
|
|
3248
|
+
for (const name of retired) fs.writeFileSync(path.join(root, dir, name), 'legacy\n');
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
const dry = installPlatforms(root, ['claude', 'codex'], ['--force-overwrite', '--dry-run']);
|
|
3252
|
+
assert.equal(dry.status, 0, `${dry.stdout}\n${dry.stderr}`);
|
|
3253
|
+
for (const dir of templateDirs) {
|
|
3254
|
+
for (const name of retired) {
|
|
3255
|
+
assert.equal(fs.existsSync(path.join(root, dir, name)), true, `dry run removed ${dir}/${name}`);
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
const again = installPlatforms(root, ['claude', 'codex'], ['--force-overwrite']);
|
|
3260
|
+
assert.equal(again.status, 0, `${again.stdout}\n${again.stderr}`);
|
|
3261
|
+
for (const dir of templateDirs) {
|
|
3262
|
+
assert.deepEqual(fs.readdirSync(path.join(root, dir)), ['research.md'], `${dir} keeps only research.md`);
|
|
3263
|
+
}
|
|
3190
3264
|
});
|
|
3191
3265
|
});
|
|
3192
3266
|
|
|
@@ -3282,4 +3356,3 @@ test('Claude and Codex installed rules preserve the ported review and process gu
|
|
|
3282
3356
|
}
|
|
3283
3357
|
});
|
|
3284
3358
|
});
|
|
3285
|
-
|