@haposoft/cafekit 0.16.0-rc.2 → 0.16.0-rc.3

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.
Files changed (49) hide show
  1. package/bin/__tests__/codex-hooks.test.js +355 -12
  2. package/bin/__tests__/codex-native.test.js +1 -1
  3. package/bin/__tests__/develop-contract.test.js +1374 -78
  4. package/bin/__tests__/installer-safety.test.js +43 -29
  5. package/bin/__tests__/opencode-spec-gate.test.js +791 -0
  6. package/bin/__tests__/package-inventory.test.js +157 -0
  7. package/bin/__tests__/validator-grounding.test.js +707 -2
  8. package/package.json +1 -1
  9. package/src/claude/agents/spec-maker.md +107 -200
  10. package/src/claude/hooks/__tests__/completion-authority.test.js +720 -0
  11. package/src/claude/hooks/__tests__/spec-gate.test.js +511 -38
  12. package/src/claude/hooks/completion-authority-check.cjs +307 -0
  13. package/src/claude/hooks/completion-authority-state.cjs +525 -0
  14. package/src/claude/hooks/completion-authority.cjs +148 -0
  15. package/src/claude/hooks/session.cjs +33 -19
  16. package/src/claude/hooks/spec-gate.cjs +173 -61
  17. package/src/claude/hooks/spec-state.cjs +104 -45
  18. package/src/claude/migration-manifest.json +6 -1
  19. package/src/claude/scripts/provenance.cjs +315 -0
  20. package/src/claude/scripts/spec-resolver.cjs +450 -0
  21. package/src/claude/scripts/spec-scaffold.cjs +716 -41
  22. package/src/claude/scripts/validate-spec-output.cjs +355 -53
  23. package/src/claude/scripts/workflow-policy.cjs +1529 -162
  24. package/src/claude/settings/settings.json +8 -0
  25. package/src/claude/skills/code-review/SKILL.md +85 -66
  26. package/src/claude/skills/develop/SKILL.md +153 -285
  27. package/src/claude/skills/develop/references/quality-gate.md +116 -148
  28. package/src/claude/skills/develop/references/subagent-patterns.md +42 -37
  29. package/src/claude/skills/specs/SKILL.md +170 -381
  30. package/src/claude/skills/specs/rules/task-scoring-rubric.md +45 -46
  31. package/src/claude/skills/specs/rules/tasks-generation.md +49 -20
  32. package/src/claude/skills/specs/templates/spec-state.json +23 -2
  33. package/src/claude/skills/specs/templates/task.md +20 -0
  34. package/src/claude/skills/sync/SKILL.md +15 -8
  35. package/src/claude/skills/sync/references/sync-protocols.md +4 -4
  36. package/src/claude/skills/test/SKILL.md +141 -167
  37. package/src/codex/hooks/completion-authority-check.cjs +260 -0
  38. package/src/codex/hooks/completion-authority-state.cjs +525 -0
  39. package/src/codex/hooks/completion-authority.cjs +100 -0
  40. package/src/codex/hooks/lib/hook-context.cjs +42 -2
  41. package/src/codex/hooks/lib/spec-receipt.cjs +76 -43
  42. package/src/codex/hooks/lib/spec-utils.cjs +53 -27
  43. package/src/codex/hooks/session.cjs +2 -0
  44. package/src/codex/hooks/spec-gate.cjs +132 -20
  45. package/src/codex/hooks/spec-state.cjs +56 -10
  46. package/src/codex/hooks.json +10 -0
  47. package/src/opencode/AGENTS.md +4 -2
  48. package/src/opencode/plugins/spec-gate.ts +296 -122
  49. package/src/opencode/plugins/spec-state.ts +183 -116
@@ -13,6 +13,7 @@ const REQUIRED_PAYLOAD = [
13
13
  'src/claude/migration-manifest.json',
14
14
  'src/claude/scripts/scan-staged-secrets.cjs',
15
15
  'src/claude/scripts/workflow-policy.cjs',
16
+ 'src/claude/scripts/provenance.cjs',
16
17
  ];
