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