@haposoft/cafekit 0.16.0-rc.3 → 0.16.0-rc.4
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/bin/__tests__/codex-hooks.test.js +444 -14
- package/bin/__tests__/codex-native.test.js +228 -7
- package/bin/__tests__/develop-contract.test.js +615 -185
- package/bin/__tests__/installer-safety.test.js +39 -0
- package/bin/__tests__/opencode-spec-gate.test.js +24 -16
- package/bin/__tests__/package-inventory.test.js +687 -13
- package/bin/__tests__/specs-v2-execution-closeout.test.js +421 -0
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +601 -0
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +952 -0
- package/bin/__tests__/test-runtime-dependency-closure.cjs +182 -0
- package/bin/__tests__/validator-grounding.test.js +1914 -221
- package/bin/lib/managed-writer.js +12 -3
- package/bin/phases/codex-runtime.js +17 -1
- package/package.json +1 -1
- package/src/claude/CLAUDE.md +6 -0
- package/src/claude/agents/code-auditor.md +20 -0
- package/src/claude/agents/spec-maker.md +143 -99
- package/src/claude/hooks/__tests__/completion-authority.test.js +291 -58
- package/src/claude/hooks/__tests__/privacy-block.test.js +172 -0
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +257 -0
- package/src/claude/hooks/__tests__/spec-gate.test.js +115 -40
- package/src/claude/hooks/completion-authority-check.cjs +37 -288
- package/src/claude/hooks/completion-authority-state.cjs +10 -8
- package/src/claude/hooks/completion-authority.cjs +13 -7
- package/src/claude/hooks/docs-sync.cjs +5 -2
- package/src/claude/hooks/lib/privacy-command-analysis.cjs +393 -0
- package/src/claude/hooks/lib/runtime-path-safety.cjs +119 -0
- package/src/claude/hooks/privacy-block.cjs +77 -30
- package/src/claude/hooks/semantic-review-authority.cjs +152 -0
- package/src/claude/hooks/spec-gate.cjs +47 -117
- package/src/claude/hooks/spec-state.cjs +8 -2
- package/src/claude/migration-manifest.json +9 -1
- package/src/claude/rules/state-sync.md +52 -21
- package/src/claude/rules/workflow.md +17 -9
- package/src/claude/scripts/spec-final-state.cjs +244 -0
- package/src/claude/scripts/spec-ground.cjs +535 -173
- package/src/claude/scripts/spec-readiness.cjs +118 -0
- package/src/claude/scripts/spec-receipt.cjs +162 -0
- package/src/claude/scripts/spec-resolver.cjs +67 -16
- package/src/claude/scripts/spec-scaffold.cjs +1354 -139
- package/src/claude/scripts/spec-semantic-model.cjs +396 -0
- package/src/claude/scripts/validate-spec-output.cjs +2782 -142
- package/src/claude/scripts/workflow-policy.cjs +501 -183
- package/src/claude/settings/settings.json +4 -0
- package/src/claude/skills/code-review/SKILL.md +12 -3
- package/src/claude/skills/code-review/references/spec-compliance-review.md +10 -2
- package/src/claude/skills/code-review/references/verification-gate.md +13 -0
- package/src/claude/skills/develop/SKILL.md +47 -60
- package/src/claude/skills/develop/references/parallel-waves.md +9 -6
- package/src/claude/skills/develop/references/quality-gate.md +19 -12
- package/src/claude/skills/specs/SKILL.md +183 -177
- package/src/claude/skills/specs/references/ask-user-question-gates.md +27 -18
- package/src/claude/skills/specs/references/cross-spec-dependency.md +8 -4
- package/src/claude/skills/specs/references/research-strategy.md +4 -1
- package/src/claude/skills/specs/references/review.md +200 -260
- package/src/claude/skills/specs/references/scope-inquiry.md +18 -5
- package/src/claude/skills/specs/rules/design-discovery-full.md +33 -81
- package/src/claude/skills/specs/rules/design-discovery-light.md +21 -42
- package/src/claude/skills/specs/rules/design-principles.md +136 -180
- package/src/claude/skills/specs/rules/design-review.md +42 -97
- package/src/claude/skills/specs/rules/ears-format.md +42 -50
- package/src/claude/skills/specs/rules/phase-decision-matrix.md +42 -41
- package/src/claude/skills/specs/rules/task-scoring-rubric.md +2 -1
- package/src/claude/skills/specs/rules/tasks-generation.md +189 -300
- package/src/claude/skills/specs/templates/design.md +49 -263
- package/src/claude/skills/specs/templates/requirements.md +26 -36
- package/src/claude/skills/specs/templates/research.md +9 -98
- package/src/claude/skills/specs/templates/spec-state.json +23 -85
- package/src/claude/skills/specs/templates/task.md +30 -102
- package/src/claude/skills/sync/SKILL.md +33 -10
- package/src/claude/skills/sync/references/sync-protocols.md +31 -13
- package/src/claude/skills/test/SKILL.md +17 -7
- package/src/codex/AGENTS.md +6 -0
- package/src/codex/hooks/completion-authority-check.cjs +43 -240
- package/src/codex/hooks/completion-authority-state.cjs +10 -8
- package/src/codex/hooks/completion-authority.cjs +13 -7
- package/src/codex/hooks/lib/hook-context.cjs +16 -2
- package/src/codex/hooks/lib/privacy-command-analysis.cjs +3 -0
- package/src/codex/hooks/lib/privacy-state.cjs +38 -16
- package/src/codex/hooks/lib/runtime-path-safety.cjs +3 -0
- package/src/codex/hooks/lib/spec-receipt.cjs +72 -134
- package/src/codex/hooks/lib/spec-utils.cjs +56 -5
- package/src/codex/hooks/privacy-approval.cjs +1 -1
- package/src/codex/hooks/privacy-block.cjs +67 -11
- package/src/codex/hooks/semantic-review-authority.cjs +130 -0
- package/src/codex/hooks/spec-gate.cjs +49 -34
- package/src/codex/hooks/spec-state.cjs +10 -3
- package/src/codex/hooks/state.cjs +2 -0
- package/src/codex/hooks.json +6 -1
- package/src/codex/rules/state-sync.md +47 -27
- package/src/common/AGENTS.md +1 -1
|
@@ -23,6 +23,7 @@ const { upsertManagedCoreBlock } = require('../lib/instruction-blocks');
|
|
|
23
23
|
const { upsertManagedCodexBlock, normalizeCodexBody } = require('../lib/codex-install');
|
|
24
24
|
const { copyRecursive } = require('../lib/copy-utils');
|
|
25
25
|
const { createTracker, sha256 } = require('../lib/manifest');
|
|
26
|
+
const { copyManagedTree } = require('../lib/managed-writer');
|
|
26
27
|
|
|
27
28
|
const START = '<!-- CAFEKIT CLAUDE START -->';
|
|
28
29
|
const END = '<!-- CAFEKIT CLAUDE END -->';
|
|
@@ -672,6 +673,44 @@ test('managed targets and snapshots reject project symlink traversal', () => {
|
|
|
672
673
|
});
|
|
673
674
|
});
|
|
674
675
|
|
|
676
|
+
test('managed tree copy rejects source and destination symlink traversal before writes', () => {
|
|
677
|
+
inTempProject((root) => {
|
|
678
|
+
const source = path.join(root, 'source');
|
|
679
|
+
const outsideSource = path.join(root, 'outside-source.txt');
|
|
680
|
+
const destination = path.join('.codex', 'payload');
|
|
681
|
+
fs.mkdirSync(source);
|
|
682
|
+
fs.writeFileSync(outsideSource, 'outside');
|
|
683
|
+
fs.symlinkSync(outsideSource, path.join(source, 'linked.txt'));
|
|
684
|
+
const ctx = {
|
|
685
|
+
dryRun: false,
|
|
686
|
+
options: { forceOverwrite: false },
|
|
687
|
+
ownership: {},
|
|
688
|
+
results: { preservedFiles: [] },
|
|
689
|
+
};
|
|
690
|
+
const copy = () => copyManagedTree({
|
|
691
|
+
src: source,
|
|
692
|
+
dest: destination,
|
|
693
|
+
platformFolder: '.codex',
|
|
694
|
+
ctx,
|
|
695
|
+
tracker: null,
|
|
696
|
+
});
|
|
697
|
+
assert.throws(copy, /source tree cannot contain symlinks/i);
|
|
698
|
+
assert.equal(fs.existsSync(destination), false);
|
|
699
|
+
|
|
700
|
+
fs.rmSync(path.join(source, 'linked.txt'));
|
|
701
|
+
fs.writeFileSync(path.join(source, 'safe.txt'), 'safe');
|
|
702
|
+
const outsideDestination = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-managed-outside-'));
|
|
703
|
+
try {
|
|
704
|
+
fs.symlinkSync(outsideDestination, path.join(root, '.codex'));
|
|
705
|
+
assert.throws(copy, /Refusing to follow symlinked managed path/);
|
|
706
|
+
assert.deepEqual(fs.readdirSync(outsideDestination), []);
|
|
707
|
+
} finally {
|
|
708
|
+
fs.rmSync(path.join(root, '.codex'), { force: true });
|
|
709
|
+
fs.rmSync(outsideDestination, { recursive: true, force: true });
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
|
|
675
714
|
test('managed Claude Addressing survives force refresh when template drops it', () => {
|
|
676
715
|
inTempProject(() => {
|
|
677
716
|
const before = '# User preface\n\n';
|
|
@@ -47,6 +47,7 @@ function ensureSharedValidator(tmp) {
|
|
|
47
47
|
const sources = [
|
|
48
48
|
['workflow-policy.cjs', path.join(PACKAGE_ROOT, 'src/claude/scripts/workflow-policy.cjs')],
|
|
49
49
|
['spec-resolver.cjs', path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-resolver.cjs')],
|
|
50
|
+
['provenance.cjs', path.join(PACKAGE_ROOT, 'src/claude/scripts/provenance.cjs')],
|
|
50
51
|
];
|
|
51
52
|
const dests = [
|
|
52
53
|
path.join(tmp, '.opencode', 'scripts'),
|
|
@@ -66,7 +67,7 @@ test('OpenCode SpecGate lists multiple active candidates and does not pick first
|
|
|
66
67
|
fs.mkdirSync(specsDir, { recursive: true });
|
|
67
68
|
for (const name of ['alpha', 'beta']) {
|
|
68
69
|
fs.mkdirSync(path.join(specsDir, name), { recursive: true });
|
|
69
|
-
fs.writeFileSync(path.join(specsDir, name, 'spec.json'), JSON.stringify({ status: 'in_progress', current_phase: 'design', task_registry: {} }));
|
|
70
|
+
fs.writeFileSync(path.join(specsDir, name, 'spec.json'), JSON.stringify({ feature_name: name, status: 'in_progress', current_phase: 'design', task_registry: {} }));
|
|
70
71
|
}
|
|
71
72
|
const out = path.join(tmp, 'plugins', 'spec-gate.mjs');
|
|
72
73
|
compileToMjs(GATE_SRC, out);
|
|
@@ -109,6 +110,7 @@ test('OpenCode installed gate reports missing and malformed shared policy as con
|
|
|
109
110
|
}
|
|
110
111
|
fs.mkdirSync(path.join(tmp, 'specs', 'demo', 'tasks'), { recursive: true });
|
|
111
112
|
fs.writeFileSync(path.join(tmp, 'specs', 'demo', 'spec.json'), JSON.stringify({
|
|
113
|
+
feature_name: 'demo',
|
|
112
114
|
status: 'in_progress',
|
|
113
115
|
task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } },
|
|
114
116
|
}));
|
|
@@ -145,6 +147,7 @@ test('OpenCode hard-blocks completion tools with a controlled result and allows
|
|
|
145
147
|
const taskDir = path.join(tmp, 'specs', 'demo', 'tasks');
|
|
146
148
|
fs.mkdirSync(taskDir, { recursive: true });
|
|
147
149
|
fs.writeFileSync(path.join(tmp, 'specs', 'demo', 'spec.json'), JSON.stringify({
|
|
150
|
+
feature_name: 'demo',
|
|
148
151
|
status: 'in_progress',
|
|
149
152
|
task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } },
|
|
150
153
|
}));
|
|
@@ -183,7 +186,7 @@ test('OpenCode explicit feature/path target selects the requested spec and never
|
|
|
183
186
|
try {
|
|
184
187
|
for (const name of ['alpha', 'beta']) {
|
|
185
188
|
fs.mkdirSync(path.join(tmp, 'specs', name), { recursive: true });
|
|
186
|
-
fs.writeFileSync(path.join(tmp, 'specs', name, 'spec.json'), JSON.stringify({ status: 'in_progress', task_registry: {} }));
|
|
189
|
+
fs.writeFileSync(path.join(tmp, 'specs', name, 'spec.json'), JSON.stringify({ feature_name: name, status: 'in_progress', task_registry: {} }));
|
|
187
190
|
}
|
|
188
191
|
const out = path.join(tmp, 'plugins', 'spec-gate.mjs');
|
|
189
192
|
compileToMjs(GATE_SRC, out);
|
|
@@ -226,6 +229,7 @@ test('OpenCode adapter enforces declared task artifact SHA-256 with missing, inv
|
|
|
226
229
|
const specsDir = path.join(tmp, 'specs');
|
|
227
230
|
fs.mkdirSync(path.join(specsDir, 'artifact-demo', 'tasks'), { recursive: true });
|
|
228
231
|
fs.writeFileSync(path.join(specsDir, 'artifact-demo', 'spec.json'), JSON.stringify({
|
|
232
|
+
feature_name: 'artifact-demo',
|
|
229
233
|
status: 'in_progress',
|
|
230
234
|
task_registry: {
|
|
231
235
|
'tasks/task.md': {
|
|
@@ -276,8 +280,9 @@ test('OpenCode gate and state use the same configured external specs authority',
|
|
|
276
280
|
const externalSpecs = path.join(tmp, 'external-specs');
|
|
277
281
|
fs.mkdirSync(path.join(localSpecs, 'local'), { recursive: true });
|
|
278
282
|
fs.mkdirSync(path.join(externalSpecs, 'remote', 'tasks'), { recursive: true });
|
|
279
|
-
fs.writeFileSync(path.join(localSpecs, 'local', 'spec.json'), JSON.stringify({ status: 'in_progress', current_phase: 'local' }));
|
|
283
|
+
fs.writeFileSync(path.join(localSpecs, 'local', 'spec.json'), JSON.stringify({ feature_name: 'local', status: 'in_progress', current_phase: 'local' }));
|
|
280
284
|
fs.writeFileSync(path.join(externalSpecs, 'remote', 'spec.json'), JSON.stringify({
|
|
285
|
+
feature_name: 'remote',
|
|
281
286
|
status: 'in_progress',
|
|
282
287
|
current_phase: 'remote',
|
|
283
288
|
task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } },
|
|
@@ -325,7 +330,7 @@ test('OpenCode SpecState injects multiple-active warning with candidates', async
|
|
|
325
330
|
fs.mkdirSync(specsDir, { recursive: true });
|
|
326
331
|
for (const name of ['alpha', 'beta']) {
|
|
327
332
|
fs.mkdirSync(path.join(specsDir, name), { recursive: true });
|
|
328
|
-
fs.writeFileSync(path.join(specsDir, name, 'spec.json'), JSON.stringify({ status: 'in_progress', current_phase: 'design', task_registry: {} }));
|
|
333
|
+
fs.writeFileSync(path.join(specsDir, name, 'spec.json'), JSON.stringify({ feature_name: name, status: 'in_progress', current_phase: 'design', task_registry: {} }));
|
|
329
334
|
}
|
|
330
335
|
const out = path.join(tmp, 'plugins', 'spec-state.mjs');
|
|
331
336
|
compileToMjs(STATE_SRC, out);
|
|
@@ -350,6 +355,7 @@ test('OpenCode SpecGate reports Exit:1 receipt as a completion failure', async (
|
|
|
350
355
|
fs.mkdirSync(path.join(specsDir, 'demo', 'tasks'), { recursive: true });
|
|
351
356
|
// Single active spec with done task but Exit 1 must remain incomplete.
|
|
352
357
|
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({
|
|
358
|
+
feature_name: 'demo',
|
|
353
359
|
status: 'in_progress',
|
|
354
360
|
current_phase: 'implement',
|
|
355
361
|
task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } },
|
|
@@ -398,7 +404,7 @@ test('OpenCode SpecState single active injects tollgate block (not multiple)', a
|
|
|
398
404
|
try {
|
|
399
405
|
const specsDir = path.join(tmp, 'specs');
|
|
400
406
|
fs.mkdirSync(path.join(specsDir, 'solo'), { recursive: true });
|
|
401
|
-
fs.writeFileSync(path.join(specsDir, 'solo', 'spec.json'), JSON.stringify({ status: 'in_progress', current_phase: 'design', task_registry: { 'tasks/a.md': { status: 'pending' } } }));
|
|
407
|
+
fs.writeFileSync(path.join(specsDir, 'solo', 'spec.json'), JSON.stringify({ feature_name: 'solo', status: 'in_progress', current_phase: 'design', task_registry: { 'tasks/a.md': { status: 'pending' } } }));
|
|
402
408
|
const out = path.join(tmp, 'plugins', 'spec-state.mjs');
|
|
403
409
|
compileToMjs(STATE_SRC, out);
|
|
404
410
|
const { SpecState } = await import(pathToFileURL(out).href);
|
|
@@ -422,6 +428,7 @@ test('OpenCode tollgate cache binds canonical root, full session identity, and s
|
|
|
422
428
|
for (const project of [projectA, projectB]) {
|
|
423
429
|
fs.mkdirSync(path.join(project, 'specs', 'same'), { recursive: true });
|
|
424
430
|
fs.writeFileSync(path.join(project, 'specs', 'same', 'spec.json'), JSON.stringify({
|
|
431
|
+
feature_name: 'same',
|
|
425
432
|
status: 'in_progress',
|
|
426
433
|
current_phase: 'design',
|
|
427
434
|
task_registry: { 'tasks/task.md': { status: 'pending' } },
|
|
@@ -443,6 +450,7 @@ test('OpenCode tollgate cache binds canonical root, full session identity, and s
|
|
|
443
450
|
fs.rmSync(path.join(projectA, 'specs', 'same'), { recursive: true, force: true });
|
|
444
451
|
fs.mkdirSync(path.join(projectA, 'specs', 'other'), { recursive: true });
|
|
445
452
|
fs.writeFileSync(path.join(projectA, 'specs', 'other', 'spec.json'), JSON.stringify({
|
|
453
|
+
feature_name: 'other',
|
|
446
454
|
status: 'in_progress',
|
|
447
455
|
current_phase: 'design',
|
|
448
456
|
task_registry: { 'tasks/task.md': { status: 'pending' } },
|
|
@@ -479,7 +487,7 @@ test('OpenCode P0 regression: placeholder, explicit failure, artifact, symlink a
|
|
|
479
487
|
try {
|
|
480
488
|
const specsDir = path.join(tmp, 'specs');
|
|
481
489
|
fs.mkdirSync(path.join(specsDir, 'demo', 'tasks'), { recursive: true });
|
|
482
|
-
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
490
|
+
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ feature_name: 'demo', status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
483
491
|
fs.writeFileSync(path.join(specsDir, 'demo', 'tasks', 'task.md'), ['# Task','','**Status:** done','','## Evidence','',body].join('\n'));
|
|
484
492
|
const out = path.join(tmp, 'plugins', 'spec-gate.mjs');
|
|
485
493
|
compileToMjs(GATE_SRC, out);
|
|
@@ -503,7 +511,7 @@ test('OpenCode P0 regression: placeholder, explicit failure, artifact, symlink a
|
|
|
503
511
|
try {
|
|
504
512
|
const specsDir = path.join(tmp, 'specs');
|
|
505
513
|
fs.mkdirSync(path.join(specsDir, 'demo', 'tasks'), { recursive: true });
|
|
506
|
-
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
514
|
+
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ feature_name: 'demo', status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
507
515
|
fs.writeFileSync(path.join(specsDir, 'demo', 'tasks', 'task.md'), ['# Task','','**Status:** done','','## Evidence','','Verification: PASS','Command: npm run todo:test','Exit: 0',`Base: ${BASE_SHA}`,`Head: ${HEAD_SHA}`].join('\n'));
|
|
508
516
|
const out = path.join(tmp, 'plugins', 'spec-gate.mjs');
|
|
509
517
|
compileToMjs(GATE_SRC, out);
|
|
@@ -525,7 +533,7 @@ test('OpenCode P0 regression: placeholder, explicit failure, artifact, symlink a
|
|
|
525
533
|
try {
|
|
526
534
|
const specsDir = path.join(tmp, 'specs');
|
|
527
535
|
fs.mkdirSync(path.join(specsDir, 'feat', 'tasks'), { recursive: true });
|
|
528
|
-
fs.writeFileSync(path.join(specsDir, 'feat', 'spec.json'), JSON.stringify({ status: 'in_progress', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
536
|
+
fs.writeFileSync(path.join(specsDir, 'feat', 'spec.json'), JSON.stringify({ feature_name: 'feat', status: 'in_progress', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
529
537
|
const outside = path.join(tmp, 'outside-task.md');
|
|
530
538
|
fs.writeFileSync(outside, `# Task\n\n**Status:** done\n\n## Evidence\n\nVerification: PASS\nCommand: pnpm test\nExit: 0\nBase: ${BASE_SHA}\nHead: ${HEAD_SHA}\n`);
|
|
531
539
|
const link = path.join(specsDir, 'feat', 'tasks', 'task.md');
|
|
@@ -552,7 +560,7 @@ test('OpenCode P0 regression: placeholder, explicit failure, artifact, symlink a
|
|
|
552
560
|
const specsDir = path.join(tmp, 'specs');
|
|
553
561
|
fs.mkdirSync(path.join(specsDir, 'good'), { recursive: true });
|
|
554
562
|
fs.mkdirSync(path.join(specsDir, 'bad'), { recursive: true });
|
|
555
|
-
fs.writeFileSync(path.join(specsDir, 'good', 'spec.json'), JSON.stringify({ status: 'in_progress' }));
|
|
563
|
+
fs.writeFileSync(path.join(specsDir, 'good', 'spec.json'), JSON.stringify({ feature_name: 'good', status: 'in_progress' }));
|
|
556
564
|
fs.writeFileSync(path.join(specsDir, 'bad', 'spec.json'), '{ malformed');
|
|
557
565
|
const out = path.join(tmp, 'plugins', 'spec-state.mjs');
|
|
558
566
|
compileToMjs(STATE_SRC, out);
|
|
@@ -572,10 +580,10 @@ test('OpenCode P0 regression: placeholder, explicit failure, artifact, symlink a
|
|
|
572
580
|
try {
|
|
573
581
|
const specsDir = path.join(tmp, 'specs');
|
|
574
582
|
fs.mkdirSync(path.join(specsDir, 'valid'), { recursive: true });
|
|
575
|
-
fs.writeFileSync(path.join(specsDir, 'valid', 'spec.json'), JSON.stringify({ status: 'in_progress', task_registry: {} }));
|
|
583
|
+
fs.writeFileSync(path.join(specsDir, 'valid', 'spec.json'), JSON.stringify({ feature_name: 'valid', status: 'in_progress', task_registry: {} }));
|
|
576
584
|
const outside = path.join(tmp, 'outside-spec');
|
|
577
585
|
fs.mkdirSync(outside, { recursive: true });
|
|
578
|
-
fs.writeFileSync(path.join(outside, 'spec.json'), JSON.stringify({ status: 'in_progress' }));
|
|
586
|
+
fs.writeFileSync(path.join(outside, 'spec.json'), JSON.stringify({ feature_name: 'linked', status: 'in_progress' }));
|
|
579
587
|
const link = path.join(specsDir, 'linked');
|
|
580
588
|
fs.symlinkSync(outside, link);
|
|
581
589
|
const out = path.join(tmp, 'plugins', 'spec-state.mjs');
|
|
@@ -596,7 +604,7 @@ test('OpenCode P0 regression: placeholder, explicit failure, artifact, symlink a
|
|
|
596
604
|
try {
|
|
597
605
|
const specsDir = path.join(tmp, 'specs');
|
|
598
606
|
fs.mkdirSync(path.join(specsDir, 'demo', 'tasks'), { recursive: true });
|
|
599
|
-
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
607
|
+
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ feature_name: 'demo', status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
600
608
|
// Notes with failure handling should pass (not structured)
|
|
601
609
|
fs.writeFileSync(path.join(specsDir, 'demo', 'tasks', 'task.md'), ['# Task','','**Status:** done','','## Evidence','','Verification: PASS','Command: pnpm test','Exit: 0',`Base: ${BASE_SHA}`,`Head: ${HEAD_SHA}`,'Notes: verifies failure handling'].join('\n'));
|
|
602
610
|
const out = path.join(tmp, 'plugins', 'spec-gate.mjs');
|
|
@@ -639,7 +647,7 @@ test('OpenCode P0 regression: placeholder, explicit failure, artifact, symlink a
|
|
|
639
647
|
try {
|
|
640
648
|
const specsDir = path.join(tmp, 'specs');
|
|
641
649
|
fs.mkdirSync(path.join(specsDir, 'demo', 'tasks'), { recursive: true });
|
|
642
|
-
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
650
|
+
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ feature_name: 'demo', status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
643
651
|
fs.writeFileSync(path.join(specsDir, 'demo', 'tasks', 'task.md'), ['# Task','','**Status:** done','','## Evidence','','Verification: PASS','Command: pnpm test','Exit: 0',`Base: ${BASE_SHA}`,`Head: ${HEAD_SHA}`].join('\n'));
|
|
644
652
|
const out = path.join(tmp, 'plugins', 'spec-gate.mjs');
|
|
645
653
|
compileToMjs(GATE_SRC, out);
|
|
@@ -661,7 +669,7 @@ test('OpenCode P0 regression: placeholder, explicit failure, artifact, symlink a
|
|
|
661
669
|
try {
|
|
662
670
|
const specsDir = path.join(tmp, 'specs');
|
|
663
671
|
fs.mkdirSync(path.join(specsDir, 'feat', 'tasks'), { recursive: true });
|
|
664
|
-
fs.writeFileSync(path.join(specsDir, 'feat', 'spec.json'), JSON.stringify({ status: 'in_progress', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
672
|
+
fs.writeFileSync(path.join(specsDir, 'feat', 'spec.json'), JSON.stringify({ feature_name: 'feat', status: 'in_progress', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
665
673
|
fs.writeFileSync(path.join(specsDir, 'feat', 'tasks', 'task.md'), ['# Task','','**Status:** done','','## Evidence','','Verification: PASS','Command: pnpm test','Exit: 0',`Base: ${BASE_SHA}`,`Head: ${HEAD_SHA}`].join('\n'));
|
|
666
674
|
const out = path.join(tmp, 'plugins', 'spec-gate.mjs');
|
|
667
675
|
compileToMjs(GATE_SRC, out);
|
|
@@ -714,7 +722,7 @@ test('OpenCode P0 phantom vectors via installed shared validator (r3)', async ()
|
|
|
714
722
|
try {
|
|
715
723
|
const specsDir = path.join(tmp, 'specs');
|
|
716
724
|
fs.mkdirSync(path.join(specsDir, 'demo', 'tasks'), { recursive: true });
|
|
717
|
-
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
725
|
+
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ feature_name: 'demo', status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
718
726
|
fs.writeFileSync(path.join(specsDir, 'demo', 'tasks', 'task.md'), ['# Task','','**Status:** done','','## Evidence','','Verification: PASS','Command: pnpm test','Exit: 0',`Base: ${BASE_SHA}`,`Head: ${HEAD_SHA}`, v].join('\n'));
|
|
719
727
|
const out = path.join(tmp, 'plugins', 'spec-gate.mjs');
|
|
720
728
|
compileToMjs(GATE_SRC, out);
|
|
@@ -758,7 +766,7 @@ test('OpenCode P0 phantom vectors via installed shared validator (r3)', async ()
|
|
|
758
766
|
try {
|
|
759
767
|
const specsDir = path.join(tmp, 'specs');
|
|
760
768
|
fs.mkdirSync(path.join(specsDir, 'demo', 'tasks'), { recursive: true });
|
|
761
|
-
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
769
|
+
fs.writeFileSync(path.join(specsDir, 'demo', 'spec.json'), JSON.stringify({ feature_name: 'demo', status: 'in_progress', current_phase: 'implement', task_registry: { 'tasks/task.md': { status: 'done', completed_at: '2026-08-11T00:00:00.000Z' } } }));
|
|
762
770
|
fs.writeFileSync(path.join(specsDir, 'demo', 'tasks', 'task.md'), [
|
|
763
771
|
'# Task',
|
|
764
772
|
'',
|