@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.
Files changed (162) hide show
  1. package/README.md +12 -1
  2. package/bin/__tests__/claude-settings.test.js +123 -0
  3. package/bin/__tests__/codex-ask-guard.test.js +123 -0
  4. package/bin/__tests__/codex-ask-projection.test.js +51 -0
  5. package/bin/__tests__/codex-hooks-ownership.test.js +80 -1
  6. package/bin/__tests__/codex-hooks.test.js +938 -237
  7. package/bin/__tests__/codex-native-hook-boundaries.test.js +136 -0
  8. package/bin/__tests__/codex-native.test.js +222 -149
  9. package/bin/__tests__/codex-projection-parity.test.js +314 -0
  10. package/bin/__tests__/codex-quiet-hooks.test.js +170 -0
  11. package/bin/__tests__/codex-research-projection.test.js +70 -0
  12. package/bin/__tests__/codex-specs-projection.test.js +76 -0
  13. package/bin/__tests__/develop-contract.test.js +102 -1362
  14. package/bin/__tests__/docs-sync-on-demand.test.js +25 -20
  15. package/bin/__tests__/inspect-paths.test.js +183 -0
  16. package/bin/__tests__/installer-safety.test.js +335 -0
  17. package/bin/__tests__/legacy-authority-decoupled.test.js +14 -19
  18. package/bin/__tests__/legacy-hooks-retired.test.js +18 -8
  19. package/bin/__tests__/omp-bridge.test.js +66 -12
  20. package/bin/__tests__/omp-hooks.test.js +11 -5
  21. package/bin/__tests__/orca-session.test.js +7 -0
  22. package/bin/__tests__/package-inventory.test.js +1 -395
  23. package/bin/__tests__/privacy-paths.test.js +317 -0
  24. package/bin/__tests__/spec-narrowing.test.js +30 -134
  25. package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -372
  26. package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
  27. package/bin/__tests__/usage-hook-retired.test.js +5 -3
  28. package/bin/__tests__/validator-grounding.test.js +17 -2929
  29. package/bin/install.js +25 -0
  30. package/bin/lib/codex-install.js +134 -12
  31. package/bin/lib/context.js +1 -1
  32. package/bin/lib/managed-writer.js +2 -3
  33. package/bin/phases/claude-runtime.js +91 -25
  34. package/bin/phases/claude-settings.js +22 -1
  35. package/bin/phases/codex-hooks.js +96 -33
  36. package/bin/phases/codex-runtime.js +45 -49
  37. package/bin/phases/copy-payload.js +11 -28
  38. package/bin/phases/omp-runtime.js +6 -3
  39. package/package.json +2 -3
  40. package/src/claude/CLAUDE.md +4 -0
  41. package/src/claude/agents/code-auditor.md +9 -6
  42. package/src/claude/agents/debugger.md +3 -9
  43. package/src/claude/agents/docs-keeper.md +4 -7
  44. package/src/claude/agents/git-ops.md +2 -4
  45. package/src/claude/agents/implementer.md +6 -7
  46. package/src/claude/agents/inspector.md +2 -0
  47. package/src/claude/agents/project-manager.md +2 -2
  48. package/src/claude/agents/spec-maker.md +4 -4
  49. package/src/claude/agents/test-runner.md +8 -11
  50. package/src/claude/agents/ui-ux-designer.md +0 -1
  51. package/src/claude/gitignore +3 -0
  52. package/src/claude/hooks/__tests__/installed-root.test.js +111 -0
  53. package/src/claude/hooks/__tests__/precompact.test.js +206 -10
  54. package/src/claude/hooks/__tests__/privacy-block.test.js +27 -0
  55. package/src/claude/hooks/__tests__/session-writeenv.test.js +25 -0
  56. package/src/claude/hooks/__tests__/spec-gate.test.js +311 -340
  57. package/src/claude/hooks/__tests__/spec-state-touched.test.js +230 -8
  58. package/src/claude/hooks/__tests__/state.test.js +207 -7
  59. package/src/claude/hooks/__tests__/update-check.test.js +88 -0
  60. package/src/claude/hooks/agent.cjs +7 -6
  61. package/src/claude/hooks/inspect-block.cjs +15 -63
  62. package/src/claude/hooks/lib/inspect-paths.cjs +304 -0
  63. package/src/claude/hooks/lib/parser.cjs +1 -1
  64. package/src/claude/hooks/lib/privacy-command-analysis.cjs +191 -17
  65. package/src/claude/hooks/lib/privacy-paths.cjs +313 -0
  66. package/src/claude/hooks/lib/project-root.cjs +15 -0
  67. package/src/claude/hooks/lib/rules-reservation.cjs +36 -0
  68. package/src/claude/hooks/lib/spec-touch.cjs +67 -0
  69. package/src/claude/hooks/lib/update-check.cjs +77 -0
  70. package/src/claude/hooks/precompact.cjs +8 -15
  71. package/src/claude/hooks/privacy-block.cjs +23 -205
  72. package/src/claude/hooks/rules.cjs +6 -28
  73. package/src/claude/hooks/session.cjs +34 -117
  74. package/src/claude/hooks/spec-gate.cjs +17 -89
  75. package/src/claude/hooks/spec-state.cjs +89 -96
  76. package/src/claude/hooks/state.cjs +96 -51
  77. package/src/claude/migration-manifest.json +20 -8
  78. package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
  79. package/src/claude/output-styles/coding-level-1-junior.md +3 -3
  80. package/src/claude/output-styles/coding-level-2-mid.md +3 -3
  81. package/src/claude/output-styles/coding-level-3-senior.md +2 -2
  82. package/src/claude/output-styles/coding-level-4-lead.md +5 -5
  83. package/src/claude/output-styles/coding-level-5-god.md +6 -6
  84. package/src/claude/rules/ai-dev-rules.md +1 -2
  85. package/src/claude/rules/hook-protocols.md +1 -1
  86. package/src/claude/rules/workflow.md +1 -1
  87. package/src/claude/runtime.json +0 -3
  88. package/src/claude/scripts/generate-skill-catalog.cjs +2 -2
  89. package/src/claude/scripts/provenance.cjs +14 -0
  90. package/src/claude/scripts/spec-receipt.cjs +0 -114
  91. package/src/claude/scripts/spec-resolver.cjs +41 -457
  92. package/src/claude/scripts/workflow-policy.cjs +3 -1801
  93. package/src/claude/settings/settings.json +1 -1
  94. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  95. package/src/claude/skills/ai-multimodal/scripts/check_setup.py +8 -68
  96. package/src/claude/skills/ai-multimodal/scripts/gemini_batch_process.py +6 -28
  97. package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
  98. package/src/claude/skills/code-review/SKILL.md +25 -108
  99. package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -7
  100. package/src/claude/skills/develop/SKILL.md +22 -70
  101. package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
  102. package/src/claude/skills/git/SKILL.md +1 -1
  103. package/src/claude/skills/git/references/worktree-blueprint.md +1 -1
  104. package/src/claude/skills/research/SKILL.md +24 -55
  105. package/src/claude/skills/sync/SKILL.md +12 -34
  106. package/src/claude/skills/sync/references/rebind-and-audit.md +2 -2
  107. package/src/claude/skills/test/SKILL.md +39 -123
  108. package/src/claude/skills/test/references/execution-strategy.md +11 -22
  109. package/src/claude/skills/ui-ux/SKILL.md +1 -1
  110. package/src/claude/skills/ui-ux-pro-max/SKILL.md +24 -79
  111. package/src/claude/skills/web-testing/SKILL.md +2 -20
  112. package/src/codex/AGENTS.md +8 -0
  113. package/src/codex/hooks/ask-guard.cjs +54 -0
  114. package/src/codex/hooks/inspect-block.cjs +11 -7
  115. package/src/codex/hooks/lib/inspect-paths.cjs +1 -199
  116. package/src/codex/hooks/lib/privacy-paths.cjs +1 -0
  117. package/src/codex/hooks/lib/privacy-state.cjs +98 -16
  118. package/src/codex/hooks/lib/rules-reservation.cjs +38 -0
  119. package/src/codex/hooks/lib/secret-keywords.cjs +1 -0
  120. package/src/codex/hooks/lib/spec-receipt.cjs +2 -30
  121. package/src/codex/hooks/lib/spec-session-touch.cjs +100 -0
  122. package/src/codex/hooks/lib/spec-touch.cjs +3 -0
  123. package/src/codex/hooks/lib/spec-utils.cjs +6 -45
  124. package/src/codex/hooks/lib/update-check.cjs +1 -0
  125. package/src/codex/hooks/precompact.cjs +39 -0
  126. package/src/codex/hooks/privacy-approval.cjs +28 -17
  127. package/src/codex/hooks/privacy-block.cjs +9 -215
  128. package/src/codex/hooks/rules.cjs +4 -30
  129. package/src/codex/hooks/secret-output-guardrail.cjs +1 -1
  130. package/src/codex/hooks/session.cjs +47 -11
  131. package/src/codex/hooks/spec-gate.cjs +32 -74
  132. package/src/codex/hooks/spec-state.cjs +68 -46
  133. package/src/codex/hooks/state.cjs +25 -4
  134. package/src/codex/hooks.json +39 -7
  135. package/src/codex/rules/hook-protocols.md +13 -6
  136. package/src/common/AGENTS.md +4 -10
  137. package/src/omp/extensions/cafekit-bridge.mjs +12 -4
  138. package/src/omp/hooks/privacy-block.cjs +24 -206
  139. package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
  140. package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
  141. package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
  142. package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
  143. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
  144. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
  145. package/src/claude/hooks/lib/context.cjs +0 -527
  146. package/src/claude/hooks/lib/detect.cjs +0 -474
  147. package/src/claude/scripts/change-firewall.cjs +0 -1748
  148. package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
  149. package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
  150. package/src/claude/scripts/spec-final-state.cjs +0 -290
  151. package/src/claude/scripts/spec-ground.cjs +0 -650
  152. package/src/claude/scripts/spec-readiness.cjs +0 -425
  153. package/src/claude/scripts/spec-scaffold.cjs +0 -2317
  154. package/src/claude/scripts/spec-semantic-model.cjs +0 -396
  155. package/src/claude/scripts/validate-spec-output.cjs +0 -3914
  156. package/src/claude/skills/code-review/references/verification-gate.md +0 -41
  157. package/src/claude/skills/specs/templates/design.md +0 -82
  158. package/src/claude/skills/specs/templates/requirements-init.md +0 -8
  159. package/src/claude/skills/specs/templates/requirements.md +0 -42
  160. package/src/claude/skills/specs/templates/spec-state.json +0 -56
  161. package/src/claude/skills/specs/templates/task.md +0 -47
  162. package/src/codex/rules/state-sync.md +0 -60
