@haposoft/cafekit 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/bin/__tests__/claude-settings.test.js +123 -0
- package/bin/__tests__/codex-ask-guard.test.js +123 -0
- package/bin/__tests__/codex-ask-projection.test.js +51 -0
- package/bin/__tests__/codex-hooks-ownership.test.js +80 -1
- package/bin/__tests__/codex-hooks.test.js +938 -237
- package/bin/__tests__/codex-native-hook-boundaries.test.js +136 -0
- package/bin/__tests__/codex-native.test.js +222 -149
- package/bin/__tests__/codex-projection-parity.test.js +314 -0
- package/bin/__tests__/codex-quiet-hooks.test.js +170 -0
- package/bin/__tests__/codex-research-projection.test.js +70 -0
- package/bin/__tests__/codex-specs-projection.test.js +76 -0
- package/bin/__tests__/develop-contract.test.js +102 -1362
- package/bin/__tests__/docs-sync-on-demand.test.js +25 -20
- package/bin/__tests__/inspect-paths.test.js +183 -0
- package/bin/__tests__/installer-safety.test.js +335 -0
- package/bin/__tests__/legacy-authority-decoupled.test.js +14 -19
- package/bin/__tests__/legacy-hooks-retired.test.js +18 -8
- package/bin/__tests__/omp-bridge.test.js +66 -12
- package/bin/__tests__/omp-hooks.test.js +11 -5
- package/bin/__tests__/orca-session.test.js +7 -0
- package/bin/__tests__/package-inventory.test.js +1 -395
- package/bin/__tests__/privacy-paths.test.js +317 -0
- package/bin/__tests__/spec-narrowing.test.js +30 -134
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -372
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- package/bin/__tests__/usage-hook-retired.test.js +5 -3
- package/bin/__tests__/validator-grounding.test.js +17 -2929
- package/bin/install.js +25 -0
- package/bin/lib/codex-install.js +134 -12
- package/bin/lib/context.js +1 -1
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/claude-runtime.js +91 -25
- package/bin/phases/claude-settings.js +22 -1
- package/bin/phases/codex-hooks.js +96 -33
- package/bin/phases/codex-runtime.js +45 -49
- package/bin/phases/copy-payload.js +11 -28
- package/bin/phases/omp-runtime.js +6 -3
- package/package.json +2 -3
- package/src/claude/CLAUDE.md +4 -0
- package/src/claude/agents/code-auditor.md +9 -6
- package/src/claude/agents/debugger.md +3 -9
- package/src/claude/agents/docs-keeper.md +4 -7
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +6 -7
- package/src/claude/agents/inspector.md +2 -0
- package/src/claude/agents/project-manager.md +2 -2
- package/src/claude/agents/spec-maker.md +4 -4
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/gitignore +3 -0
- package/src/claude/hooks/__tests__/installed-root.test.js +111 -0
- package/src/claude/hooks/__tests__/precompact.test.js +206 -10
- package/src/claude/hooks/__tests__/privacy-block.test.js +27 -0
- package/src/claude/hooks/__tests__/session-writeenv.test.js +25 -0
- package/src/claude/hooks/__tests__/spec-gate.test.js +311 -340
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +230 -8
- package/src/claude/hooks/__tests__/state.test.js +207 -7
- package/src/claude/hooks/__tests__/update-check.test.js +88 -0
- package/src/claude/hooks/agent.cjs +7 -6
- package/src/claude/hooks/inspect-block.cjs +15 -63
- package/src/claude/hooks/lib/inspect-paths.cjs +304 -0
- package/src/claude/hooks/lib/parser.cjs +1 -1
- package/src/claude/hooks/lib/privacy-command-analysis.cjs +191 -17
- package/src/claude/hooks/lib/privacy-paths.cjs +313 -0
- package/src/claude/hooks/lib/project-root.cjs +15 -0
- package/src/claude/hooks/lib/rules-reservation.cjs +36 -0
- package/src/claude/hooks/lib/spec-touch.cjs +67 -0
- package/src/claude/hooks/lib/update-check.cjs +77 -0
- package/src/claude/hooks/precompact.cjs +8 -15
- package/src/claude/hooks/privacy-block.cjs +23 -205
- package/src/claude/hooks/rules.cjs +6 -28
- package/src/claude/hooks/session.cjs +34 -117
- package/src/claude/hooks/spec-gate.cjs +17 -89
- package/src/claude/hooks/spec-state.cjs +89 -96
- package/src/claude/hooks/state.cjs +96 -51
- package/src/claude/migration-manifest.json +20 -8
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +1 -2
- package/src/claude/rules/hook-protocols.md +1 -1
- package/src/claude/rules/workflow.md +1 -1
- package/src/claude/runtime.json +0 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +2 -2
- package/src/claude/scripts/provenance.cjs +14 -0
- package/src/claude/scripts/spec-receipt.cjs +0 -114
- package/src/claude/scripts/spec-resolver.cjs +41 -457
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/settings/settings.json +1 -1
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/ai-multimodal/scripts/check_setup.py +8 -68
- package/src/claude/skills/ai-multimodal/scripts/gemini_batch_process.py +6 -28
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +25 -108
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -7
- package/src/claude/skills/develop/SKILL.md +22 -70
- package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
- package/src/claude/skills/git/SKILL.md +1 -1
- package/src/claude/skills/git/references/worktree-blueprint.md +1 -1
- package/src/claude/skills/research/SKILL.md +24 -55
- package/src/claude/skills/sync/SKILL.md +12 -34
- package/src/claude/skills/sync/references/rebind-and-audit.md +2 -2
- package/src/claude/skills/test/SKILL.md +39 -123
- package/src/claude/skills/test/references/execution-strategy.md +11 -22
- package/src/claude/skills/ui-ux/SKILL.md +1 -1
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +24 -79
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- package/src/codex/AGENTS.md +8 -0
- package/src/codex/hooks/ask-guard.cjs +54 -0
- package/src/codex/hooks/inspect-block.cjs +11 -7
- package/src/codex/hooks/lib/inspect-paths.cjs +1 -199
- package/src/codex/hooks/lib/privacy-paths.cjs +1 -0
- package/src/codex/hooks/lib/privacy-state.cjs +98 -16
- package/src/codex/hooks/lib/rules-reservation.cjs +38 -0
- package/src/codex/hooks/lib/secret-keywords.cjs +1 -0
- package/src/codex/hooks/lib/spec-receipt.cjs +2 -30
- package/src/codex/hooks/lib/spec-session-touch.cjs +100 -0
- package/src/codex/hooks/lib/spec-touch.cjs +3 -0
- package/src/codex/hooks/lib/spec-utils.cjs +6 -45
- package/src/codex/hooks/lib/update-check.cjs +1 -0
- package/src/codex/hooks/precompact.cjs +39 -0
- package/src/codex/hooks/privacy-approval.cjs +28 -17
- package/src/codex/hooks/privacy-block.cjs +9 -215
- package/src/codex/hooks/rules.cjs +4 -30
- package/src/codex/hooks/secret-output-guardrail.cjs +1 -1
- package/src/codex/hooks/session.cjs +47 -11
- package/src/codex/hooks/spec-gate.cjs +32 -74
- package/src/codex/hooks/spec-state.cjs +68 -46
- package/src/codex/hooks/state.cjs +25 -4
- package/src/codex/hooks.json +39 -7
- package/src/codex/rules/hook-protocols.md +13 -6
- package/src/common/AGENTS.md +4 -10
- package/src/omp/extensions/cafekit-bridge.mjs +12 -4
- package/src/omp/hooks/privacy-block.cjs +24 -206
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- package/src/claude/hooks/lib/context.cjs +0 -527
- package/src/claude/hooks/lib/detect.cjs +0 -474
- package/src/claude/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3914
- package/src/claude/skills/code-review/references/verification-gate.md +0 -41
- package/src/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- package/src/claude/skills/specs/templates/task.md +0 -47
- package/src/codex/rules/state-sync.md +0 -60
|
@@ -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
|
+
}));
|