17
18
  const FORBIDDEN_PAYLOAD = [
18
19
  /(^|\/)\.logs(\/|$)/,
@@ -84,6 +85,136 @@ function runInstaller(installer, root, platforms, lang) {
84
85
  assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
85
86
  }
86
87
 
88
+ function createProvenanceFixture(root) {
89
+ const feature = 'installer-provenance';
90
+ const specFile = path.join('specs', feature, 'spec.json');
91
+ fs.mkdirSync(path.join(root, path.dirname(specFile)), { recursive: true });
92
+ fs.writeFileSync(
93
+ path.join(root, specFile),
94
+ JSON.stringify({ feature_name: feature, status: 'in_progress' }) + '\n'
95
+ );
96
+ fs.writeFileSync(path.join(root, 'tracked-fixture.txt'), 'tracked fixture\n');
97
+
98
+ for (const args of [
99
+ ['init', '-q'],
100
+ ['add', '--', 'tracked-fixture.txt', specFile],
101
+ ['-c', 'user.name=CafeKit Fixture', '-c', 'user.email=cafekit-fixture@example.invalid', 'commit', '--no-gpg-sign', '-qm', 'provenance fixture'],
102
+ ]) {
103
+ const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
104
+ assert.equal(result.status, 0, `${args.join(' ')}\n${result.stdout}\n${result.stderr}`);
105
+ }
106
+
107
+ return {
108
+ specsRoot: 'specs',
109
+ specFile,
110
+ feature,
111
+ session: `package-${path.basename(root)}`,
112
+ };
113
+ }
114
+
115
+ function provenanceCliArgs(helper, root, fixture) {
116
+ return [
117
+ helper,
118
+ '--json',
119
+ '--project-root', root,
120
+ '--specs-root', fixture.specsRoot,
121
+ '--spec-file', fixture.specFile,
122
+ '--feature-name', fixture.feature,
123
+ '--session', fixture.session,
124
+ ];
125
+ }
126
+
127
+ function assertInstalledProvenance(root, platform, fixture) {
128
+ const scripts = path.join(root, RUNTIMES[platform].root, 'scripts');
129
+ const helper = path.join(scripts, 'provenance.cjs');
130
+ const policy = path.join(scripts, 'workflow-policy.cjs');
131
+ const resolver = path.join(scripts, 'spec-resolver.cjs');
132
+ const gate = path.join(
133
+ root,
134
+ platform === 'claude' ? '.claude/hooks/spec-gate.cjs' : '.codex/hooks/spec-gate.cjs'
135
+ );
136
+ const authorityDir = path.join(root, platform === 'claude' ? '.claude/hooks' : '.codex/hooks');
137
+ const authority = path.join(authorityDir, 'completion-authority.cjs');
138
+ const authorityCheck = path.join(authorityDir, 'completion-authority-check.cjs');
139
+ const authorityState = path.join(authorityDir, 'completion-authority-state.cjs');
140
+ for (const file of [helper, policy, resolver]) {
141
+ assert.equal(fs.existsSync(file), true, `${platform} installed file missing: ${file}`);
142
+ }
143
+ assert.equal(fs.existsSync(gate), true, `${platform} installed gate missing: ${gate}`);
144
+
145
+ for (const file of [authority, authorityCheck, authorityState]) {
146
+ assert.equal(fs.existsSync(file), true, `${platform} installed completion authority missing: ${file}`);
147
+ }
148
+ const authorityCheckBytes = fs.readFileSync(authorityCheck);
149
+ fs.writeFileSync(authorityCheck, 'module.exports = {};\n');
150
+ const malformedAuthority = spawnSync(process.execPath, [authority, '--stop'], {
151
+ cwd: root,
152
+ env: { ...process.env, PROJECT_ROOT: root },
153
+ input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
154
+ encoding: 'utf8',
155
+ });
156
+ assert.equal(malformedAuthority.status, 0, `${platform} malformed authority must fail closed`);
157
+ assert.equal(JSON.parse(malformedAuthority.stdout).decision, 'block');
158
+ fs.writeFileSync(authorityCheck, authorityCheckBytes);
159
+ fs.rmSync(authorityCheck);
160
+ const missingAuthority = spawnSync(process.execPath, [authority, '--stop'], {
161
+ cwd: root,
162
+ env: { ...process.env, PROJECT_ROOT: root },
163
+ input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
164
+ encoding: 'utf8',
165
+ });
166
+ assert.equal(missingAuthority.status, 0, `${platform} missing authority dependency must fail closed`);
167
+ assert.equal(JSON.parse(missingAuthority.stdout).decision, 'block');
168
+ fs.writeFileSync(authorityCheck, authorityCheckBytes);
169
+
170
+ const helperRun = spawnSync(process.execPath, provenanceCliArgs(helper, root, fixture), {
171
+ cwd: root,
172
+ encoding: 'utf8',
173
+ });
174
+ assert.equal(helperRun.status, 0, `${helperRun.stdout}\n${helperRun.stderr}`);
175
+ const output = JSON.parse(helperRun.stdout);
176
+ assert.equal(output.ok, true);
177
+ assert.match(output.Base, /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/);
178
+ assert.match(output.Head, /^[a-f0-9]{64}$/);
179
+ assert.match(output.context_id, /^[a-f0-9]{64}$/);
180
+ assert.equal(output.context.runtime_session, fixture.session);
181
+
182
+ const policyLoad = spawnSync(process.execPath, [policy, '--json'], {
183
+ cwd: root,
184
+ encoding: 'utf8',
185
+ });
186
+ assert.equal(policyLoad.status, 0, `${policyLoad.stdout}\n${policyLoad.stderr}`);
187
+ assert.equal(JSON.parse(policyLoad.stdout).ok, true);
188
+
189
+ const gateInput = JSON.stringify({
190
+ cwd: root,
191
+ session_id: fixture.session,
192
+ featureName: fixture.feature,
193
+ });
194
+ const assertControlledBlock = (label) => {
195
+ const gateRun = spawnSync(process.execPath, [gate], {
196
+ cwd: root,
197
+ input: `${gateInput}\n`,
198
+ encoding: 'utf8',
199
+ });
200
+ assert.equal(gateRun.status, 0, `${label}: ${gateRun.stdout}\n${gateRun.stderr}`);
201
+ const decision = JSON.parse(gateRun.stdout);
202
+ assert.equal(decision.decision, 'block', `${label}: ${gateRun.stdout}`);
203
+ assert.equal(decision.ok, undefined, `${label}: ${gateRun.stdout}`);
204
+ assert.match(decision.reason, /unavailable|shared workflow policy|provenance|helper/i);
205
+ assert.doesNotMatch(gateRun.stdout, /"decision"\s*:\s*"allow"|"ok"\s*:\s*true/);
206
+ };
207
+
208
+ const helperBytes = fs.readFileSync(helper);
209
+ fs.rmSync(helper);
210
+ assertControlledBlock('missing provenance helper');
211
+
212
+ fs.writeFileSync(helper, 'module.exports = {;\n');
213
+ assertControlledBlock('malformed provenance helper');
214
+
215
+ fs.writeFileSync(helper, helperBytes);
216
+ }
217
+
87
218
  function managedBlock(content, start, end) {
88
219
  const startIndex = content.indexOf(start);
89
220
  const endIndex = content.indexOf(end);
@@ -256,3 +387,29 @@ test('packed tarball installer matrix proves locale, transforms, paths, and reru
256
387
  fs.rmSync(root, { recursive: true, force: true });
257
388
  }
258
389
  });
390
+
391
+ test('packed Claude and Codex installs self-contain runtime provenance and fail closed without it', () => {
392
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-provenance-package-'));
393
+ const destination = path.join(root, 'pack');
394
+ fs.mkdirSync(destination, { recursive: true });
395
+ try {
396
+ const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
397
+ const tarball = path.join(destination, packed.filename);
398
+ assertCleanInventory(packedInventory(tarball));
399
+
400
+ for (const platform of ['claude', 'codex']) {
401
+ const project = path.join(root, platform);
402
+ const installer = installPacked(tarball, project);
403
+ runInstaller(installer, project, [platform], null);
404
+
405
+ const packedSource = path.resolve(path.dirname(installer), '..', 'src');
406
+ fs.rmSync(packedSource, { recursive: true, force: true });
407
+ assert.equal(fs.existsSync(packedSource), false, 'installed gate must not need package source fallback');
408
+
409
+ const fixture = createProvenanceFixture(project);
410
+ assertInstalledProvenance(project, platform, fixture);
411
+ }
412
+ } finally {
413
+ fs.rmSync(root, { recursive: true, force: true });
414
+ }
415
+ });