@haposoft/cafekit 0.16.7 → 0.18.0

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 (193) hide show
  1. package/README.md +14 -22
  2. package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
  3. package/bin/__tests__/codex-hooks.test.js +0 -36
  4. package/bin/__tests__/codex-native.test.js +46 -88
  5. package/bin/__tests__/develop-contract.test.js +26 -57
  6. package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
  7. package/bin/__tests__/grok-envelope.test.js +0 -56
  8. package/bin/__tests__/hook-payload.test.js +4 -6
  9. package/bin/__tests__/legacy-authority-decoupled.test.js +159 -0
  10. package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
  11. package/bin/__tests__/omp-bridge.test.js +20 -17
  12. package/bin/__tests__/omp-hooks.test.js +0 -19
  13. package/bin/__tests__/optional-skill-inventory.test.js +112 -4
  14. package/bin/__tests__/orca-session.test.js +0 -2
  15. package/bin/__tests__/package-inventory.test.js +100 -541
  16. package/bin/__tests__/runtime-dir.test.js +0 -3
  17. package/bin/__tests__/runtime-schema.test.js +3 -2
  18. package/bin/__tests__/skill-routing-source.test.js +27 -15
  19. package/bin/__tests__/spec-narrowing.test.js +0 -27
  20. package/bin/__tests__/specs-v2-execution-closeout.test.js +4 -69
  21. package/bin/__tests__/ui-ux-skill.test.js +101 -0
  22. package/bin/__tests__/usage-hook-retired.test.js +114 -0
  23. package/bin/__tests__/validator-grounding.test.js +9 -28
  24. package/bin/phases/codex-runtime.js +24 -0
  25. package/bin/phases/omp-runtime.js +22 -0
  26. package/package.json +1 -1
  27. package/src/claude/CLAUDE.md +3 -10
  28. package/src/claude/agents/brainstormer.md +7 -3
  29. package/src/claude/agents/code-auditor.md +35 -50
  30. package/src/claude/agents/debugger.md +5 -79
  31. package/src/claude/agents/deployer.md +3 -5
  32. package/src/claude/agents/docs-keeper.md +2 -7
  33. package/src/claude/agents/implementer.md +6 -9
  34. package/src/claude/agents/inspector.md +2 -3
  35. package/src/claude/agents/project-manager.md +7 -15
  36. package/src/claude/agents/researcher.md +9 -3
  37. package/src/claude/agents/spec-maker.md +11 -11
  38. package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
  39. package/src/claude/hooks/__tests__/spec-gate.test.js +74 -3
  40. package/src/claude/hooks/__tests__/spec-state-touched.test.js +134 -0
  41. package/src/claude/hooks/__tests__/state.test.js +68 -0
  42. package/src/claude/hooks/__tests__/statusline.test.js +140 -11
  43. package/src/claude/hooks/agent.cjs +2 -1
  44. package/src/claude/hooks/lib/context.cjs +0 -89
  45. package/src/claude/hooks/lib/hook-payload.cjs +2 -2
  46. package/src/claude/hooks/rules.cjs +2 -1
  47. package/src/claude/hooks/session.cjs +0 -1
  48. package/src/claude/hooks/spec-gate.cjs +17 -28
  49. package/src/claude/hooks/spec-state.cjs +95 -1
  50. package/src/claude/hooks/state.cjs +21 -2
  51. package/src/claude/migration-manifest.json +30 -15
  52. package/src/claude/rules/ai-dev-rules.md +1 -1
  53. package/src/claude/rules/hook-protocols.md +8 -15
  54. package/src/claude/rules/manage-docs.md +9 -23
  55. package/src/claude/rules/orchestrator.md +3 -4
  56. package/src/claude/rules/review-audit-self-decision.md +3 -3
  57. package/src/claude/rules/skill-domain-routing.md +1 -3
  58. package/src/claude/rules/skill-workflow-routing.md +4 -5
  59. package/src/claude/rules/state-sync.md +3 -12
  60. package/src/claude/rules/workflow.md +10 -18
  61. package/src/claude/runtime.schema.json +5 -3
  62. package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
  63. package/src/claude/scripts/provenance.cjs +23 -5
  64. package/src/claude/scripts/spec-receipt.cjs +24 -7
  65. package/src/claude/scripts/spec-resolver.cjs +1 -2
  66. package/src/claude/scripts/validate-spec-output.cjs +5 -32
  67. package/src/claude/settings/settings.json +1 -32
  68. package/src/claude/skills/{question → ask}/SKILL.md +9 -75
  69. package/src/claude/skills/brainstorm/SKILL.md +43 -131
  70. package/src/claude/skills/code-review/SKILL.md +48 -17
  71. package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
  72. package/src/claude/skills/code-review/references/verification-gate.md +22 -27
  73. package/src/claude/skills/debug/SKILL.md +29 -149
  74. package/src/claude/skills/develop/SKILL.md +27 -36
  75. package/src/claude/skills/develop/references/parallel-waves.md +0 -8
  76. package/src/claude/skills/develop/references/quality-gate.md +15 -13
  77. package/src/claude/skills/docs/SKILL.md +2 -2
  78. package/src/claude/skills/docs/references/update-workflow.md +1 -1
  79. package/src/claude/skills/fix/SKILL.md +153 -0
  80. package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
  81. package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
  82. package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
  83. package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
  84. package/src/claude/skills/git/SKILL.md +48 -9
  85. package/src/claude/skills/git/references/commit-protocols.md +35 -16
  86. package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
  87. package/src/claude/skills/orca/SKILL.md +9 -8
  88. package/src/claude/skills/research/SKILL.md +2 -0
  89. package/src/claude/skills/scout/SKILL.md +61 -0
  90. package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
  91. package/src/claude/skills/specs/SKILL.md +22 -29
  92. package/src/claude/skills/specs/references/review.md +8 -6
  93. package/src/claude/skills/specs/references/templates.md +34 -41
  94. package/src/claude/skills/sync/SKILL.md +8 -15
  95. package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
  96. package/src/claude/skills/sync/references/sync-protocols.md +4 -13
  97. package/src/claude/skills/test/SKILL.md +22 -3
  98. package/src/claude/skills/test/references/execution-strategy.md +83 -0
  99. package/src/claude/skills/test/references/failure-triage.md +1 -1
  100. package/src/claude/skills/ui-ux/LICENSE +21 -0
  101. package/src/claude/skills/ui-ux/SKILL.md +497 -0
  102. package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
  103. package/src/claude/skills/ui-ux/references/budgets.md +113 -0
  104. package/src/claude/skills/ui-ux/references/checklist.md +271 -0
  105. package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
  106. package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
  107. package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
  108. package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
  109. package/src/claude/skills/ui-ux/references/components/button.md +142 -0
  110. package/src/claude/skills/ui-ux/references/components/card.md +113 -0
  111. package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
  112. package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
  113. package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
  114. package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
  115. package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
  116. package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
  117. package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
  118. package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
  119. package/src/claude/skills/ui-ux/references/components/input.md +161 -0
  120. package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
  121. package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
  122. package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
  123. package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
  124. package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
  125. package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
  126. package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
  127. package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
  128. package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
  129. package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
  130. package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
  131. package/src/claude/skills/ui-ux/references/design-process.md +650 -0
  132. package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
  133. package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
  134. package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
  135. package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
  136. package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
  137. package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
  138. package/src/claude/skills/ui-ux/references/principles.md +408 -0
  139. package/src/claude/skills/ui-ux/references/refactor.md +245 -0
  140. package/src/claude/skills/ui-ux/references/responsive.md +195 -0
  141. package/src/claude/skills/ui-ux/references/review.md +528 -0
  142. package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
  143. package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
  144. package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
  145. package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
  146. package/src/claude/skills/ui-ux/references/styles.md +311 -0
  147. package/src/claude/skills/ui-ux/references/system.md +219 -0
  148. package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
  149. package/src/claude/skills/ui-ux/references/tokens.css +362 -0
  150. package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
  151. package/src/claude/skills/ui-ux/scripts/package.json +9 -0
  152. package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
  153. package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
  154. package/src/claude/status.cjs +149 -144
  155. package/src/codex/AGENTS.md +8 -13
  156. package/src/codex/hooks/agent.cjs +2 -1
  157. package/src/codex/hooks/lib/spec-receipt.cjs +2 -2
  158. package/src/codex/hooks/lib/spec-utils.cjs +0 -7
  159. package/src/codex/hooks/rules.cjs +2 -1
  160. package/src/codex/hooks/session.cjs +0 -2
  161. package/src/codex/hooks/spec-gate.cjs +7 -18
  162. package/src/codex/hooks.json +0 -20
  163. package/src/codex/rules/state-sync.md +3 -12
  164. package/src/common/AGENTS.md +2 -2
  165. package/src/omp/extensions/cafekit-bridge.mjs +6 -6
  166. package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
  167. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
  168. package/src/claude/hooks/completion-authority-check.cjs +0 -56
  169. package/src/claude/hooks/completion-authority-state.cjs +0 -527
  170. package/src/claude/hooks/completion-authority.cjs +0 -163
  171. package/src/claude/hooks/semantic-review-authority.cjs +0 -154
  172. package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
  173. package/src/claude/hooks/usage.cjs +0 -197
  174. package/src/claude/skills/delegate/SKILL.md +0 -124
  175. package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
  176. package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
  177. package/src/claude/skills/hotfix/SKILL.md +0 -289
  178. package/src/claude/skills/inspect/SKILL.md +0 -142
  179. package/src/claude/skills/loop/SKILL.md +0 -114
  180. package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
  181. package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
  182. package/src/claude/skills/route/SKILL.md +0 -94
  183. package/src/claude/skills/route/references/agent-timing.md +0 -46
  184. package/src/claude/skills/route/references/chaining-patterns.md +0 -53
  185. package/src/claude/skills/route/references/task-taxonomy.md +0 -48
  186. package/src/codex/hooks/completion-authority-check.cjs +0 -67
  187. package/src/codex/hooks/completion-authority-state.cjs +0 -527
  188. package/src/codex/hooks/completion-authority.cjs +0 -109
  189. package/src/codex/hooks/semantic-review-authority.cjs +0 -130
  190. package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
  191. /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
  192. /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
  193. /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
