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

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 (91) hide show
  1. package/bin/__tests__/codex-hooks.test.js +444 -14
  2. package/bin/__tests__/codex-native.test.js +228 -7
  3. package/bin/__tests__/develop-contract.test.js +615 -185
  4. package/bin/__tests__/installer-safety.test.js +39 -0
  5. package/bin/__tests__/opencode-spec-gate.test.js +24 -16
  6. package/bin/__tests__/package-inventory.test.js +687 -13
  7. package/bin/__tests__/specs-v2-execution-closeout.test.js +421 -0
  8. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +601 -0
  9. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +952 -0
  10. package/bin/__tests__/test-runtime-dependency-closure.cjs +182 -0
  11. package/bin/__tests__/validator-grounding.test.js +1914 -221
  12. package/bin/lib/managed-writer.js +12 -3
  13. package/bin/phases/codex-runtime.js +17 -1
  14. package/package.json +1 -1
  15. package/src/claude/CLAUDE.md +6 -0
  16. package/src/claude/agents/code-auditor.md +20 -0
  17. package/src/claude/agents/spec-maker.md +143 -99
  18. package/src/claude/hooks/__tests__/completion-authority.test.js +291 -58
  19. package/src/claude/hooks/__tests__/privacy-block.test.js +172 -0
  20. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +257 -0
  21. package/src/claude/hooks/__tests__/spec-gate.test.js +115 -40
  22. package/src/claude/hooks/completion-authority-check.cjs +37 -288
  23. package/src/claude/hooks/completion-authority-state.cjs +10 -8
  24. package/src/claude/hooks/completion-authority.cjs +13 -7
  25. package/src/claude/hooks/docs-sync.cjs +5 -2
  26. package/src/claude/hooks/lib/privacy-command-analysis.cjs +393 -0
  27. package/src/claude/hooks/lib/runtime-path-safety.cjs +119 -0
  28. package/src/claude/hooks/privacy-block.cjs +77 -30
  29. package/src/claude/hooks/semantic-review-authority.cjs +152 -0
  30. package/src/claude/hooks/spec-gate.cjs +47 -117
  31. package/src/claude/hooks/spec-state.cjs +8 -2
  32. package/src/claude/migration-manifest.json +9 -1
  33. package/src/claude/rules/state-sync.md +52 -21
  34. package/src/claude/rules/workflow.md +17 -9
  35. package/src/claude/scripts/spec-final-state.cjs +244 -0
  36. package/src/claude/scripts/spec-ground.cjs +535 -173
  37. package/src/claude/scripts/spec-readiness.cjs +118 -0
  38. package/src/claude/scripts/spec-receipt.cjs +162 -0
  39. package/src/claude/scripts/spec-resolver.cjs +67 -16
  40. package/src/claude/scripts/spec-scaffold.cjs +1354 -139
  41. package/src/claude/scripts/spec-semantic-model.cjs +396 -0
  42. package/src/claude/scripts/validate-spec-output.cjs +2782 -142
  43. package/src/claude/scripts/workflow-policy.cjs +501 -183
  44. package/src/claude/settings/settings.json +4 -0
  45. package/src/claude/skills/code-review/SKILL.md +12 -3
  46. package/src/claude/skills/code-review/references/spec-compliance-review.md +10 -2
  47. package/src/claude/skills/code-review/references/verification-gate.md +13 -0
  48. package/src/claude/skills/develop/SKILL.md +47 -60
  49. package/src/claude/skills/develop/references/parallel-waves.md +9 -6
  50. package/src/claude/skills/develop/references/quality-gate.md +19 -12
  51. package/src/claude/skills/specs/SKILL.md +183 -177
  52. package/src/claude/skills/specs/references/ask-user-question-gates.md +27 -18
  53. package/src/claude/skills/specs/references/cross-spec-dependency.md +8 -4
  54. package/src/claude/skills/specs/references/research-strategy.md +4 -1
  55. package/src/claude/skills/specs/references/review.md +200 -260
  56. package/src/claude/skills/specs/references/scope-inquiry.md +18 -5
  57. package/src/claude/skills/specs/rules/design-discovery-full.md +33 -81
  58. package/src/claude/skills/specs/rules/design-discovery-light.md +21 -42
  59. package/src/claude/skills/specs/rules/design-principles.md +136 -180
  60. package/src/claude/skills/specs/rules/design-review.md +42 -97
  61. package/src/claude/skills/specs/rules/ears-format.md +42 -50
  62. package/src/claude/skills/specs/rules/phase-decision-matrix.md +42 -41
  63. package/src/claude/skills/specs/rules/task-scoring-rubric.md +2 -1
  64. package/src/claude/skills/specs/rules/tasks-generation.md +189 -300
  65. package/src/claude/skills/specs/templates/design.md +49 -263
  66. package/src/claude/skills/specs/templates/requirements.md +26 -36
  67. package/src/claude/skills/specs/templates/research.md +9 -98
  68. package/src/claude/skills/specs/templates/spec-state.json +23 -85
  69. package/src/claude/skills/specs/templates/task.md +30 -102
  70. package/src/claude/skills/sync/SKILL.md +33 -10
  71. package/src/claude/skills/sync/references/sync-protocols.md +31 -13
  72. package/src/claude/skills/test/SKILL.md +17 -7
  73. package/src/codex/AGENTS.md +6 -0
  74. package/src/codex/hooks/completion-authority-check.cjs +43 -240
  75. package/src/codex/hooks/completion-authority-state.cjs +10 -8
  76. package/src/codex/hooks/completion-authority.cjs +13 -7
  77. package/src/codex/hooks/lib/hook-context.cjs +16 -2
  78. package/src/codex/hooks/lib/privacy-command-analysis.cjs +3 -0
  79. package/src/codex/hooks/lib/privacy-state.cjs +38 -16
  80. package/src/codex/hooks/lib/runtime-path-safety.cjs +3 -0
  81. package/src/codex/hooks/lib/spec-receipt.cjs +72 -134
  82. package/src/codex/hooks/lib/spec-utils.cjs +56 -5
  83. package/src/codex/hooks/privacy-approval.cjs +1 -1
  84. package/src/codex/hooks/privacy-block.cjs +67 -11
  85. package/src/codex/hooks/semantic-review-authority.cjs +130 -0
  86. package/src/codex/hooks/spec-gate.cjs +49 -34
  87. package/src/codex/hooks/spec-state.cjs +10 -3
  88. package/src/codex/hooks/state.cjs +2 -0
  89. package/src/codex/hooks.json +6 -1
  90. package/src/codex/rules/state-sync.md +47 -27
  91. 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
+ }