@haposoft/cafekit 0.16.0-rc.1 → 0.16.0-rc.3
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 +355 -12
- package/bin/__tests__/codex-native.test.js +1 -1
- package/bin/__tests__/develop-contract.test.js +1374 -78
- package/bin/__tests__/installer-safety.test.js +43 -29
- package/bin/__tests__/opencode-spec-gate.test.js +791 -0
- package/bin/__tests__/package-inventory.test.js +157 -0
- package/bin/__tests__/validator-grounding.test.js +707 -2
- package/package.json +1 -1
- package/src/claude/agents/code-auditor.md +8 -1
- package/src/claude/agents/spec-maker.md +107 -200
- package/src/claude/hooks/__tests__/completion-authority.test.js +720 -0
- package/src/claude/hooks/__tests__/spec-gate.test.js +511 -38
- package/src/claude/hooks/completion-authority-check.cjs +307 -0
- package/src/claude/hooks/completion-authority-state.cjs +525 -0
- package/src/claude/hooks/completion-authority.cjs +148 -0
- package/src/claude/hooks/session.cjs +33 -19
- package/src/claude/hooks/spec-gate.cjs +173 -61
- package/src/claude/hooks/spec-state.cjs +104 -45
- package/src/claude/migration-manifest.json +6 -1
- package/src/claude/scripts/provenance.cjs +315 -0
- package/src/claude/scripts/spec-resolver.cjs +450 -0
- package/src/claude/scripts/spec-scaffold.cjs +716 -41
- package/src/claude/scripts/validate-spec-output.cjs +355 -53
- package/src/claude/scripts/workflow-policy.cjs +1529 -162
- package/src/claude/settings/settings.json +8 -0
- package/src/claude/skills/backend-development/references/backend-security.md +52 -0
- package/src/claude/skills/code-review/SKILL.md +85 -66
- package/src/claude/skills/develop/SKILL.md +153 -285
- package/src/claude/skills/develop/references/quality-gate.md +112 -124
- package/src/claude/skills/develop/references/subagent-patterns.md +42 -37
- package/src/claude/skills/specs/SKILL.md +170 -381
- package/src/claude/skills/specs/rules/task-scoring-rubric.md +45 -46
- package/src/claude/skills/specs/rules/tasks-generation.md +49 -20
- package/src/claude/skills/specs/templates/spec-state.json +23 -2
- package/src/claude/skills/specs/templates/task.md +20 -0
- package/src/claude/skills/sync/SKILL.md +15 -8
- package/src/claude/skills/sync/references/sync-protocols.md +4 -4
- package/src/claude/skills/test/SKILL.md +141 -167
- package/src/codex/hooks/completion-authority-check.cjs +260 -0
- package/src/codex/hooks/completion-authority-state.cjs +525 -0
- package/src/codex/hooks/completion-authority.cjs +100 -0
- package/src/codex/hooks/lib/hook-context.cjs +42 -2
- package/src/codex/hooks/lib/spec-receipt.cjs +76 -43
- package/src/codex/hooks/lib/spec-utils.cjs +53 -27
- package/src/codex/hooks/session.cjs +2 -0
- package/src/codex/hooks/spec-gate.cjs +132 -20
- package/src/codex/hooks/spec-state.cjs +56 -10
- package/src/codex/hooks.json +10 -0
- package/src/opencode/AGENTS.md +4 -2
- package/src/opencode/plugins/spec-gate.ts +296 -122
- package/src/opencode/plugins/spec-state.ts +183 -116
|
@@ -13,6 +13,7 @@ const REQUIRED_PAYLOAD = [
|
|
|
13
13
|
'src/claude/migration-manifest.json',
|
|
14
14
|
'src/claude/scripts/scan-staged-secrets.cjs',
|
|
15
15
|
'src/claude/scripts/workflow-policy.cjs',
|
|
16
|
+
'src/claude/scripts/provenance.cjs',
|
|
16
17
|
];
|
|
17
18
|
const FORBIDDEN_PAYLOAD = [
|
|
18
19
|
/(^|\/)\.logs(\/|$)/,
|
|
@@ -84,6 +85,136 @@ function runInstaller(installer, root, platforms, lang) {
|
|
|
84
85
|
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
function createProvenanceFixture(root) {
|
|
89
|
+
const feature = 'installer-provenance';
|
|
90
|
+
const specFile = path.join('specs', feature, 'spec.json');
|
|
91
|
+
fs.mkdirSync(path.join(root, path.dirname(specFile)), { recursive: true });
|
|
92
|
+
fs.writeFileSync(
|
|
93
|
+
path.join(root, specFile),
|
|
94
|
+
JSON.stringify({ feature_name: feature, status: 'in_progress' }) + '\n'
|
|
95
|
+
);
|
|
96
|
+
fs.writeFileSync(path.join(root, 'tracked-fixture.txt'), 'tracked fixture\n');
|
|
97
|
+
|
|
98
|
+
for (const args of [
|
|
99
|
+
['init', '-q'],
|
|
100
|
+
['add', '--', 'tracked-fixture.txt', specFile],
|
|
101
|
+
['-c', 'user.name=CafeKit Fixture', '-c', 'user.email=cafekit-fixture@example.invalid', 'commit', '--no-gpg-sign', '-qm', 'provenance fixture'],
|
|
102
|
+
]) {
|
|
103
|
+
const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
|
|
104
|
+
assert.equal(result.status, 0, `${args.join(' ')}\n${result.stdout}\n${result.stderr}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
specsRoot: 'specs',
|
|
109
|
+
specFile,
|
|
110
|
+
feature,
|
|
111
|
+
session: `package-${path.basename(root)}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function provenanceCliArgs(helper, root, fixture) {
|
|
116
|
+
return [
|
|
117
|
+
helper,
|
|
118
|
+
'--json',
|
|
119
|
+
'--project-root', root,
|
|
120
|
+
'--specs-root', fixture.specsRoot,
|
|
121
|
+
'--spec-file', fixture.specFile,
|
|
122
|
+
'--feature-name', fixture.feature,
|
|
123
|
+
'--session', fixture.session,
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function assertInstalledProvenance(root, platform, fixture) {
|
|
128
|
+
const scripts = path.join(root, RUNTIMES[platform].root, 'scripts');
|
|
129
|
+
const helper = path.join(scripts, 'provenance.cjs');
|
|
130
|
+
const policy = path.join(scripts, 'workflow-policy.cjs');
|
|
131
|
+
const resolver = path.join(scripts, 'spec-resolver.cjs');
|
|
132
|
+
const gate = path.join(
|
|
133
|
+
root,
|
|
134
|
+
platform === 'claude' ? '.claude/hooks/spec-gate.cjs' : '.codex/hooks/spec-gate.cjs'
|
|
135
|
+
);
|
|
136
|
+
const authorityDir = path.join(root, platform === 'claude' ? '.claude/hooks' : '.codex/hooks');
|
|
137
|
+
const authority = path.join(authorityDir, 'completion-authority.cjs');
|
|
138
|
+
const authorityCheck = path.join(authorityDir, 'completion-authority-check.cjs');
|
|
139
|
+
const authorityState = path.join(authorityDir, 'completion-authority-state.cjs');
|
|
140
|
+
for (const file of [helper, policy, resolver]) {
|
|
141
|
+
assert.equal(fs.existsSync(file), true, `${platform} installed file missing: ${file}`);
|
|
142
|
+
}
|
|
143
|
+
assert.equal(fs.existsSync(gate), true, `${platform} installed gate missing: ${gate}`);
|
|
144
|
+
|
|
145
|
+
for (const file of [authority, authorityCheck, authorityState]) {
|
|
146
|
+
assert.equal(fs.existsSync(file), true, `${platform} installed completion authority missing: ${file}`);
|
|
147
|
+
}
|
|
148
|
+
const authorityCheckBytes = fs.readFileSync(authorityCheck);
|
|
149
|
+
fs.writeFileSync(authorityCheck, 'module.exports = {};\n');
|
|
150
|
+
const malformedAuthority = spawnSync(process.execPath, [authority, '--stop'], {
|
|
151
|
+
cwd: root,
|
|
152
|
+
env: { ...process.env, PROJECT_ROOT: root },
|
|
153
|
+
input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
|
|
154
|
+
encoding: 'utf8',
|
|
155
|
+
});
|
|
156
|
+
assert.equal(malformedAuthority.status, 0, `${platform} malformed authority must fail closed`);
|
|
157
|
+
assert.equal(JSON.parse(malformedAuthority.stdout).decision, 'block');
|
|
158
|
+
fs.writeFileSync(authorityCheck, authorityCheckBytes);
|
|
159
|
+
fs.rmSync(authorityCheck);
|
|
160
|
+
const missingAuthority = spawnSync(process.execPath, [authority, '--stop'], {
|
|
161
|
+
cwd: root,
|
|
162
|
+
env: { ...process.env, PROJECT_ROOT: root },
|
|
163
|
+
input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
|
|
164
|
+
encoding: 'utf8',
|
|
165
|
+
});
|
|
166
|
+
assert.equal(missingAuthority.status, 0, `${platform} missing authority dependency must fail closed`);
|
|
167
|
+
assert.equal(JSON.parse(missingAuthority.stdout).decision, 'block');
|
|
168
|
+
fs.writeFileSync(authorityCheck, authorityCheckBytes);
|
|
169
|
+
|
|
170
|
+
const helperRun = spawnSync(process.execPath, provenanceCliArgs(helper, root, fixture), {
|
|
171
|
+
cwd: root,
|
|
172
|
+
encoding: 'utf8',
|
|
173
|
+
});
|
|
174
|
+
assert.equal(helperRun.status, 0, `${helperRun.stdout}\n${helperRun.stderr}`);
|
|
175
|
+
const output = JSON.parse(helperRun.stdout);
|
|
176
|
+
assert.equal(output.ok, true);
|
|
177
|
+
assert.match(output.Base, /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/);
|
|
178
|
+
assert.match(output.Head, /^[a-f0-9]{64}$/);
|
|
179
|
+
assert.match(output.context_id, /^[a-f0-9]{64}$/);
|
|
180
|
+
assert.equal(output.context.runtime_session, fixture.session);
|
|
181
|
+
|
|
182
|
+
const policyLoad = spawnSync(process.execPath, [policy, '--json'], {
|
|
183
|
+
cwd: root,
|
|
184
|
+
encoding: 'utf8',
|
|
185
|
+
});
|
|
186
|
+
assert.equal(policyLoad.status, 0, `${policyLoad.stdout}\n${policyLoad.stderr}`);
|
|
187
|
+
assert.equal(JSON.parse(policyLoad.stdout).ok, true);
|
|
188
|
+
|
|
189
|
+
const gateInput = JSON.stringify({
|
|
190
|
+
cwd: root,
|
|
191
|
+
session_id: fixture.session,
|
|
192
|
+
featureName: fixture.feature,
|
|
193
|
+
});
|
|
194
|
+
const assertControlledBlock = (label) => {
|
|
195
|
+
const gateRun = spawnSync(process.execPath, [gate], {
|
|
196
|
+
cwd: root,
|
|
197
|
+
input: `${gateInput}\n`,
|
|
198
|
+
encoding: 'utf8',
|
|
199
|
+
});
|
|
200
|
+
assert.equal(gateRun.status, 0, `${label}: ${gateRun.stdout}\n${gateRun.stderr}`);
|
|
201
|
+
const decision = JSON.parse(gateRun.stdout);
|
|
202
|
+
assert.equal(decision.decision, 'block', `${label}: ${gateRun.stdout}`);
|
|
203
|
+
assert.equal(decision.ok, undefined, `${label}: ${gateRun.stdout}`);
|
|
204
|
+
assert.match(decision.reason, /unavailable|shared workflow policy|provenance|helper/i);
|
|
205
|
+
assert.doesNotMatch(gateRun.stdout, /"decision"\s*:\s*"allow"|"ok"\s*:\s*true/);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const helperBytes = fs.readFileSync(helper);
|
|
209
|
+
fs.rmSync(helper);
|
|
210
|
+
assertControlledBlock('missing provenance helper');
|
|
211
|
+
|
|
212
|
+
fs.writeFileSync(helper, 'module.exports = {;\n');
|
|
213
|
+
assertControlledBlock('malformed provenance helper');
|
|
214
|
+
|
|
215
|
+
fs.writeFileSync(helper, helperBytes);
|
|
216
|
+
}
|
|
217
|
+
|
|
87
218
|
function managedBlock(content, start, end) {
|
|
88
219
|
const startIndex = content.indexOf(start);
|
|
89
220
|
const endIndex = content.indexOf(end);
|
|
@@ -256,3 +387,29 @@ test('packed tarball installer matrix proves locale, transforms, paths, and reru
|
|
|
256
387
|
fs.rmSync(root, { recursive: true, force: true });
|
|
257
388
|
}
|
|
258
389
|
});
|
|
390
|
+
|
|
391
|
+
test('packed Claude and Codex installs self-contain runtime provenance and fail closed without it', () => {
|
|
392
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-provenance-package-'));
|
|
393
|
+
const destination = path.join(root, 'pack');
|
|
394
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
395
|
+
try {
|
|
396
|
+
const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
|
|
397
|
+
const tarball = path.join(destination, packed.filename);
|
|
398
|
+
assertCleanInventory(packedInventory(tarball));
|
|
399
|
+
|
|
400
|
+
for (const platform of ['claude', 'codex']) {
|
|
401
|
+
const project = path.join(root, platform);
|
|
402
|
+
const installer = installPacked(tarball, project);
|
|
403
|
+
runInstaller(installer, project, [platform], null);
|
|
404
|
+
|
|
405
|
+
const packedSource = path.resolve(path.dirname(installer), '..', 'src');
|
|
406
|
+
fs.rmSync(packedSource, { recursive: true, force: true });
|
|
407
|
+
assert.equal(fs.existsSync(packedSource), false, 'installed gate must not need package source fallback');
|
|
408
|
+
|
|
409
|
+
const fixture = createProvenanceFixture(project);
|
|
410
|
+
assertInstalledProvenance(project, platform, fixture);
|
|
411
|
+
}
|
|
412
|
+
} finally {
|
|
413
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
414
|
+
}
|
|
415
|
+
});
|