@@ -1,130 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const { spawnSync } = require('child_process');
7
- const STATE = require('./completion-authority-state.cjs');
8
- const PATH_SAFETY = require('./lib/runtime-path-safety.cjs');
9
-
10
- const SCHEMA_VERSION = 1;
11
- const MARKER = 'CAFEKIT_SEMANTIC_REVIEW_ATTESTATION ';
12
- const SHA256_RE = /^sha256:[a-f0-9]{64}$/;
13
- const REVIEWER_ROLE_ALLOWLIST = new Set(['code-auditor', 'code_auditor']);
14
- const RECORD_FIELDS = ['schema_version', 'kind', 'identity', 'semantic_digest', 'event', 'verdict', 'host_session_id', 'reviewer_agent_id', 'reviewer_agent_type', 'issued_at', 'mac'];
15
-
16
- function plain(value) { return Boolean(value && typeof value === 'object' && !Array.isArray(value)); }
17
- function exactKeys(value, fields) { return plain(value) && JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...fields].sort()); }
18
- function inside(root, target) { const relative = path.relative(root, target); return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative); }
19
-
20
- function projectRoot(payload = {}) {
21
- const installedRoot = path.resolve(__dirname, '..', '..');
22
- if (fs.existsSync(path.join(installedRoot, '.codex', 'hooks', path.basename(__filename)))) {
23
- try { return STATE.canonicalProjectRoot(installedRoot); } catch {}
24
- }
25
- for (const candidate of [process.env.CLAUDE_PROJECT_DIR, process.env.PROJECT_ROOT]) {
26
- if (typeof candidate === 'string' && candidate.trim()) { try { return STATE.canonicalProjectRoot(candidate); } catch { /* continue */ } }
27
- }
28
- const cwd = typeof payload.cwd === 'string' && payload.cwd.trim() ? payload.cwd : process.cwd();
29
- return STATE.canonicalProjectRoot(cwd);
30
- }
31
-
32
- function identityFor(root, specFile, featureName) {
33
- if (typeof featureName !== 'string' || !featureName.trim() || featureName.includes('/') || featureName.includes('\\') || featureName === '.' || featureName === '..' || featureName.includes('..')) throw new Error('feature identity is malformed');
34
- if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(featureName)) throw new Error('feature identity is malformed');
35
- const canonicalRoot = STATE.canonicalProjectRoot(root);
36
- const canonicalSpec = fs.realpathSync(path.resolve(specFile));
37
- if (!inside(canonicalRoot, canonicalSpec) || path.basename(canonicalSpec) !== 'spec.json') throw new Error('spec identity escapes the project');
38
- const relative = path.relative(canonicalRoot, canonicalSpec);
39
- const expected = path.join('specs', featureName, 'spec.json');
40
- if (relative !== expected) throw new Error(`spec identity must be ${expected}, not ${relative}`);
41
- const spec = JSON.parse(fs.readFileSync(canonicalSpec, 'utf8'));
42
- if (spec.feature_name !== featureName) throw new Error('feature identity does not match spec.json');
43
- return { project_root: canonicalRoot, spec_file: canonicalSpec, feature_name: featureName };
44
- }
45
-
46
- function recordPath(root, identity) {
47
- const name = STATE.digest({ namespace: 'cafekit-semantic-review-observation-v1', identity });
48
- return path.join(STATE.stateDir(root), 'semantic-reviews', `observation-${name}.json`);
49
- }
50
-
51
- function parseClaim(message) {
52
- const lines = String(message || '').split(/\r?\n/).filter((line) => line.startsWith(MARKER));
53
- if (lines.length === 0) return null;
54
- if (lines.length !== 1) throw new Error('exactly one semantic review attestation marker is required');
55
- const claim = JSON.parse(lines[0].slice(MARKER.length));
56
- if (!exactKeys(claim, ['feature_name', 'spec_file', 'semantic_digest', 'verdict'])) throw new Error('semantic review claim fields are malformed');
57
- if (claim.verdict !== 'PASS' || !SHA256_RE.test(claim.semantic_digest || '')) throw new Error('semantic review claim must contain literal PASS and a sha256 digest');
58
- return claim;
59
- }
60
-
61
- function validatorFor(root) {
62
- const installedRoot = path.join(root, '.codex');
63
- const installedHook = path.join(installedRoot, 'hooks', path.basename(__filename));
64
- if (fs.existsSync(installedHook)) {
65
- const canonicalHook = PATH_SAFETY.canonicalRegularFile(root, installedHook, 'installed semantic hook');
66
- if (canonicalHook !== fs.realpathSync(__filename)) throw new Error('installed semantic hook identity is invalid');
67
- const validator = path.join(installedRoot, 'scripts', 'validate-spec-output.cjs');
68
- PATH_SAFETY.verifyCommonJsClosure(installedRoot, validator);
69
- return PATH_SAFETY.canonicalRegularFile(installedRoot, validator, 'semantic validator');
70
- }
71
-
72
- const sourceRuntime = path.resolve(__dirname, '..', '..', 'claude');
73
- const sourceRoot = path.resolve(sourceRuntime, '..');
74
- if (path.basename(sourceRuntime) !== 'claude' || path.basename(sourceRoot) !== 'src') {
75
- throw new Error('semantic validator runtime root is not recognized');
76
- }
77
- const validator = path.join(sourceRuntime, 'scripts', 'validate-spec-output.cjs');
78
- PATH_SAFETY.verifyCommonJsClosure(sourceRoot, validator);
79
- return PATH_SAFETY.canonicalRegularFile(sourceRoot, validator, 'semantic validator');
80
- }
81
-
82
- function currentDigest(root, specFile) {
83
- const validator = validatorFor(root);
84
- const result = spawnSync(process.execPath, [validator, path.dirname(specFile), '--semantic-digest'], { cwd: root, encoding: 'utf8' });
85
- const digest = result.stdout.trim();
86
- if (result.status !== 0 || !SHA256_RE.test(digest)) throw new Error(`semantic digest could not be recomputed (${result.stderr.trim() || `exit ${result.status}`})`);
87
- return digest;
88
- }
89
-
90
- function recordFromSubagentStop(payload) {
91
- // Honest-agent guardrail: the host hook observes this SubagentStop payload.
92
- // The worker-authored claim is not authenticated input or a security boundary.
93
- if (!plain(payload) || payload.hook_event_name !== 'SubagentStop') return { recorded: false, reason: 'event' };
94
- for (const field of ['session_id', 'agent_id', 'agent_type', 'last_assistant_message']) if (typeof payload[field] !== 'string' || !payload[field].trim()) return { recorded: false, reason: `missing-${field}` };
95
- if (!REVIEWER_ROLE_ALLOWLIST.has(payload.agent_type)) return { recorded: false, reason: 'reviewer-role' };
96
- const root = projectRoot(payload);
97
- const claim = parseClaim(payload.last_assistant_message);
98
- if (claim === null) return { recorded: false, reason: 'no-claim' };
99
- const identity = identityFor(root, path.resolve(root, claim.spec_file), claim.feature_name);
100
- const digest = currentDigest(root, identity.spec_file);
101
- if (digest !== claim.semantic_digest) throw new Error('claimed semantic digest does not match current artifacts');
102
- const key = STATE.ensureKey();
103
- const record = { schema_version: SCHEMA_VERSION, kind: 'semantic-review-observation', identity, semantic_digest: digest, event: 'SubagentStop', verdict: 'PASS', host_session_id: payload.session_id, reviewer_agent_id: payload.agent_id, reviewer_agent_type: payload.agent_type, issued_at: new Date().toISOString() };
104
- record.mac = STATE.recordMac(record, key);
105
- STATE.atomicWrite(recordPath(root, identity), record);
106
- return { recorded: true, record };
107
- }
108
-
109
- function verifyAttestation(root, specFile, featureName, semanticDigest) {
110
- let identity;
111
- try { identity = identityFor(root, specFile, featureName); } catch (error) { return { ok: false, reason: error.message }; }
112
- let record;
113
- try { record = JSON.parse(fs.readFileSync(recordPath(root, identity), 'utf8')); } catch (error) { return { ok: false, reason: error.code === 'ENOENT' ? 'matching host-hook observation is missing' : 'host-hook observation is unreadable' }; }
114
- const key = STATE.readKey();
115
- if (key.missing) return { ok: false, reason: 'completion authority key is missing' };
116
- if (!exactKeys(record, RECORD_FIELDS) || !STATE.macMatches(record, key.key)) return { ok: false, reason: 'host-hook observation is malformed or its MAC is invalid' };
117
- if (record.schema_version !== SCHEMA_VERSION || record.kind !== 'semantic-review-observation' || record.event !== 'SubagentStop' || record.verdict !== 'PASS') return { ok: false, reason: 'host-hook observation event or verdict is invalid' };
118
- if (STATE.stableStringify(record.identity) !== STATE.stableStringify(identity)) return { ok: false, reason: 'host-hook observation project, feature, or spec identity is invalid' };
119
- if (record.semantic_digest !== semanticDigest) return { ok: false, reason: 'host-hook observation semantic digest is stale' };
120
- return { ok: true, record };
121
- }
122
-
123
- if (require.main === module) {
124
- try { const raw = fs.readFileSync(0, 'utf8').trim(); if (raw) recordFromSubagentStop(JSON.parse(raw)); }
125
- catch (error) { process.stderr.write(`CafeKit semantic review SubagentStop claim rejected: ${error.message}\n`); }
126
- // Codex SubagentStop requires valid JSON on stdout for every exit-0 hook.
127
- process.stdout.write('{}\n');
128
- }
129
-
130
- module.exports = { MARKER, REVIEWER_ROLE_ALLOWLIST, recordFromSubagentStop, verifyAttestation, identityFor, recordPath };
@@ -1,63 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const {
7
- getHookContext,
8
- logCrash,
9
- readPayload
10
- } = require('./lib/hook-context.cjs');
11
-
12
- function addedFiles(toolName, input) {
13
- if (toolName === 'Write') {
14
- return [input.file_path || input.filePath || input.path || ''].filter(Boolean);
15
- }
16
- if (toolName !== 'apply_patch') return [];
17
- const patch = typeof input.command === 'string'
18
- ? input.command
19
- : typeof input.patch === 'string' ? input.patch : '';
20
- return [...patch.matchAll(/^\*\*\* Add File:\s+(.+)$/gm)]
21
- .map((match) => match[1].trim());
22
- }
23
-
24
- function projectRelative(filePath, projectRoot, sessionCwd) {
25
- const absolute = path.isAbsolute(filePath)
26
- ? path.normalize(filePath)
27
- : path.resolve(sessionCwd, filePath);
28
- return path.relative(projectRoot, absolute).replace(/\\/g, '/');
29
- }
30
-
31
- try {
32
- const payload = readPayload();
33
- if (!payload) process.exit(0);
34
- const { projectRoot, runtime, sessionCwd } = getHookContext(payload);
35
- const candidates = addedFiles(payload.tool_name || '', payload.tool_input || {});
36
- const taskFile = candidates
37
- .map((requested) => ({
38
- requested,
39
- relative: projectRelative(requested, projectRoot, sessionCwd)
40
- }))
41
- .find(({ relative }) => (
42
- /^specs\/[^/]+\/tasks\/task-[^/]+\.md$/.test(relative)
43
- ));
44
- if (!taskFile) process.exit(0);
45
- if (runtime.spec?.scaffold_guard === false) process.exit(0);
46
-
47
- const scaffold = path.join(projectRoot, '.codex', 'scripts', 'spec-scaffold.cjs');
48
- if (!fs.existsSync(scaffold)) process.exit(0);
49
-
50
- const feature = taskFile.relative.split('/')[1];
51
- const command = sessionCwd === projectRoot
52
- ? `node .codex/scripts/spec-scaffold.cjs ${feature}`
53
- : `node ${JSON.stringify(scaffold)} ${feature}`;
54
- process.stderr.write(
55
- 'TASK SCAFFOLD REQUIRED: task files must be generated, not hand-written.\n'
56
- + `Blocked Write: ${taskFile.requested}\n\n`
57
- + 'Generate the stubs, then fill their {{...}} placeholders with apply_patch:\n'
58
- + ` ${command} --tasks "R0-01-slug,R1-01-slug,..." --tasks-only\n`
59
- );
60
- process.exit(2);
61
- } catch (error) {
62
- logCrash('task-scaffold-guard', error);
63
- }