@@ -48,6 +48,7 @@ function install(root, platforms) {
48
48
  const result = spawnSync(process.execPath, [INSTALLER, '--platform', platforms.join(','), '--yes'],
49
49
  { cwd: root, encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin' } });
50
50
  assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
51
+ return result;
51
52
  }
52
53
 
53
54
  function commands(settings) {
@@ -74,7 +75,7 @@ test('fresh installs ship and register none of the five legacy hooks', () => {
74
75
  });
75
76
  });
76
77
 
77
- test('claude upgrade removes the legacy hooks and their CafeKit entries but keeps foreign ones', () => {
78
+ test('claude upgrade preserves edited legacy hooks while removing their CafeKit entries', () => {
78
79
  inTempProject((root) => {
79
80
  install(root, ['claude']);
80
81
  const settingsPath = path.join(root, '.claude', 'settings.json');
@@ -99,10 +100,11 @@ test('claude upgrade removes the legacy hooks and their CafeKit entries but keep
99
100
  settings.hooks.SubagentStop[0].hooks.push({ type: 'command', command: cmd('semantic-review-authority.cjs') });
100
101
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
101
102
 
102
- install(root, ['claude']);
103
- for (const name of NAMES) assert.equal(fs.existsSync(path.join(root, '.claude', 'hooks', name)), false, name);
103
+ const upgrade = install(root, ['claude']);
104
+ assert.match(upgrade.stdout + upgrade.stderr, /Preserved 5 obsolete runtime/);
105
+ for (const name of NAMES) assert.equal(fs.readFileSync(path.join(root, '.claude', 'hooks', name), 'utf8'), '// user edit\n', name);
104
106
  const after = readJson(manifestPath);
105
- for (const name of NAMES) assert.equal(Object.prototype.hasOwnProperty.call(after.files, `hooks/${name}`), false, `record ${name}`);
107
+ for (const name of NAMES) assert.equal(after.files[`hooks/${name}`].sha256, '0'.repeat(64), `record ${name}`);
106
108
  const afterCommands = commands(readJson(settingsPath));
107
109
  for (const name of REGISTERED) {
108
110
  assert.equal(afterCommands.some((command) => command.includes(`.claude/hooks/${name}`)), false, name);
@@ -112,7 +114,7 @@ test('claude upgrade removes the legacy hooks and their CafeKit entries but keep
112
114
  });
113
115
  });
114
116
 
115
- test('codex upgrade removes the legacy hooks, their records and their CafeKit entries but keeps foreign ones', () => {
117
+ test('codex upgrade preserves edited legacy hooks and prunes only absent records', () => {
116
118
  inTempProject((root) => {
117
119
  install(root, ['codex']);
118
120
  const hooksPath = path.join(root, '.codex', 'hooks.json');
@@ -137,10 +139,18 @@ test('codex upgrade removes the legacy hooks, their records and their CafeKit en
137
139
  add('UserPromptSubmit', FOREIGN[0]);
138
140
  fs.writeFileSync(hooksPath, JSON.stringify(config, null, 2));
139
141
 
140
- install(root, ['codex']);
141
- for (const name of NAMES) assert.equal(fs.existsSync(path.join(root, '.codex', 'hooks', name)), false, name);
142
+ const upgrade = install(root, ['codex']);
143
+ assert.match(upgrade.stdout + upgrade.stderr, /Preserved 4 obsolete runtime/);
144
+ for (const name of NAMES) {
145
+ const file = path.join(root, '.codex', 'hooks', name);
146
+ if (name === 'completion-authority-state.cjs') assert.equal(fs.existsSync(file), false, name);
147
+ else assert.equal(fs.readFileSync(file, 'utf8'), '// user edit\n', name);
148
+ }
142
149
  const after = readJson(manifestPath);
143
- for (const name of NAMES) assert.equal(Object.prototype.hasOwnProperty.call(after.files, `.codex/hooks/${name}`), false, `record ${name}`);
150
+ for (const name of NAMES) {
151
+ if (name === 'completion-authority-state.cjs') assert.equal(after.files[`.codex/hooks/${name}`], undefined, `record ${name}`);
152
+ else assert.equal(after.files[`.codex/hooks/${name}`].sha256, '0'.repeat(64), `record ${name}`);
153
+ }
144
154
  const afterCommands = commands(readJson(hooksPath));
145
155
  for (const name of REGISTERED) {
146
156
  assert.equal(afterCommands.some((command) => command.includes(`.codex/hooks/${name}`) || command.includes(`.codex\\hooks\\${name}`)), false, name);
@@ -22,15 +22,18 @@ const MANIFEST = require(path.join(PACKAGE_ROOT, 'src/claude/migration-manifest.
22
22
  * The hooks an omp install runs: the Claude set the manifest ships, then `src/omp/hooks/`
23
23
  * written over it. Composed once under a dotted folder so runtime-dir.cjs derives `.omp`.
24
24
  */
25
- const COMPOSED_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-omp-composed-'));
26
- const HOOKS = path.join(COMPOSED_ROOT, '.omp', 'hooks');
27
- for (const rel of MANIFEST.runtime.files.filter((f) => f.startsWith('hooks/'))) {
28
- const target = path.join(COMPOSED_ROOT, '.omp', rel);
29
- fs.mkdirSync(path.dirname(target), { recursive: true });
30
- fs.copyFileSync(path.join(PACKAGE_ROOT, 'src/claude', rel), target);
25
+ function composeOmp(root) {
26
+ for (const rel of MANIFEST.runtime.files.filter((f) => f.startsWith('hooks/'))) {
27
+ const target = path.join(root, '.omp', rel);
28
+ fs.mkdirSync(path.dirname(target), { recursive: true });
29
+ fs.copyFileSync(path.join(PACKAGE_ROOT, 'src/claude', rel), target);
30
+ }
31
+ fs.cpSync(path.join(PACKAGE_ROOT, 'src/omp/hooks'), path.join(root, '.omp', 'hooks'), { recursive: true });
32
+ fs.cpSync(path.join(PACKAGE_ROOT, 'src/claude/scripts'), path.join(root, '.omp', 'scripts'), { recursive: true });
33
+ return path.join(root, '.omp', 'hooks');
31
34
  }
32
- fs.cpSync(path.join(PACKAGE_ROOT, 'src/omp/hooks'), HOOKS, { recursive: true });
33
- fs.cpSync(path.join(PACKAGE_ROOT, 'src/claude/scripts'), path.join(COMPOSED_ROOT, '.omp', 'scripts'), { recursive: true });
35
+ const COMPOSED_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-omp-composed-'));
36
+ const HOOKS = composeOmp(COMPOSED_ROOT);
34
37
 
35
38
  let bridge;
36
39
  test.before(async () => { bridge = await import(BRIDGE); });
@@ -49,10 +52,10 @@ function registered(eventName) {
49
52
  .filter(Boolean);
50
53
  }
51
54
 
52
- // Deliberate, named divergences while omp is not yet updated. Each must still be a real
53
- // difference, so the test fails (and the entry must go) once omp catches up.
54
- const CLAUDE_ONLY = { PostToolUse: ['spec-state.cjs'] };
55
- const OMP_ONLY = { SessionStart: ['docs-sync.cjs'] };
55
+ // Deliberate, named divergences. Each must still be a real difference, so the test fails
56
+ // (and the entry must go) once omp catches up.
57
+ const CLAUDE_ONLY = {};
58
+ const OMP_ONLY = {};
56
59
 
57
60
  test('the dispatch table mirrors settings.json for every event omp can deliver', () => {
58
61
  const { DISPATCH } = bridge;
@@ -169,3 +172,54 @@ test('input injections flow back as a transform that keeps the user text intact'
169
172
  assert.match(r.inject, /Secret handling/, 'secret-output-guardrail must inject on a credential prompt');
170
173
  } finally { fs.rmSync(root, { recursive: true, force: true }); }
171
174
  });
175
+
176
+ test('the omp session id is used for every event that does not carry its own', () => {
177
+ const ctx = { cwd: '/tmp/project', sessionManager: { getSessionId: () => 'omp-session-1' } };
178
+ assert.equal(bridge.shapePayload('input', { text: 'hi' }, ctx).session_id, 'omp-session-1');
179
+ assert.equal(bridge.shapePayload('tool_result', { toolName: 'edit', input: {} }, ctx).session_id, 'omp-session-1');
180
+ assert.equal(bridge.shapePayload('session_stop', { session_id: 'omp-session-1' }, ctx).session_id, 'omp-session-1',
181
+ 'session_stop carries the same id omp hands extensions, so touch and state stay in one place');
182
+ assert.ok(!bridge.DISPATCH.session_start.hooks.includes('docs-sync.cjs'), 'docs-sync runs on demand, as on Claude');
183
+ });
184
+
185
+ function ompPacket(root, name) {
186
+ const dir = path.join(root, 'specs', name);
187
+ fs.mkdirSync(dir, { recursive: true });
188
+ fs.writeFileSync(path.join(dir, 'plan.md'), `# ${name}\nSpecs-Contract: process-first-ready-v1\n`);
189
+ fs.writeFileSync(path.join(dir, 'task-01-work.md'), ['# Task 01 — work', '', 'Status: pending', '',
190
+ '## Dependencies', '', '- none', '', '## Verification Plan', '', '- Command: node --test', ''].join('\n'));
191
+ return dir;
192
+ }
193
+
194
+ test('an omp edit inside a packet scopes the tollgate to it', async () => {
195
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-omp-touch-'));
196
+ try {
197
+ const hooks = composeOmp(root);
198
+ const alpha = ompPacket(root, 'alpha');
199
+ ompPacket(root, 'beta');
200
+ const { spawnSync } = require('node:child_process');
201
+ for (const args of [['init', '-q'], ['config', 'user.email', 'cafekit@example.invalid'],
202
+ ['config', 'user.name', 'CafeKit Test'], ['commit', '--allow-empty', '-qm', 'fixture']]) {
203
+ assert.equal(spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' }).status, 0);
204
+ }
205
+ const session = (id) => ({ cwd: root, sessionManager: { getSessionId: () => id } });
206
+
207
+ const untouched = await bridge.runEvent('input', { text: 'Continue' }, session('omp-a'), { dir: hooks });
208
+ assert.doesNotMatch(untouched.inject, /Multiple active specs|Spec state changed/, 'a session that touched nothing hears nothing');
209
+
210
+ // A failed edit records nothing.
211
+ await bridge.runEvent('tool_result', { toolName: 'edit', input: { path: path.join(alpha, 'plan.md') }, content: [], isError: true },
212
+ session('omp-a'), { dir: hooks });
213
+ const afterFailure = await bridge.runEvent('input', { text: 'Continue' }, session('omp-a'), { dir: hooks });
214
+ assert.doesNotMatch(afterFailure.inject, /Multiple active specs|Spec state changed/, 'a failed edit records nothing');
215
+
216
+ await bridge.runEvent('tool_result', { toolName: 'edit', input: { path: path.join(alpha, 'plan.md') }, content: [], isError: false },
217
+ session('omp-a'), { dir: hooks });
218
+ // As on Claude, a session that touched one of two active packets is told about both.
219
+ const touched = await bridge.runEvent('input', { text: 'Continue' }, session('omp-a'), { dir: hooks });
220
+ assert.match(touched.inject, /Multiple active specs detected: alpha, beta/);
221
+
222
+ const other = await bridge.runEvent('input', { text: 'Continue' }, session('omp-b'), { dir: hooks });
223
+ assert.doesNotMatch(other.inject, /Multiple active specs|Spec state changed/, "another omp session's touch does not leak");
224
+ } finally { fs.rmSync(root, { recursive: true, force: true }); }
225
+ });
@@ -133,11 +133,9 @@ function expectedOverlay(name) {
133
133
  source = source.replace(from, to);
134
134
  };
135
135
  if (name === 'privacy-block.cjs') {
136
- edit(" permissionDecision: 'ask',\n permissionDecisionReason: `Sensitive file access requires approval: ${basename}`\n",
137
- " // omp has no ask state on tool_call, so an ask under Claude becomes a denial here.\n"
138
- + " permissionDecision: 'deny',\n permissionDecisionReason: `Sensitive file access requires approval: ${basename}`\n");
139
- edit(" permissionDecision: 'ask',\n permissionDecisionReason: 'Sensitive access could not be evaluated safely.'\n",
140
- " permissionDecision: 'deny',\n permissionDecisionReason: 'Sensitive access could not be evaluated safely.'\n");
136
+ edit(" permissionDecision: 'ask',\n",
137
+ " // omp has no ask state on tool_call, so an ask under Claude becomes a denial here.\n"
138
+ + " permissionDecision: 'deny',\n");
141
139
  }
142
140
  return source;
143
141
  }
@@ -157,3 +155,11 @@ test('the fork differs from Claude only by its overlay', () => {
157
155
  assert.match(phase, /claudeHookFiles/, 'the phase must install the Claude set first');
158
156
  assert.match(phase, /overlayFiles/, 'the phase must write the overlay over it');
159
157
  });
158
+
159
+ test('an empty stdin is denied under omp, where Claude asks', () => {
160
+ const result = spawnSync(process.execPath, [path.join(INSTALLED, 'privacy-block.cjs')], { input: '', encoding: 'utf8' });
161
+ assert.equal(result.status, 0, result.stderr);
162
+ const output = JSON.parse(result.stdout).hookSpecificOutput;
163
+ assert.equal(output.permissionDecision, 'deny');
164
+ assert.match(output.permissionDecisionReason, /Sensitive access could not be evaluated safely\./);
165
+ });
@@ -149,8 +149,15 @@ function composeCodexHookTree(root) {
149
149
  path.join(hooksDir, 'lib', 'hook-context.cjs'));
150
150
  fs.copyFileSync(path.join(PACKAGE_ROOT, 'src/codex/hooks/lib/privacy-state.cjs'),
151
151
  path.join(hooksDir, 'lib', 'privacy-state.cjs'));
152
+ fs.copyFileSync(path.join(PACKAGE_ROOT, 'src/codex/hooks/lib/rules-reservation.cjs'),
153
+ path.join(hooksDir, 'lib', 'rules-reservation.cjs'));
154
+ fs.copyFileSync(path.join(PACKAGE_ROOT, 'src/claude/hooks/lib/update-check.cjs'),
155
+ path.join(hooksDir, 'lib', 'update-check.cjs'));
152
156
  fs.copyFileSync(path.join(PACKAGE_ROOT, 'src/claude/hooks/lib/runtime-path-safety.cjs'),
153
157
  path.join(hooksDir, 'lib', 'runtime-path-safety.cjs'));
158
+ for (const name of ['privacy-paths.cjs', 'privacy-command-analysis.cjs']) {
159
+ fs.copyFileSync(path.join(PACKAGE_ROOT, 'src/claude/hooks/lib', name), path.join(hooksDir, 'lib', name));
160
+ }
154
161
  return path.join(hooksDir, 'session.cjs');
155
162
  }
156
163
 
@@ -114,13 +114,6 @@ const REQUIRED_PAYLOAD = [
114
114
  'src/claude/scripts/workflow-policy.cjs',
115
115
  'src/claude/scripts/provenance.cjs',
116
116
  'src/claude/scripts/spec-receipt.cjs',
117
- 'src/claude/scripts/spec-ground.cjs',
118
- 'src/claude/scripts/spec-final-state.cjs',
119
- 'src/claude/scripts/spec-readiness.cjs',
120
- 'src/claude/scripts/spec-semantic-model.cjs',
121
- 'src/claude/scripts/validate-spec-output.cjs',
122
- 'src/claude/scripts/spec-authoring-validation.cjs',
123
- 'src/claude/scripts/spec-authoring-digest.cjs',
124
117
  'src/claude/scripts/generate-skill-catalog.cjs',
125
118
  'src/claude/rules/process-management.md',
126
119
  'src/claude/rules/review-audit-self-decision.md',
@@ -1522,13 +1515,11 @@ function assertInstalledProvenance(root, platform, fixture) {
1522
1515
  const policy = path.join(scripts, 'workflow-policy.cjs');
1523
1516
  const resolver = path.join(scripts, 'spec-resolver.cjs');
1524
1517
  const receipt = path.join(scripts, 'spec-receipt.cjs');
1525
- const finalState = path.join(scripts, 'spec-final-state.cjs');
1526
- const readiness = path.join(scripts, 'spec-readiness.cjs');
1527
1518
  const gate = path.join(
1528
1519
  root,
1529
1520
  platform === 'claude' ? '.claude/hooks/spec-gate.cjs' : '.codex/hooks/spec-gate.cjs'
1530
1521
  );
1531
- for (const file of [helper, policy, resolver, receipt, finalState, readiness]) {
1522
+ for (const file of [helper, policy, resolver, receipt]) {
1532
1523
  assert.equal(fs.existsSync(file), true, `${platform} installed file missing: ${file}`);
1533
1524
  }
1534
1525
  assert.equal(fs.existsSync(gate), true, `${platform} installed gate missing: ${gate}`);
@@ -1660,26 +1651,13 @@ function assertInstalledScripts(root, platform) {
1660
1651
  const scripts = path.join(root, RUNTIMES[platform].root, 'scripts');
1661
1652
  const policy = path.join(scripts, 'workflow-policy.cjs');
1662
1653
  const scanner = path.join(scripts, 'scan-staged-secrets.cjs');
1663
- const grounder = path.join(scripts, 'spec-ground.cjs');
1664
- const finalState = path.join(scripts, 'spec-final-state.cjs');
1665
- const readiness = path.join(scripts, 'spec-readiness.cjs');
1666
- const validator = path.join(scripts, 'validate-spec-output.cjs');
1667
1654
  assert.ok(fs.existsSync(policy), `installed policy missing: ${policy}`);
1668
1655
  assert.ok(fs.existsSync(scanner), `installed scanner missing: ${scanner}`);
1669
- assert.ok(fs.existsSync(grounder), `installed validator dependency missing: ${grounder}`);
1670
- assert.ok(fs.existsSync(finalState), `installed final-state dependency missing: ${finalState}`);
1671
- assert.ok(fs.existsSync(readiness), `installed readiness finalizer missing: ${readiness}`);
1672
- assert.ok(fs.existsSync(validator), `installed validator missing: ${validator}`);
1673
1656
 
1674
1657
  const policyRun = spawnSync(process.execPath, [policy, '--json'], { cwd: root, encoding: 'utf8' });
1675
1658
  assert.equal(policyRun.status, 0, `${policyRun.stdout}\n${policyRun.stderr}`);
1676
1659
  assert.equal(JSON.parse(policyRun.stdout).contract, 'execution-policy');
1677
1660
 
1678
- const validatorRun = spawnSync(process.execPath, [validator], { cwd: root, encoding: 'utf8' });
1679
- assert.equal(validatorRun.status, 2, `${validatorRun.stdout}\n${validatorRun.stderr}`);
1680
- assert.match(validatorRun.stderr, /Usage: .*validate-spec-output\.cjs/);
1681
- assert.doesNotMatch(validatorRun.stderr, /MODULE_NOT_FOUND|Cannot find module/);
1682
-
1683
1661
  const safe = path.join(root, 'safe.txt');
1684
1662
  fs.writeFileSync(safe, 'mode=safe\n');
1685
1663
  spawnSync('git', ['init', '-q'], { cwd: root });
@@ -1689,347 +1667,10 @@ function assertInstalledScripts(root, platform) {
1689
1667
  assert.match(scannerRun.stdout, /No staged secrets found/);
1690
1668
  }
1691
1669
 
1692
- function installedSemanticPaths(root, platform) {
1693
- const runtimeRoot = path.join(root, RUNTIMES[platform].root);
1694
- const skillRoot = platform === 'codex'
1695
- ? path.join(root, '.agents', 'skills')
1696
- : path.join(runtimeRoot, 'skills');
1697
- const paths = {
1698
- policy: path.join(runtimeRoot, 'scripts', 'workflow-policy.cjs'),
1699
- scaffold: path.join(runtimeRoot, 'scripts', 'spec-scaffold.cjs'),
1700
- validator: path.join(runtimeRoot, 'scripts', 'validate-spec-output.cjs'),
1701
- authoringValidator: path.join(runtimeRoot, 'scripts', 'spec-authoring-validation.cjs'),
1702
- authoringDigest: path.join(runtimeRoot, 'scripts', 'spec-authoring-digest.cjs'),
1703
- grounder: path.join(runtimeRoot, 'scripts', 'spec-ground.cjs'),
1704
- finalState: path.join(runtimeRoot, 'scripts', 'spec-final-state.cjs'),
1705
- readiness: path.join(runtimeRoot, 'scripts', 'spec-readiness.cjs'),
1706
- semanticModel: path.join(runtimeRoot, 'scripts', 'spec-semantic-model.cjs'),
1707
- resolver: path.join(runtimeRoot, 'scripts', 'spec-resolver.cjs'),
1708
- provenance: path.join(runtimeRoot, 'scripts', 'provenance.cjs'),
1709
- stateTemplate: path.join(skillRoot, 'specs', 'templates', 'spec-state.json'),
1710
- };
1711
- for (const [name, target] of Object.entries(paths)) {
1712
- assert.equal(fs.existsSync(target), true, `${platform} installed ${name} missing: ${target}`);
1713
- assert.equal(path.relative(root, target).startsWith('..'), false, `${platform} ${name} escaped install root`);
1714
- }
1715
- return paths;
1716
- }
1717
-
1718
- function runInstalled(script, args, root, expected = 0) {
1719
- const result = spawnSync(process.execPath, [script, ...args], { cwd: root, encoding: 'utf8' });
1720
- assert.equal(result.status, expected, `${script}\n${result.stdout}\n${result.stderr}`);
1721
- return result;
1722
- }
1723
-
1724
1670
  function writeJson(target, value) {
1725
1671
  fs.writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`);
1726
1672
  }
1727
1673
 
1728
- function materializeSpecState(paths, root, feature, { strict = false } = {}) {
1729
- const featureDir = path.join(root, 'specs', feature);
1730
- runInstalled(paths.scaffold, [feature], root);
1731
- const state = JSON.parse(fs.readFileSync(path.join(featureDir, 'spec.json'), 'utf8'));
1732
- state.scope_lock.in_scope = [`${feature} behavior`];
1733
- state.scope_lock.out_of_scope = ['unrelated behavior'];
1734
- if (strict) {
1735
- state.workflow_policy.assurance_level = 'Strict';
1736
- state.workflow_policy.classified_minimum.assurance_level = 'Strict';
1737
- state.workflow_policy.risks = ['auth'];
1738
- }
1739
- writeJson(path.join(featureDir, 'spec.json'), state);
1740
- return { featureDir, specFile: path.join(featureDir, 'spec.json'), state };
1741
- }
1742
-
1743
- function requirementsProjection({ proof = false } = {}) {
1744
- return `# Requirements
1745
-
1746
- ## Requirements
1747
-
1748
- ### Requirement 1: Installed behavior
1749
-
1750
- - **R1.1**: The CommonJS entrypoint \`src/entry.js\` shall return an object containing only status \`enabled\` when its sole \`valid\` argument is the boolean \`true\`; it shall return an object containing only status \`rejected\` for \`false\`, a missing argument, or any non-boolean value.${proof ? '\n- **R1.2**: The installed verifier shall prove the observed entry behavior through its grounded evidence boundary.' : ''}
1751
- `;
1752
- }
1753
-
1754
- const TYPED_ANCHOR_COLUMNS = ['ID', 'Type', 'Target', 'Role', 'Access', 'Action'];
1755
-
1756
- function typedAnchorTable(rows) {
1757
- assert.ok(Array.isArray(rows) && rows.length > 0, 'typed anchor projection requires rows');
1758
- const header = `| ${TYPED_ANCHOR_COLUMNS.join(' | ')} |`;
1759
- const separator = `|${TYPED_ANCHOR_COLUMNS.map(() => '---').join('|')}|`;
1760
- const body = rows.map((row) => {
1761
- assert.deepEqual(Object.keys(row), ['id', 'type', 'target', 'role', 'access', 'action']);
1762
- assert.ok(Object.values(row).every((value) => typeof value === 'string' && value.trim() !== ''));
1763
- return `| ${row.id} | ${row.type} | \`${row.target}\` | ${row.role} | ${row.access} | ${row.action} |`;
1764
- });
1765
- return [header, separator, ...body].join('\n');
1766
- }
1767
-
1768
- function designProjection({
1769
- expected = 'exit 0; boolean true returns the exact enabled object; false, undefined, null, `"true"`, 1, and an object return the exact rejected object',
1770
- taskful = false,
1771
- } = {}) {
1772
- const anchors = typedAnchorTable([{
1773
- id: 'A-D-01', type: 'file', target: taskful ? 'src/design-boundary.js' : 'src/entry.js',
1774
- role: taskful ? 'existing design boundary for the entrypoint' : 'existing runtime entrypoint and contract owner',
1775
- access: 'read', action: 'read',
1776
- }]);
1777
- const reachabilityAnchors = taskful ? 'A-R1-01-01, A-R1-02-02' : 'A-D-01';
1778
- return `# Design
1779
-
1780
- ## Boundary
1781
-
1782
- - **Owns:** The exact return contract of the CommonJS function exported by \`src/entry.js\`.
1783
- - **Reads:** One \`valid\` argument; only the boolean \`true\` is valid.
1784
- - **Writes/exposes:** Exactly one status field whose value is \`enabled\` or \`rejected\`.
1785
- - **Outside boundary:** Unrelated runtime behavior.
1786
-
1787
- ## Typed Anchors
1788
-
1789
- ${anchors}
1790
-
1791
- ## Decisions and Invariants
1792
-
1793
- ### D1 — Installed result decision
1794
-
1795
- - **Decision:** \`valid === true\` returns an object containing only status \`enabled\`; every other value returns an object containing only status \`rejected\`.
1796
- - **Rejects ambiguity:** Truthy non-booleans such as \`"true"\` never count as valid.
1797
- - **Negative path:** \`false\`, \`undefined\`, \`null\`, strings, numbers, and objects return rejected.
1798
- - **Anchors:** A-D-01
1799
-
1800
- ### I1 — Rejection invariant
1801
-
1802
- Any input other than the boolean \`true\` never returns enabled.
1803
-
1804
- ### C1 — Result contract
1805
-
1806
- - **Owner:** A-D-01
1807
- - **Consumers:** \`test/entry.test.js\` and callers of the CommonJS export.
1808
- - **Shape/behavior:** The result is an object with exactly one \`status\` field set to \`enabled\` or \`rejected\`.
1809
- - **Compatibility:** The strict boolean discriminator, exact object shape, and two status values remain stable.
1810
-
1811
- ## Verification Definitions
1812
-
1813
- - **V1**: Criteria R1.1; Owner ${taskful ? 'R1-01' : 'A-D-01'}; ${taskful ? 'Proof criteria R1.2; Proof owner R1-02; Evidence anchor A-R1-02-02; ' : ''}Decision refs D1, I1, C1; Method command \`node --test test/entry.test.js\`; Expected ${expected}; Negative/failure \`false\`, missing input, \`null\`, and truthy non-booleans all return the exact rejected object; Reachability/grounding entrypoint \`src/entry.js\` via ${reachabilityAnchors}.
1814
- `;
1815
- }
1816
-
1817
- function completeSemanticReview(paths, root, fixture, counterexamples) {
1818
- // C16/D13: only the installed spec-authoring-validation.cjs coordinator may
1819
- // flip authoring.* to validated and write the matching receipt (I21/R3.9) —
1820
- // run it over the fixture's current bytes instead of hand-writing the enum.
1821
- // Lifecycle order (I15/R3.7): authoring -> coordinator -> semantic review ->
1822
- // readiness, never the reverse.
1823
- runInstalled(paths.scaffold, [fixture.state.feature_name, '--sync-semantic-model'], root);
1824
- runInstalled(paths.authoringValidator, [fixture.featureDir], root);
1825
- const state = JSON.parse(fs.readFileSync(fixture.specFile, 'utf8'));
1826
- fixture.reviewResult = {
1827
- verdict: 'PASS',
1828
- findings: [],
1829
- unresolved_decisions: [],
1830
- graph_coverage: [
1831
- 'criterion_local', 'cross_criterion', 'runtime_path',
1832
- 'assumption_provenance', 'compatibility_migration',
1833
- ].map((surface) => ({
1834
- surface, covered: true,
1835
- notes: 'The review covers this semantic surface against the canonical model.',
1836
- })),
1837
- reviewed_criteria: counterexamples.map(({ criterion }) => criterion),
1838
- counterexamples,
1839
- reviewer_evidence: null,
1840
- };
1841
- if (state.workflow_policy.assurance_level === 'Strict') {
1842
- const digest = runInstalled(paths.validator, [fixture.featureDir, '--semantic-digest'], root).stdout.trim();
1843
- assert.match(digest, /^sha256:[a-f0-9]{64}$/);
1844
- return digest;
1845
- }
1846
- const reviewFile = path.join(fixture.featureDir, '.review-result.json');
1847
- writeJson(reviewFile, fixture.reviewResult);
1848
- runInstalled(paths.readiness, [fixture.featureDir, '--review-result', reviewFile], root);
1849
- fs.unlinkSync(reviewFile);
1850
- runInstalled(paths.validator, [fixture.featureDir], root);
1851
- runInstalled(paths.grounder, [fixture.featureDir, '--root', root], root);
1852
- return JSON.parse(fs.readFileSync(fixture.specFile, 'utf8')).validation.semantic_review.semantic_digest;
1853
- }
1854
-
1855
- function createTasklessFixture(paths, root, feature, options = {}) {
1856
- fs.mkdirSync(path.join(root, 'src'), { recursive: true });
1857
- fs.mkdirSync(path.join(root, 'test'), { recursive: true });
1858
- fs.writeFileSync(path.join(root, 'src', 'entry.js'), 'module.exports = (valid) => ({ status: valid === true ? "enabled" : "rejected" });\n');
1859
- fs.writeFileSync(path.join(root, 'src', 'design-boundary.js'), 'module.exports = { entry: "src/entry.js" };\n');
1860
- fs.writeFileSync(path.join(root, 'test', 'entry.test.js'), `const test = require('node:test');
1861
- const assert = require('node:assert/strict');
1862
- const entry = require('../src/entry.js');
1863
-
1864
- test('entry accepts only the boolean true discriminator', () => {
1865
- assert.deepEqual(entry(true), { status: 'enabled' });
1866
- for (const invalid of [false, undefined, null, 'true', 1, {}]) {
1867
- assert.deepEqual(entry(invalid), { status: 'rejected' });
1868
- }
1869
- });
1870
- `);
1871
- fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ scripts: { test: 'node --test' } }));
1872
- const fixture = materializeSpecState(paths, root, feature, options);
1873
- fs.writeFileSync(path.join(fixture.featureDir, 'requirements.md'), requirementsProjection());
1874
- fs.writeFileSync(path.join(fixture.featureDir, 'design.md'), designProjection());
1875
- fixture.digest = completeSemanticReview(paths, root, fixture, [{
1876
- criterion: 'R1.1', case_kind: 'failure',
1877
- scenario: 'The installed entry receives input without the required discriminator.',
1878
- expected: 'The installed entry returns rejected and never returns enabled.',
1879
- decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
1880
- }]);
1881
- return fixture;
1882
- }
1883
-
1884
- function taskProjection({ id, title, ownedPath, criterion, verificationRef, role, artifact = false }) {
1885
- const artifactPath = `artifacts/${id}.json`;
1886
- const anchors = typedAnchorTable([
1887
- { id: `A-${id}-01`, type: 'file', target: ownedPath, role: 'owner', access: 'write', action: 'modify' },
1888
- ...(artifact ? [{
1889
- id: `A-${id}-02`, type: 'artifact', target: artifactPath,
1890
- role: 'verifier', access: 'write', action: 'create',
1891
- }] : []),
1892
- {
1893
- id: `A-${id}-${artifact ? '03' : '02'}`, type: 'command',
1894
- target: 'node --test test/entry.test.js', role: 'verifier', access: 'read', action: 'read',
1895
- },
1896
- ]);
1897
- return `# Task ${id}: ${title}
1898
- **Status:** pending
1899
-
1900
- ## Outcome
1901
-
1902
- Deliver observable ${title.toLowerCase()} behavior through the installed entrypoint.
1903
-
1904
- ## Scope
1905
-
1906
- - **In scope:** Exact behavior owned at ${ownedPath}.
1907
- - **Out of scope:** Unrelated runtime behavior.
1908
-
1909
- ## Anchors and Ownership
1910
-
1911
- ${anchors}
1912
-
1913
- ## Changes
1914
-
1915
- - [ ] Implement the exact owned behavior. _Requirements: ${criterion.slice(1)}_
1916
-
1917
- ## Acceptance
1918
-
1919
- - **${criterion}:** The installed command returns the criterion-specific observable state.
1920
-
1921
- ## Dependencies
1922
-
1923
- - none
1924
-
1925
- ## Verification Plan
1926
-
1927
- - **Verification ref:** ${verificationRef}
1928
- - **Task role:** ${role}
1929
- - **Command:** \`node --test test/entry.test.js\`
1930
- - **Expected:** Exit code 0 and the criterion-specific state is observed.${artifact ? ` The artifact \`${artifactPath}\` must exist, and SHA-256 over its current bytes must match the recorded digest.` : ''}
1931
- - **Negative path:** Invalid input returns the named rejected state.
1932
- - **Reachability:** \`src/entry.js\` is reached by the installed test command.
1933
- `;
1934
- }
1935
-
1936
- function createTaskFixture(paths, root, feature) {
1937
- fs.mkdirSync(path.join(root, 'artifacts'), { recursive: true });
1938
- const fixture = materializeSpecState(paths, root, feature);
1939
- fs.writeFileSync(path.join(fixture.featureDir, 'requirements.md'), requirementsProjection({ proof: true }));
1940
- fs.writeFileSync(path.join(fixture.featureDir, 'design.md'), designProjection({
1941
- expected: 'exit 0, status enabled, and verifier artifact `artifacts/R1-02.json` whose SHA-256 over current bytes matches the recorded digest',
1942
- taskful: true,
1943
- }));
1944
- const taskOne = 'tasks/task-R1-01-implement.md';
1945
- const taskTwo = 'tasks/task-R1-02-verify.md';
1946
- fs.mkdirSync(path.join(fixture.featureDir, 'tasks'), { recursive: true });
1947
- fs.writeFileSync(path.join(fixture.featureDir, taskOne), taskProjection({
1948
- id: 'R1-01', title: 'Implement behavior', ownedPath: 'src/entry.js', criterion: 'R1.1',
1949
- verificationRef: 'V1', role: 'subject implements R1.1',
1950
- }));
1951
- fs.writeFileSync(path.join(fixture.featureDir, taskTwo), taskProjection({
1952
- id: 'R1-02', title: 'Verify behavior', ownedPath: 'test/entry.test.js', criterion: 'R1.2',
1953
- verificationRef: 'V1', role: 'verifier verifies V1 through separately owned R1.2 proof', artifact: true,
1954
- }));
1955
- const state = fixture.state;
1956
- state.authoring.tasks = 'draft';
1957
- state.task_files = [taskOne, taskTwo];
1958
- state.task_registry = Object.fromEntries([
1959
- [taskOne, { id: 'R1-01', title: 'Implement behavior', status: 'pending', dependencies: [], blocker: null, started_at: null, completed_at: null, last_updated_at: null }],
1960
- [taskTwo, { id: 'R1-02', title: 'Verify behavior', status: 'pending', dependencies: [], blocker: null, started_at: null, completed_at: null, last_updated_at: null }],
1961
- ]);
1962
- state.coordination.boundaries = [{
1963
- id: 'B-OWN', type: 'ownership', tasks: ['R1-01', 'R1-02'],
1964
- write_sets: { 'R1-01': ['src/entry.js'], 'R1-02': ['test/entry.test.js', 'artifacts/R1-02.json'] },
1965
- }, {
1966
- id: 'B-PROOF', type: 'proof', subject: 'R1-01', verifier: 'R1-02',
1967
- verification_ref: 'V1', artifact_anchor: 'A-R1-02-02',
1968
- }];
1969
- writeJson(fixture.specFile, state);
1970
- fixture.digest = completeSemanticReview(paths, root, fixture, [{
1971
- criterion: 'R1.1', case_kind: 'failure',
1972
- scenario: 'Invalid input reaches the installed implementation boundary.',
1973
- expected: 'The implementation returns rejected and never reports enabled.',
1974
- decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
1975
- }, {
1976
- criterion: 'R1.2', case_kind: 'failure',
1977
- scenario: 'The verification command completes without producing its proof artifact.',
1978
- expected: 'The verification criterion remains unsatisfied until `artifacts/R1-02.json` exists and its SHA-256 over current bytes matches the recorded digest.',
1979
- decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
1980
- }]);
1981
- return fixture;
1982
- }
1983
-
1984
- function initializeGit(root) {
1985
- fs.writeFileSync(path.join(root, '.gitignore'), `${'node_' + 'modules'}/\n`);
1986
- for (const args of [
1987
- ['init', '-q'], ['config', 'user.name', 'CafeKit Packed E2E'],
1988
- ['config', 'user.email', 'packed-e2e@example.invalid'],
1989
- ['add', '--', '.gitignore', 'src/entry.js', 'src/design-boundary.js', 'test/entry.test.js', 'package.json'],
1990
- ['commit', '--no-gpg-sign', '-qm', 'packed semantic fixture'],
1991
- ]) {
1992
- const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
1993
- assert.equal(result.status, 0, `${args.join(' ')}\n${result.stdout}\n${result.stderr}`);
1994
- }
1995
- }
1996
-
1997
- function assertStrictRefusedAsUnsupported(paths, root, fixture) {
1998
- const specFile = path.join(fixture.featureDir, 'spec.json');
1999
- const before = fs.readFileSync(specFile);
2000
- const reviewFile = path.join(root, 'strict-review.json');
2001
- fs.writeFileSync(reviewFile, JSON.stringify(fixture.reviewResult));
2002
- const result = spawnSync(process.execPath, [paths.readiness, fixture.featureDir, '--review-result', reviewFile], { cwd: root, encoding: 'utf8' });
2003
- fs.rmSync(reviewFile, { force: true });
2004
- assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`);
2005
- assert.match(`${result.stdout}\n${result.stderr}`, /Strict assurance is no longer supported/);
2006
- assert.equal(fs.readFileSync(specFile).equals(before), true, 'a refused Strict readiness must leave spec.json unchanged');
2007
- }
2008
-
2009
- function assertStaleDigestMutations(paths, root, taskless, taskBearing) {
2010
- const requirementFile = path.join(taskless.featureDir, 'requirements.md');
2011
- const requirementBytes = fs.readFileSync(requirementFile);
2012
- fs.appendFileSync(requirementFile, '\nThe installed Markdown meaning changed.\n');
2013
- assert.match(runInstalled(paths.validator, [taskless.featureDir], root, 1).stderr, /semantic_digest: stale/);
2014
- fs.writeFileSync(requirementFile, requirementBytes);
2015
-
2016
- const tasklessState = JSON.parse(fs.readFileSync(taskless.specFile, 'utf8'));
2017
- const originalPolicy = JSON.parse(JSON.stringify(tasklessState.workflow_policy));
2018
- tasklessState.workflow_policy.planning_depth = 'Full';
2019
- writeJson(taskless.specFile, tasklessState);
2020
- assert.match(runInstalled(paths.validator, [taskless.featureDir], root, 1).stderr, /semantic_digest: stale/);
2021
- tasklessState.workflow_policy = originalPolicy;
2022
- writeJson(taskless.specFile, tasklessState);
2023
-
2024
- const taskState = JSON.parse(fs.readFileSync(taskBearing.specFile, 'utf8'));
2025
- const originalId = taskState.coordination.boundaries[0].id;
2026
- taskState.coordination.boundaries[0].id = 'B-OWN-MUTATED';
2027
- writeJson(taskBearing.specFile, taskState);
2028
- assert.match(runInstalled(paths.validator, [taskBearing.featureDir], root, 1).stderr, /semantic_digest: stale/);
2029
- taskState.coordination.boundaries[0].id = originalId;
2030
- writeJson(taskBearing.specFile, taskState);
2031
- }
2032
-
2033
1674
  function assertTransforms(root, platform) {
2034
1675
  const skillRoot = path.join(
2035
1676
  root,
@@ -2745,41 +2386,6 @@ test('repository and package guides document adaptive Docs usage', () => {
2745
2386
  }
2746
2387
  });
2747
2388
 
2748
- test('packed Claude and Codex installs execute semantic kernel behavior without package source', () => {
2749
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-semantic-'));
2750
- const destination = path.join(root, 'pack');
2751
- fs.mkdirSync(destination, { recursive: true });
2752
- try {
2753
- const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
2754
- const tarball = path.join(destination, packed.filename);
2755
- const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2756
- assertCleanInventory(packedInventory(tarball));
2757
-
2758
- for (const platform of ['claude', 'codex']) {
2759
- const project = path.join(root, platform);
2760
- const installer = installPacked(tarball, project, runtimeClosure);
2761
- runInstaller(installer, project, [platform], null);
2762
- const packedSource = path.resolve(path.dirname(installer), '..', 'src');
2763
- fs.rmSync(packedSource, { recursive: true, force: true });
2764
- assert.equal(fs.existsSync(packedSource), false, `${platform} package source must be unavailable`);
2765
-
2766
- const paths = installedSemanticPaths(project, platform);
2767
- const taskless = createTasklessFixture(paths, project, 'compact-installed');
2768
- initializeGit(project);
2769
-
2770
- const strict = createTasklessFixture(paths, project, 'strict-installed', { strict: true });
2771
- const taskBearing = createTaskFixture(paths, project, 'tasks-installed');
2772
- assertStrictRefusedAsUnsupported(paths, project, strict);
2773
- assertStaleDigestMutations(paths, project, taskless, taskBearing);
2774
- runInstalled(paths.validator, [taskless.featureDir], project);
2775
- runInstalled(paths.validator, [taskBearing.featureDir], project);
2776
- runInstalled(paths.grounder, [taskBearing.featureDir, '--root', project], project);
2777
- }
2778
- } finally {
2779
- fs.rmSync(root, { recursive: true, force: true });
2780
- }
2781
- });
2782
-
2783
2389
  test('packed Claude and Codex installs self-contain runtime provenance and fail closed without it', () => {
2784
2390
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-provenance-package-'));
2785
2391
  const destination = path.join(root, 'pack');