@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
@@ -0,0 +1,136 @@
1
+ 'use strict';
2
+
3
+ const assert = require('node:assert/strict');
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+ const { spawnSync } = require('node:child_process');
8
+ const { test } = require('node:test');
9
+ const SOURCE = path.resolve(__dirname, '../../src');
10
+
11
+ function fixture(run) {
12
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ck-native-boundaries-'));
13
+ const hooks = path.join(root, '.codex/hooks');
14
+ try {
15
+ fs.cpSync(path.join(SOURCE, 'codex/hooks'), hooks, { recursive: true });
16
+ for (const name of ['runtime-path-safety', 'privacy-command-analysis', 'privacy-paths', 'secret-keywords', 'inspect-paths', 'update-check']) {
17
+ fs.copyFileSync(path.join(SOURCE, `claude/hooks/lib/${name}.cjs`), path.join(hooks, `lib/${name}.cjs`));
18
+ }
19
+ fs.cpSync(path.join(SOURCE, 'claude/scripts'), path.join(root, '.codex/scripts'), { recursive: true });
20
+ fs.writeFileSync(path.join(root, '.gitignore'), '.codex/\n');
21
+ for (const args of [['init', '-q'], ['config', 'user.email', 'probe@example.invalid'], ['config', 'user.name', 'Probe'], ['add', '.gitignore'], ['commit', '-qm', 'base']]) {
22
+ assert.equal(spawnSync('git', ['-C', root, ...args]).status, 0);
23
+ }
24
+ const hook = (name, payload = {}) => spawnSync(process.execPath, [path.join(hooks, `${name}.cjs`)], {
25
+ cwd: root, input: JSON.stringify({ cwd: root, session_id: 'a', ...payload }), encoding: 'utf8',
26
+ });
27
+ run({ root, hooks, hook });
28
+ } finally { fs.rmSync(root, { recursive: true, force: true }); }
29
+ }
30
+ function task(root, name, status = 'pending', receipt = '') {
31
+ const dir = path.join(root, 'specs/demo'); fs.mkdirSync(dir, { recursive: true });
32
+ fs.writeFileSync(path.join(dir, 'plan.md'), '# Plan\nSpecs-Contract: process-first-ready-v1\n');
33
+ fs.writeFileSync(path.join(dir, name), `# Task\nStatus: ${status}\n## Dependencies\n- none\n## Verification Plan\n- Command: node --test\n${receipt}`);
34
+ }
35
+
36
+ test('privacy walks nested operation arrays but ignores prose', () => fixture(({ hook }) => {
37
+ for (const tool_input of [{ operations: [{ path: 'credentials.json' }] }, { operations: [[{ file_path: 'credentials.json' }]] }, { paths: ['credentials.json'] }]) {
38
+ const result = hook('privacy-block', { tool_name: 'mcp__files__read', tool_input });
39
+ assert.match(result.stdout, /APPROVE CAFEKIT PRIVACY/);
40
+ assert.match(JSON.parse(result.stdout).hookSpecificOutput.permissionDecisionReason, /y \(or yes \/ có\)/);
41
+ }
42
+ assert.equal(hook('privacy-block', { tool_name: 'mcp__files__read', tool_input: { messages: ['credentials.json', { text: 'credentials.json' }] } }).stdout, '');
43
+ }));
44
+
45
+ test('privacy-block allows ls when configured specs escapes the project', () => fixture(({ root, hook }) => {
46
+ fs.writeFileSync(path.join(root, '.codex/runtime.json'), JSON.stringify({ paths: { specs: '../outside' } }));
47
+ const result = hook('privacy-block', { tool_name: 'Bash', tool_input: { command: 'ls' } });
48
+ assert.equal(result.status, 0);
49
+ assert.equal(result.stdout, '');
50
+ }));
51
+
52
+ test('privacy-block issues approval for a sensitive read when configured specs escapes the project', () => fixture(({ root, hook }) => {
53
+ fs.writeFileSync(path.join(root, '.env'), '');
54
+ fs.writeFileSync(path.join(root, '.codex/runtime.json'), JSON.stringify({ paths: { specs: '../outside' } }));
55
+ const result = hook('privacy-block', { tool_name: 'Bash', tool_input: { command: 'cat .env' } });
56
+ assert.equal(result.status, 0);
57
+ const output = JSON.parse(result.stdout).hookSpecificOutput;
58
+ assert.equal(output.permissionDecision, 'deny');
59
+ assert.match(output.permissionDecisionReason, /APPROVE CAFEKIT PRIVACY [a-f0-9]{24}/);
60
+ assert.match(output.permissionDecisionReason, /y \(or yes \/ có\)/);
61
+ }));
62
+
63
+ test('privacy-approval grants a pending read when configured specs escapes the project', () => fixture(({ root, hook }) => {
64
+ fs.writeFileSync(path.join(root, '.env'), '');
65
+ const payload = { tool_name: 'Bash', tool_input: { command: 'cat .env' } };
66
+ const pending = hook('privacy-block', payload);
67
+ assert.equal(pending.status, 0);
68
+ const approval = pending.stdout.match(/APPROVE CAFEKIT PRIVACY [a-f0-9]{24}/);
69
+ assert.ok(approval, 'the sensitive read must issue a pending approval');
70
+ fs.writeFileSync(path.join(root, '.codex/runtime.json'), JSON.stringify({ paths: { specs: '../outside' } }));
71
+ const granted = hook('privacy-approval', { prompt: approval[0] });
72
+ assert.equal(granted.status, 0);
73
+ assert.match(granted.stdout, /User approved the pending sensitive access/);
74
+ const allowed = hook('privacy-block', payload);
75
+ assert.equal(allowed.status, 0);
76
+ assert.equal(allowed.stdout, '');
77
+ }));
78
+
79
+ test('session startup revokes only its own pending requests and grants', () => fixture(({ root, hooks, hook }) => {
80
+ const privacy = require(path.join(hooks, 'lib/privacy-state.cjs'));
81
+ const request = { projectRoot: root, sessionCwd: root, sessionId: 'a', filePath: 'credentials.json' };
82
+ const grant = privacy.createPending(request);
83
+ assert.equal(privacy.approvePending({ ...request, requestId: grant.requestId }).ok, true);
84
+ const pending = privacy.createPending(request);
85
+ for (const session_id of ['b', undefined]) {
86
+ assert.equal(hook('session', { session_id, hook_event_name: 'SessionStart', source: 'compact' }).status, 0);
87
+ assert.equal(privacy.hasGrant(request), true);
88
+ assert.ok(fs.existsSync(path.join(hooks, `.privacy/pending-${pending.requestId}.json`)));
89
+ }
90
+ assert.equal(hook('session', { hook_event_name: 'SessionStart', source: 'resume' }).status, 0);
91
+ assert.equal(privacy.hasGrant(request), false);
92
+ assert.equal(privacy.approvePending({ ...request, requestId: pending.requestId }).ok, false);
93
+ }));
94
+
95
+ test('cache persistence failure does not replace receipt authority', () => fixture(({ root, hooks, hook }) => {
96
+ task(root, 'task-01-work.md');
97
+ fs.mkdirSync(path.join(hooks, '.logs/spec-gate-last.json'), { recursive: true });
98
+ assert.equal(hook('spec-gate', { hook_event_name: 'Stop' }).stdout, '');
99
+ task(root, 'task-01-work.md', 'done');
100
+ const result = hook('spec-gate', { hook_event_name: 'Stop', explicitFeature: 'demo' });
101
+ assert.match(JSON.parse(result.stdout).reason, /lack a verification receipt/);
102
+ assert.doesNotMatch(result.stdout, /EISDIR|controlled failure/);
103
+ fs.writeFileSync(path.join(root, '.codex/runtime.json'), '{"spec":{"completion_gate":false}}');
104
+ assert.match(hook('spec-gate', { hook_event_name: 'Stop' }).stdout, /no completion-gate bypass/);
105
+ }));
106
+
107
+ test('build output directories are allowed while inspection commands stay blocked', () => fixture(({ hook }) => {
108
+ for (const command of ['vite build --outDir ./dist', 'tsc --outDir ./build', 'npm run build -- --outDir ./dist', 'cargo build --target-dir ./target']) {
109
+ assert.equal(hook('inspect-block', { tool_name: 'exec_command', tool_input: { command } }).status, 0, command);
110
+ }
111
+ for (const command of ['cat dist/index.js', 'find ./dist', 'grep x dist/index.js', 'vite build --outDir ./dist && cat dist/index.js', 'npm exec cat ./dist/index.js', 'vite build --outDir ./dist $(cat dist/index.js)']) {
112
+ assert.equal(hook('inspect-block', { tool_name: 'exec_command', tool_input: { command } }).status, 2, command);
113
+ }
114
+ assert.equal(hook('inspect-block', { tool_input: { path: './dist/index.js' } }).status, 2);
115
+ }));
116
+
117
+ test('one gate invocation captures one stable snapshot for multiple receipts and rechecks next run', () => fixture(({ root, hook }) => {
118
+ task(root, 'task-06-pending.md');
119
+ const provenanceFile = path.join(root, '.codex/scripts/provenance.cjs');
120
+ const trace = path.join(root, '.codex/manifest-count');
121
+ fs.writeFileSync(provenanceFile, fs.readFileSync(provenanceFile, 'utf8').replace('function manifest(root, specsRoot) {', `function manifest(root, specsRoot) { fs.appendFileSync(${JSON.stringify(trace)}, 'x');`));
122
+ const provenance = require(provenanceFile);
123
+ const context = provenance.deriveRuntimeContext({ projectRoot: root, specsRoot: path.join(root, 'specs'), specFile: path.join(root, 'specs/demo/plan.md'), featureName: 'demo', runtimeSession: 'a' });
124
+ const receipt = `## Receipt\nVerification: PASS\nCommand: node --test\nExit: 0\nBase: ${context.base}\nHead: ${context.head}\n\`\`\`text\nnode --test\n1 test passed\n\`\`\`\n`;
125
+ for (let n = 1; n <= 5; n += 1) task(root, `task-0${n}-work.md`, 'done', receipt);
126
+ for (const args of [['add', 'specs'], ['commit', '-qm', 'packet']]) assert.equal(spawnSync('git', ['-C', root, ...args]).status, 0);
127
+ fs.writeFileSync(trace, '');
128
+ assert.equal(hook('spec-gate', { hook_event_name: 'Stop' }).stdout, '');
129
+ assert.equal(fs.readFileSync(trace, 'utf8').length, 2);
130
+ fs.writeFileSync(path.join(root, 'changed.js'), 'changed');
131
+ fs.appendFileSync(path.join(root, 'specs/demo/task-01-work.md'), '\n');
132
+ const blocked = hook('spec-gate', { hook_event_name: 'Stop' }).stdout;
133
+ assert.match(blocked, /provenance/);
134
+ assert.match(blocked, /lines are present but are not the pair/);
135
+ assert.equal(fs.readFileSync(trace, 'utf8').length, 4);
136
+ }));