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

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 (98) hide show
  1. package/bin/__tests__/change-firewall.semantic-firewall.test.js +2134 -0
  2. package/bin/__tests__/codex-hooks.test.js +531 -29
  3. package/bin/__tests__/codex-native.test.js +228 -7
  4. package/bin/__tests__/develop-contract.test.js +653 -185
  5. package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +342 -0
  6. package/bin/__tests__/installer-safety.test.js +39 -0
  7. package/bin/__tests__/opencode-spec-gate.test.js +24 -16
  8. package/bin/__tests__/package-inventory.test.js +705 -13
  9. package/bin/__tests__/release-preflight.semantic-firewall.test.js +578 -0
  10. package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +378 -0
  11. package/bin/__tests__/specs-v2-execution-closeout.test.js +468 -0
  12. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +665 -0
  13. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +1132 -0
  14. package/bin/__tests__/test-runtime-dependency-closure.cjs +182 -0
  15. package/bin/__tests__/validator-grounding.test.js +2013 -221
  16. package/bin/lib/managed-writer.js +12 -3
  17. package/bin/phases/codex-runtime.js +17 -1
  18. package/package.json +5 -2
  19. package/src/claude/CLAUDE.md +6 -0
  20. package/src/claude/agents/code-auditor.md +20 -0
  21. package/src/claude/agents/spec-maker.md +143 -99
  22. package/src/claude/hooks/__tests__/completion-authority.test.js +321 -58
  23. package/src/claude/hooks/__tests__/privacy-block.test.js +254 -0
  24. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +257 -0
  25. package/src/claude/hooks/__tests__/spec-gate.test.js +115 -40
  26. package/src/claude/hooks/completion-authority-check.cjs +37 -288
  27. package/src/claude/hooks/completion-authority-state.cjs +10 -8
  28. package/src/claude/hooks/completion-authority.cjs +13 -7
  29. package/src/claude/hooks/docs-sync.cjs +5 -2
  30. package/src/claude/hooks/lib/privacy-command-analysis.cjs +393 -0
  31. package/src/claude/hooks/lib/runtime-path-safety.cjs +119 -0
  32. package/src/claude/hooks/privacy-block.cjs +99 -28
  33. package/src/claude/hooks/semantic-review-authority.cjs +152 -0
  34. package/src/claude/hooks/spec-gate.cjs +47 -117
  35. package/src/claude/hooks/spec-state.cjs +8 -2
  36. package/src/claude/migration-manifest.json +9 -1
  37. package/src/claude/rules/state-sync.md +52 -21
  38. package/src/claude/rules/workflow.md +17 -9
  39. package/src/claude/scripts/change-firewall.cjs +1748 -0
  40. package/src/claude/scripts/spec-authoring-digest.cjs +151 -0
  41. package/src/claude/scripts/spec-authoring-validation.cjs +126 -0
  42. package/src/claude/scripts/spec-final-state.cjs +290 -0
  43. package/src/claude/scripts/spec-ground.cjs +571 -173
  44. package/src/claude/scripts/spec-readiness.cjs +425 -0
  45. package/src/claude/scripts/spec-receipt.cjs +162 -0
  46. package/src/claude/scripts/spec-resolver.cjs +67 -16
  47. package/src/claude/scripts/spec-scaffold.cjs +1594 -139
  48. package/src/claude/scripts/spec-semantic-model.cjs +396 -0
  49. package/src/claude/scripts/validate-spec-output.cjs +3110 -218
  50. package/src/claude/scripts/workflow-policy.cjs +501 -183
  51. package/src/claude/settings/settings.json +4 -0
  52. package/src/claude/skills/code-review/SKILL.md +12 -3
  53. package/src/claude/skills/code-review/references/spec-compliance-review.md +10 -2
  54. package/src/claude/skills/code-review/references/verification-gate.md +13 -0
  55. package/src/claude/skills/develop/SKILL.md +47 -60
  56. package/src/claude/skills/develop/references/parallel-waves.md +9 -6
  57. package/src/claude/skills/develop/references/quality-gate.md +19 -12
  58. package/src/claude/skills/specs/SKILL.md +183 -177
  59. package/src/claude/skills/specs/references/ask-user-question-gates.md +27 -18
  60. package/src/claude/skills/specs/references/cross-spec-dependency.md +8 -4
  61. package/src/claude/skills/specs/references/research-strategy.md +4 -1
  62. package/src/claude/skills/specs/references/review.md +214 -262
  63. package/src/claude/skills/specs/references/scope-inquiry.md +18 -5
  64. package/src/claude/skills/specs/rules/design-discovery-full.md +33 -81
  65. package/src/claude/skills/specs/rules/design-discovery-light.md +21 -42
  66. package/src/claude/skills/specs/rules/design-principles.md +136 -180
  67. package/src/claude/skills/specs/rules/design-review.md +42 -97
  68. package/src/claude/skills/specs/rules/ears-format.md +42 -50
  69. package/src/claude/skills/specs/rules/phase-decision-matrix.md +42 -41
  70. package/src/claude/skills/specs/rules/task-scoring-rubric.md +2 -1
  71. package/src/claude/skills/specs/rules/tasks-generation.md +189 -300
  72. package/src/claude/skills/specs/templates/design.md +49 -263
  73. package/src/claude/skills/specs/templates/requirements.md +26 -36
  74. package/src/claude/skills/specs/templates/research.md +9 -98
  75. package/src/claude/skills/specs/templates/spec-state.json +35 -85
  76. package/src/claude/skills/specs/templates/task.md +30 -102
  77. package/src/claude/skills/sync/SKILL.md +33 -10
  78. package/src/claude/skills/sync/references/sync-protocols.md +31 -13
  79. package/src/claude/skills/test/SKILL.md +17 -7
  80. package/src/codex/AGENTS.md +6 -0
  81. package/src/codex/hooks/completion-authority-check.cjs +43 -240
  82. package/src/codex/hooks/completion-authority-state.cjs +10 -8
  83. package/src/codex/hooks/completion-authority.cjs +13 -7
  84. package/src/codex/hooks/lib/hook-context.cjs +16 -2
  85. package/src/codex/hooks/lib/privacy-command-analysis.cjs +3 -0
  86. package/src/codex/hooks/lib/privacy-state.cjs +61 -38
  87. package/src/codex/hooks/lib/runtime-path-safety.cjs +3 -0
  88. package/src/codex/hooks/lib/spec-receipt.cjs +72 -134
  89. package/src/codex/hooks/lib/spec-utils.cjs +56 -5
  90. package/src/codex/hooks/privacy-approval.cjs +6 -2
  91. package/src/codex/hooks/privacy-block.cjs +94 -15
  92. package/src/codex/hooks/semantic-review-authority.cjs +130 -0
  93. package/src/codex/hooks/spec-gate.cjs +49 -34
  94. package/src/codex/hooks/spec-state.cjs +10 -3
  95. package/src/codex/hooks/state.cjs +2 -0
  96. package/src/codex/hooks.json +6 -1
  97. package/src/codex/rules/state-sync.md +47 -27
  98. package/src/common/AGENTS.md +1 -1
@@ -0,0 +1,182 @@
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
+
8
+ const CLAUDE_RUNTIME_ENTRYPOINTS = Object.freeze([
9
+ 'scripts/spec-final-state.cjs',
10
+ 'scripts/spec-receipt.cjs',
11
+ 'scripts/spec-resolver.cjs',
12
+ 'scripts/validate-spec-output.cjs',
13
+ ]);
14
+
15
+ function inside(root, target) {
16
+ const relative = path.relative(path.resolve(root), path.resolve(target));
17
+ return relative === '' || (relative !== '..'
18
+ && !relative.startsWith(`..${path.sep}`)
19
+ && !path.isAbsolute(relative));
20
+ }
21
+
22
+ function assertRegularSource(root, target) {
23
+ if (!inside(root, target)) throw new Error(`runtime dependency escapes source root: ${target}`);
24
+ const relative = path.relative(root, target);
25
+ let cursor = root;
26
+ for (const part of relative.split(path.sep).filter(Boolean)) {
27
+ cursor = path.join(cursor, part);
28
+ const stat = fs.lstatSync(cursor);
29
+ if (stat.isSymbolicLink()) throw new Error(`runtime dependency cannot be a symlink: ${cursor}`);
30
+ }
31
+ if (!fs.lstatSync(target).isFile()) throw new Error(`runtime dependency is not a regular file: ${target}`);
32
+ const canonical = fs.realpathSync(target);
33
+ if (!inside(fs.realpathSync(root), canonical)) {
34
+ throw new Error(`runtime dependency canonical path escapes source root: ${target}`);
35
+ }
36
+ return canonical;
37
+ }
38
+
39
+ function resolveDependency(sourceRoot, sourceFile, request) {
40
+ const unresolved = path.resolve(path.dirname(sourceFile), request);
41
+ if (!inside(sourceRoot, unresolved)) {
42
+ throw new Error(`runtime dependency escapes source root: ${sourceFile} -> ${request}`);
43
+ }
44
+ const candidates = path.extname(unresolved)
45
+ ? [unresolved]
46
+ : [unresolved, `${unresolved}.cjs`, `${unresolved}.js`, `${unresolved}.json`,
47
+ path.join(unresolved, 'index.cjs'), path.join(unresolved, 'index.js')];
48
+ for (const candidate of candidates) {
49
+ try { return assertRegularSource(sourceRoot, candidate); }
50
+ catch (error) {
51
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') continue;
52
+ throw error;
53
+ }
54
+ }
55
+ throw new Error(`runtime dependency is missing: ${sourceFile} -> ${request}`);
56
+ }
57
+
58
+ function relativeRequires(sourceFile) {
59
+ if (path.extname(sourceFile) === '.json') return [];
60
+ const source = fs.readFileSync(sourceFile, 'utf8');
61
+ if (/\brequire\s*\(\s*path\.(?:join|resolve)\s*\(\s*__dirname\b/.test(source)) {
62
+ throw new Error(`dynamic local require is not allowed: ${sourceFile}`);
63
+ }
64
+ const localBindings = new Set();
65
+ for (const match of source.matchAll(
66
+ /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:String\s*\(\s*)?(['"])(\.\.?\/[^'"]*)\2\s*\)?/g,
67
+ )) localBindings.add(match[1]);
68
+ const requests = new Set();
69
+ for (const match of source.matchAll(/\brequire\s*\(\s*([^()\r\n]+?)\s*\)/g)) {
70
+ const expression = match[1].trim();
71
+ const literal = expression.match(/^(['"])([^'"]+)\1$/);
72
+ if (literal) {
73
+ if (literal[2].startsWith('.')) requests.add(literal[2]);
74
+ continue;
75
+ }
76
+ const identifier = expression.match(/^[A-Za-z_$][\w$]*$/)?.[0];
77
+ const visiblyLocal = /^(['"])(\.\.?\/)/.test(expression)
78
+ || /^`\.\.?\//.test(expression)
79
+ || (identifier && localBindings.has(identifier));
80
+ if (visiblyLocal) throw new Error(`dynamic local require is not allowed: ${sourceFile}`);
81
+ }
82
+ return [...requests].sort();
83
+ }
84
+
85
+ function copyCommonJsDependencyClosure({ sourceRoot, destinationRoot, entrypoints }) {
86
+ const requestedSource = path.resolve(sourceRoot);
87
+ const destination = path.resolve(destinationRoot);
88
+ const sourceStat = fs.lstatSync(requestedSource);
89
+ if (sourceStat.isSymbolicLink() || !sourceStat.isDirectory()) {
90
+ throw new Error(`source root must be a regular directory: ${requestedSource}`);
91
+ }
92
+ const source = fs.realpathSync(requestedSource);
93
+ if (!Array.isArray(entrypoints) || entrypoints.length === 0) {
94
+ throw new Error('at least one runtime entrypoint is required');
95
+ }
96
+ const pending = entrypoints.map((entrypoint) => {
97
+ if (typeof entrypoint !== 'string' || path.isAbsolute(entrypoint)) {
98
+ throw new Error(`runtime entrypoint must be relative: ${entrypoint}`);
99
+ }
100
+ return resolveDependency(source, path.join(source, '__entrypoint__.cjs'), `./${entrypoint}`);
101
+ }).sort();
102
+ const closure = new Set();
103
+ while (pending.length > 0) {
104
+ const sourceFile = pending.shift();
105
+ if (closure.has(sourceFile)) continue;
106
+ closure.add(sourceFile);
107
+ for (const request of relativeRequires(sourceFile)) {
108
+ const dependency = resolveDependency(source, sourceFile, request);
109
+ if (!closure.has(dependency)) pending.push(dependency);
110
+ }
111
+ pending.sort();
112
+ }
113
+ const files = [...closure].map((file) => path.relative(source, file)).sort();
114
+ for (const relative of files) {
115
+ const target = path.resolve(destination, relative);
116
+ if (!inside(destination, target)) throw new Error(`runtime destination escapes fixture: ${relative}`);
117
+ fs.mkdirSync(path.dirname(target), { recursive: true });
118
+ fs.copyFileSync(path.join(source, relative), target);
119
+ }
120
+ return files;
121
+ }
122
+
123
+ function copyClaudeTestRuntime(packageRoot, destinationRoot, entrypoints = CLAUDE_RUNTIME_ENTRYPOINTS) {
124
+ return copyCommonJsDependencyClosure({
125
+ sourceRoot: path.join(packageRoot, 'src/claude'),
126
+ destinationRoot,
127
+ entrypoints,
128
+ });
129
+ }
130
+
131
+ module.exports = { copyCommonJsDependencyClosure, copyClaudeTestRuntime };
132
+
133
+ if (require.main === module) {
134
+ const { test } = require('node:test');
135
+ function fixture(run) {
136
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-runtime-closure-'));
137
+ const source = path.join(root, 'source');
138
+ const destination = path.join(root, 'destination');
139
+ fs.mkdirSync(source);
140
+ try { run({ root, source, destination }); }
141
+ finally { fs.rmSync(root, { recursive: true, force: true }); }
142
+ }
143
+
144
+ test('copies a deterministic transitive closure and preserves layout', () => fixture(({ source, destination }) => {
145
+ fs.mkdirSync(path.join(source, 'nested'));
146
+ fs.writeFileSync(path.join(source, 'entry.cjs'), "require('./nested/one.cjs');\n");
147
+ fs.writeFileSync(path.join(source, 'nested/one.cjs'), "require('../two');\n");
148
+ fs.writeFileSync(path.join(source, 'two.cjs'), 'module.exports = 2;\n');
149
+ const files = copyCommonJsDependencyClosure({ sourceRoot: source, destinationRoot: destination, entrypoints: ['entry.cjs'] });
150
+ assert.deepEqual(files, ['entry.cjs', 'nested/one.cjs', 'two.cjs']);
151
+ assert.equal(fs.readFileSync(path.join(destination, 'nested/one.cjs'), 'utf8'), "require('../two');\n");
152
+ }));
153
+
154
+ test('fails closed for missing dependencies', () => fixture(({ source, destination }) => {
155
+ fs.writeFileSync(path.join(source, 'entry.cjs'), "require('./missing.cjs');\n");
156
+ assert.throws(() => copyCommonJsDependencyClosure({ sourceRoot: source, destinationRoot: destination, entrypoints: ['entry.cjs'] }), /dependency is missing/);
157
+ assert.equal(fs.existsSync(destination), false);
158
+ }));
159
+
160
+ test('fails closed for symlinks and source-root escapes', () => fixture(({ root, source, destination }) => {
161
+ const outside = path.join(root, 'outside.cjs');
162
+ fs.writeFileSync(outside, 'module.exports = true;\n');
163
+ fs.symlinkSync(outside, path.join(source, 'linked.cjs'));
164
+ fs.writeFileSync(path.join(source, 'entry.cjs'), "require('./linked.cjs');\n");
165
+ const copy = (entrypoints) => copyCommonJsDependencyClosure({ sourceRoot: source, destinationRoot: destination, entrypoints });
166
+ assert.throws(() => copy(['entry.cjs']), /cannot be a symlink/);
167
+ assert.throws(() => copy(['../outside.cjs']), /escapes source root/);
168
+ }));
169
+
170
+ test('fails closed for dynamic local requires', () => fixture(({ source, destination }) => {
171
+ fs.writeFileSync(path.join(source, 'dependency.cjs'), 'module.exports = true;\n');
172
+ const copy = () => copyCommonJsDependencyClosure({ sourceRoot: source, destinationRoot: destination, entrypoints: ['entry.cjs'] });
173
+ for (const body of [
174
+ "const target = './dependency.cjs';\nrequire(target);\n",
175
+ "const path = require('node:path');\nrequire(path.join(__dirname, 'dependency.cjs'));\n",
176
+ "const request = String('./dependency.cjs');\nrequire(request);\n",
177
+ ]) {
178
+ fs.writeFileSync(path.join(source, 'entry.cjs'), body);
179
+ assert.throws(copy, /dynamic local require/);
180
+ }
181
+ }));
182
+ }