@haposoft/cafekit 0.16.7 → 0.18.1

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 (231) 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 +169 -272
  4. package/bin/__tests__/codex-native.test.js +100 -202
  5. package/bin/__tests__/develop-contract.test.js +122 -1413
  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 +154 -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 +90 -925
  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 +30 -161
  20. package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -437
  21. package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
  22. package/bin/__tests__/ui-ux-skill.test.js +101 -0
  23. package/bin/__tests__/usage-hook-retired.test.js +114 -0
  24. package/bin/__tests__/validator-grounding.test.js +18 -2949
  25. package/bin/lib/managed-writer.js +2 -3
  26. package/bin/phases/codex-runtime.js +27 -0
  27. package/bin/phases/copy-payload.js +13 -28
  28. package/bin/phases/omp-runtime.js +25 -0
  29. package/package.json +2 -3
  30. package/src/claude/CLAUDE.md +3 -10
  31. package/src/claude/agents/brainstormer.md +7 -3
  32. package/src/claude/agents/code-auditor.md +44 -56
  33. package/src/claude/agents/debugger.md +8 -88
  34. package/src/claude/agents/deployer.md +3 -5
  35. package/src/claude/agents/docs-keeper.md +6 -14
  36. package/src/claude/agents/git-ops.md +2 -4
  37. package/src/claude/agents/implementer.md +12 -16
  38. package/src/claude/agents/inspector.md +4 -3
  39. package/src/claude/agents/project-manager.md +9 -17
  40. package/src/claude/agents/researcher.md +9 -3
  41. package/src/claude/agents/spec-maker.md +15 -15
  42. package/src/claude/agents/test-runner.md +8 -11
  43. package/src/claude/agents/ui-ux-designer.md +0 -1
  44. package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
  45. package/src/claude/hooks/__tests__/spec-gate.test.js +244 -333
  46. package/src/claude/hooks/__tests__/spec-state-touched.test.js +160 -0
  47. package/src/claude/hooks/__tests__/state.test.js +68 -0
  48. package/src/claude/hooks/__tests__/statusline.test.js +140 -11
  49. package/src/claude/hooks/agent.cjs +2 -1
  50. package/src/claude/hooks/lib/hook-payload.cjs +2 -2
  51. package/src/claude/hooks/rules.cjs +2 -1
  52. package/src/claude/hooks/session.cjs +0 -1
  53. package/src/claude/hooks/spec-gate.cjs +17 -83
  54. package/src/claude/hooks/spec-state.cjs +142 -39
  55. package/src/claude/hooks/state.cjs +21 -2
  56. package/src/claude/migration-manifest.json +42 -22
  57. package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
  58. package/src/claude/output-styles/coding-level-1-junior.md +3 -3
  59. package/src/claude/output-styles/coding-level-2-mid.md +3 -3
  60. package/src/claude/output-styles/coding-level-3-senior.md +2 -2
  61. package/src/claude/output-styles/coding-level-4-lead.md +5 -5
  62. package/src/claude/output-styles/coding-level-5-god.md +6 -6
  63. package/src/claude/rules/ai-dev-rules.md +2 -3
  64. package/src/claude/rules/hook-protocols.md +8 -15
  65. package/src/claude/rules/manage-docs.md +9 -23
  66. package/src/claude/rules/orchestrator.md +3 -4
  67. package/src/claude/rules/review-audit-self-decision.md +3 -3
  68. package/src/claude/rules/skill-domain-routing.md +1 -3
  69. package/src/claude/rules/skill-workflow-routing.md +4 -5
  70. package/src/claude/rules/state-sync.md +3 -12
  71. package/src/claude/rules/workflow.md +11 -19
  72. package/src/claude/runtime.schema.json +5 -3
  73. package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
  74. package/src/claude/scripts/provenance.cjs +23 -5
  75. package/src/claude/scripts/spec-receipt.cjs +24 -121
  76. package/src/claude/scripts/spec-resolver.cjs +41 -458
  77. package/src/claude/scripts/workflow-policy.cjs +3 -1801
  78. package/src/claude/settings/settings.json +1 -32
  79. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  80. package/src/claude/skills/{question → ask}/SKILL.md +9 -75
  81. package/src/claude/skills/brainstorm/SKILL.md +43 -131
  82. package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
  83. package/src/claude/skills/code-review/SKILL.md +29 -81
  84. package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -8
  85. package/src/claude/skills/debug/SKILL.md +29 -149
  86. package/src/claude/skills/develop/SKILL.md +26 -83
  87. package/src/claude/skills/develop/references/parallel-waves.md +0 -8
  88. package/src/claude/skills/develop/references/quality-gate.md +15 -13
  89. package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
  90. package/src/claude/skills/docs/SKILL.md +2 -2
  91. package/src/claude/skills/docs/references/update-workflow.md +1 -1
  92. package/src/claude/skills/fix/SKILL.md +153 -0
  93. package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
  94. package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
  95. package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
  96. package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
  97. package/src/claude/skills/git/SKILL.md +48 -9
  98. package/src/claude/skills/git/references/commit-protocols.md +35 -16
  99. package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
  100. package/src/claude/skills/orca/SKILL.md +9 -8
  101. package/src/claude/skills/research/SKILL.md +17 -46
  102. package/src/claude/skills/scout/SKILL.md +61 -0
  103. package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
  104. package/src/claude/skills/specs/SKILL.md +22 -29
  105. package/src/claude/skills/specs/references/review.md +8 -6
  106. package/src/claude/skills/specs/references/templates.md +34 -41
  107. package/src/claude/skills/sync/SKILL.md +16 -45
  108. package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
  109. package/src/claude/skills/sync/references/sync-protocols.md +4 -13
  110. package/src/claude/skills/test/SKILL.md +39 -104
  111. package/src/claude/skills/test/references/execution-strategy.md +93 -21
  112. package/src/claude/skills/test/references/failure-triage.md +1 -1
  113. package/src/claude/skills/ui-ux/LICENSE +21 -0
  114. package/src/claude/skills/ui-ux/SKILL.md +497 -0
  115. package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
  116. package/src/claude/skills/ui-ux/references/budgets.md +113 -0
  117. package/src/claude/skills/ui-ux/references/checklist.md +271 -0
  118. package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
  119. package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
  120. package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
  121. package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
  122. package/src/claude/skills/ui-ux/references/components/button.md +142 -0
  123. package/src/claude/skills/ui-ux/references/components/card.md +113 -0
  124. package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
  125. package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
  126. package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
  127. package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
  128. package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
  129. package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
  130. package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
  131. package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
  132. package/src/claude/skills/ui-ux/references/components/input.md +161 -0
  133. package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
  134. package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
  135. package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
  136. package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
  137. package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
  138. package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
  139. package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
  140. package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
  141. package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
  142. package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
  143. package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
  144. package/src/claude/skills/ui-ux/references/design-process.md +650 -0
  145. package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
  146. package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
  147. package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
  148. package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
  149. package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
  150. package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
  151. package/src/claude/skills/ui-ux/references/principles.md +408 -0
  152. package/src/claude/skills/ui-ux/references/refactor.md +245 -0
  153. package/src/claude/skills/ui-ux/references/responsive.md +195 -0
  154. package/src/claude/skills/ui-ux/references/review.md +528 -0
  155. package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
  156. package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
  157. package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
  158. package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
  159. package/src/claude/skills/ui-ux/references/styles.md +311 -0
  160. package/src/claude/skills/ui-ux/references/system.md +219 -0
  161. package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
  162. package/src/claude/skills/ui-ux/references/tokens.css +362 -0
  163. package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
  164. package/src/claude/skills/ui-ux/scripts/package.json +9 -0
  165. package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
  166. package/src/claude/skills/ui-ux-pro-max/SKILL.md +25 -80
  167. package/src/claude/skills/web-testing/SKILL.md +2 -20
  168. package/src/claude/status.cjs +149 -144
  169. package/src/codex/AGENTS.md +8 -13
  170. package/src/codex/hooks/agent.cjs +2 -1
  171. package/src/codex/hooks/lib/spec-receipt.cjs +4 -32
  172. package/src/codex/hooks/lib/spec-utils.cjs +6 -52
  173. package/src/codex/hooks/rules.cjs +2 -1
  174. package/src/codex/hooks/session.cjs +0 -2
  175. package/src/codex/hooks/spec-gate.cjs +13 -81
  176. package/src/codex/hooks/spec-state.cjs +37 -41
  177. package/src/codex/hooks.json +0 -20
  178. package/src/codex/rules/state-sync.md +3 -12
  179. package/src/common/AGENTS.md +6 -12
  180. package/src/omp/extensions/cafekit-bridge.mjs +6 -6
  181. package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
  182. package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
  183. package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
  184. package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
  185. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
  186. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
  187. package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
  188. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
  189. package/src/claude/hooks/completion-authority-check.cjs +0 -56
  190. package/src/claude/hooks/completion-authority-state.cjs +0 -527
  191. package/src/claude/hooks/completion-authority.cjs +0 -163
  192. package/src/claude/hooks/lib/context.cjs +0 -616
  193. package/src/claude/hooks/lib/detect.cjs +0 -474
  194. package/src/claude/hooks/semantic-review-authority.cjs +0 -154
  195. package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
  196. package/src/claude/hooks/usage.cjs +0 -197
  197. package/src/claude/scripts/change-firewall.cjs +0 -1748
  198. package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
  199. package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
  200. package/src/claude/scripts/spec-final-state.cjs +0 -290
  201. package/src/claude/scripts/spec-ground.cjs +0 -650
  202. package/src/claude/scripts/spec-readiness.cjs +0 -425
  203. package/src/claude/scripts/spec-scaffold.cjs +0 -2317
  204. package/src/claude/scripts/spec-semantic-model.cjs +0 -396
  205. package/src/claude/scripts/validate-spec-output.cjs +0 -3941
  206. package/src/claude/skills/code-review/references/verification-gate.md +0 -46
  207. package/src/claude/skills/delegate/SKILL.md +0 -124
  208. package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
  209. package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
  210. package/src/claude/skills/hotfix/SKILL.md +0 -289
  211. package/src/claude/skills/inspect/SKILL.md +0 -142
  212. package/src/claude/skills/loop/SKILL.md +0 -114
  213. package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
  214. package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
  215. package/src/claude/skills/route/SKILL.md +0 -94
  216. package/src/claude/skills/route/references/agent-timing.md +0 -46
  217. package/src/claude/skills/route/references/chaining-patterns.md +0 -53
  218. package/src/claude/skills/route/references/task-taxonomy.md +0 -48
  219. package/src/claude/skills/specs/templates/design.md +0 -82
  220. package/src/claude/skills/specs/templates/requirements-init.md +0 -8
  221. package/src/claude/skills/specs/templates/requirements.md +0 -42
  222. package/src/claude/skills/specs/templates/spec-state.json +0 -56
  223. package/src/claude/skills/specs/templates/task.md +0 -47
  224. package/src/codex/hooks/completion-authority-check.cjs +0 -67
  225. package/src/codex/hooks/completion-authority-state.cjs +0 -527
  226. package/src/codex/hooks/completion-authority.cjs +0 -109
  227. package/src/codex/hooks/semantic-review-authority.cjs +0 -130
  228. package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
  229. /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
  230. /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
  231. /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
@@ -1,474 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * detect.cjs - Project and environment detection logic
4
- *
5
- * Extracted from session.cjs for reuse across runtime adapters.
6
- * Detects project type, package manager, framework, and runtime versions.
7
- *
8
- * @module detect
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
- const os = require('os');
14
- const { execSync, execFileSync } = require('child_process');
15
-
16
- // ═══════════════════════════════════════════════════════════════════════════
17
- // SAFE EXECUTION HELPERS
18
- // ═══════════════════════════════════════════════════════════════════════════
19
-
20
- /**
21
- * Safely execute shell command with optional timeout
22
- * @param {string} cmd - Command to execute
23
- * @param {number} [timeoutMs=5000] - Timeout in milliseconds
24
- * @returns {string|null} Output or null on error
25
- */
26
- function execSafe(cmd, timeoutMs = 5000) {
27
- try {
28
- return execSync(cmd, {
29
- encoding: 'utf8',
30
- timeout: timeoutMs,
31
- stdio: ['pipe', 'pipe', 'pipe']
32
- }).trim();
33
- } catch (e) {
34
- return null;
35
- }
36
- }
37
-
38
- /**
39
- * Safely execute a binary with arguments (no shell interpolation)
40
- * @param {string} binary - Path to the executable
41
- * @param {string[]} args - Arguments array
42
- * @param {number} [timeoutMs=2000] - Timeout in milliseconds
43
- * @returns {string|null} Output or null on error
44
- */
45
- function execFileSafe(binary, args, timeoutMs = 2000) {
46
- try {
47
- return execFileSync(binary, args, {
48
- encoding: 'utf8',
49
- timeout: timeoutMs,
50
- stdio: ['pipe', 'pipe', 'pipe']
51
- }).trim();
52
- } catch (e) {
53
- return null;
54
- }
55
- }
56
-
57
- // ═══════════════════════════════════════════════════════════════════════════
58
- // PYTHON DETECTION
59
- // ═══════════════════════════════════════════════════════════════════════════
60
-
61
- /**
62
- * Validate that a path is a file and doesn't contain shell metacharacters
63
- * @param {string} p - Path to validate
64
- * @returns {boolean}
65
- */
66
- function isValidPythonPath(p) {
67
- if (!p || typeof p !== 'string') return false;
68
- if (/[;&|`$(){}[\]<>!#*?]/.test(p)) return false;
69
- try {
70
- const stat = fs.statSync(p);
71
- return stat.isFile();
72
- } catch (e) {
73
- return false;
74
- }
75
- }
76
-
77
- /**
78
- * Build platform-specific Python paths for fast filesystem check
79
- * @returns {string[]} Array of potential Python paths
80
- */
81
- function getPythonPaths() {
82
- const paths = [];
83
-
84
- if (process.env.PYTHON_PATH) {
85
- paths.push(process.env.PYTHON_PATH);
86
- }
87
-
88
- if (process.platform === 'win32') {
89
- const localAppData = process.env.LOCALAPPDATA;
90
- const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
91
- const programFilesX86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
92
-
93
- if (localAppData) {
94
- paths.push(path.join(localAppData, 'Microsoft', 'WindowsApps', 'python.exe'));
95
- paths.push(path.join(localAppData, 'Microsoft', 'WindowsApps', 'python3.exe'));
96
- for (const ver of ['313', '312', '311', '310', '39']) {
97
- paths.push(path.join(localAppData, 'Programs', 'Python', `Python${ver}`, 'python.exe'));
98
- }
99
- }
100
-
101
- for (const ver of ['313', '312', '311', '310', '39']) {
102
- paths.push(path.join(programFiles, `Python${ver}`, 'python.exe'));
103
- paths.push(path.join(programFilesX86, `Python${ver}`, 'python.exe'));
104
- }
105
-
106
- paths.push('C:\\Python313\\python.exe');
107
- paths.push('C:\\Python312\\python.exe');
108
- paths.push('C:\\Python311\\python.exe');
109
- paths.push('C:\\Python310\\python.exe');
110
- paths.push('C:\\Python39\\python.exe');
111
- } else {
112
- paths.push('/usr/bin/python3');
113
- paths.push('/usr/local/bin/python3');
114
- paths.push('/opt/homebrew/bin/python3');
115
- paths.push('/opt/homebrew/bin/python');
116
- paths.push('/usr/bin/python');
117
- paths.push('/usr/local/bin/python');
118
- }
119
-
120
- return paths;
121
- }
122
-
123
- /**
124
- * Find Python binary using fast `which` lookup first, then filesystem check
125
- * @returns {string|null} Python binary path or null
126
- */
127
- function findPythonBinary() {
128
- // Fast path: try `which` command first (10ms vs 2000ms per path)
129
- if (process.platform !== 'win32') {
130
- const whichPython3 = execSafe('which python3', 500);
131
- if (whichPython3 && isValidPythonPath(whichPython3)) return whichPython3;
132
-
133
- const whichPython = execSafe('which python', 500);
134
- if (whichPython && isValidPythonPath(whichPython)) return whichPython;
135
- } else {
136
- // Windows: try `where` command
137
- const wherePython = execSafe('where python', 500);
138
- if (wherePython) {
139
- const firstPath = wherePython.split('\n')[0].trim();
140
- if (isValidPythonPath(firstPath)) return firstPath;
141
- }
142
- }
143
-
144
- // Fallback: check known paths
145
- const paths = getPythonPaths();
146
- for (const p of paths) {
147
- if (isValidPythonPath(p)) return p;
148
- }
149
- return null;
150
- }
151
-
152
- /**
153
- * Get Python version with optimized detection
154
- * @returns {string|null} Python version string or null
155
- */
156
- function getPythonVersion() {
157
- const pythonPath = findPythonBinary();
158
- if (pythonPath) {
159
- const result = execFileSafe(pythonPath, ['--version']);
160
- if (result) return result;
161
- }
162
-
163
- const commands = ['python3', 'python'];
164
- for (const cmd of commands) {
165
- const result = execFileSafe(cmd, ['--version']);
166
- if (result) return result;
167
- }
168
-
169
- return null;
170
- }
171
-
172
- // ═══════════════════════════════════════════════════════════════════════════
173
- // GIT DETECTION
174
- // ═══════════════════════════════════════════════════════════════════════════
175
-
176
- /**
177
- * Check if current directory is inside a git repository (fast check)
178
- * Uses filesystem traversal instead of git command to avoid command failures
179
- * @param {string} [startDir] - Directory to check from (defaults to cwd)
180
- * @returns {boolean}
181
- */
182
- function isGitRepo(startDir) {
183
- let dir;
184
- try {
185
- dir = startDir || process.cwd();
186
- } catch (e) {
187
- // CWD deleted or inaccessible
188
- return false;
189
- }
190
- const root = path.parse(dir).root;
191
-
192
- while (dir !== root) {
193
- if (fs.existsSync(path.join(dir, '.git'))) return true;
194
- dir = path.dirname(dir);
195
- }
196
- return fs.existsSync(path.join(root, '.git'));
197
- }
198
-
199
- /**
200
- * Get git remote URL
201
- * @returns {string|null}
202
- */
203
- function getGitRemoteUrl() {
204
- if (!isGitRepo()) return null;
205
- return execSafe('git config --get remote.origin.url');
206
- }
207
-
208
- /**
209
- * Get current git branch
210
- * @returns {string|null}
211
- */
212
- function getGitBranch() {
213
- if (!isGitRepo()) return null;
214
- return execSafe('git branch --show-current');
215
- }
216
-
217
- /**
218
- * Get git repository root
219
- * @returns {string|null}
220
- */
221
- function getGitRoot() {
222
- if (!isGitRepo()) return null;
223
- return execSafe('git rev-parse --show-toplevel');
224
- }
225
-
226
- // ═══════════════════════════════════════════════════════════════════════════
227
- // PROJECT DETECTION
228
- // ═══════════════════════════════════════════════════════════════════════════
229
-
230
- /**
231
- * Detect project type based on workspace indicators
232
- * @param {string} [configOverride] - Manual override from config
233
- * @returns {'monorepo' | 'library' | 'single-repo'}
234
- */
235
- function detectProjectType(configOverride) {
236
- if (configOverride && configOverride !== 'auto') return configOverride;
237
-
238
- if (fs.existsSync('pnpm-workspace.yaml')) return 'monorepo';
239
- if (fs.existsSync('lerna.json')) return 'monorepo';
240
-
241
- if (fs.existsSync('package.json')) {
242
- try {
243
- const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
244
- if (pkg.workspaces) return 'monorepo';
245
- if (pkg.main || pkg.exports) return 'library';
246
- } catch (e) { /* ignore */ }
247
- }
248
-
249
- return 'single-repo';
250
- }
251
-
252
- /**
253
- * Detect package manager from lock files
254
- * @param {string} [configOverride] - Manual override from config
255
- * @returns {'npm' | 'pnpm' | 'yarn' | 'bun' | null}
256
- */
257
- function detectPackageManager(configOverride) {
258
- if (configOverride && configOverride !== 'auto') return configOverride;
259
-
260
- if (fs.existsSync('bun.lockb')) return 'bun';
261
- if (fs.existsSync('pnpm-lock.yaml')) return 'pnpm';
262
- if (fs.existsSync('yarn.lock')) return 'yarn';
263
- if (fs.existsSync('package-lock.json')) return 'npm';
264
-
265
- return null;
266
- }
267
-
268
- /**
269
- * Detect framework from package.json dependencies
270
- * @param {string} [configOverride] - Manual override from config
271
- * @returns {string|null}
272
- */
273
- function detectFramework(configOverride) {
274
- if (configOverride && configOverride !== 'auto') return configOverride;
275
- if (!fs.existsSync('package.json')) return null;
276
-
277
- try {
278
- const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
279
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
280
-
281
- if (deps['next']) return 'next';
282
- if (deps['nuxt']) return 'nuxt';
283
- if (deps['astro']) return 'astro';
284
- if (deps['@remix-run/node'] || deps['@remix-run/react']) return 'remix';
285
- if (deps['svelte'] || deps['@sveltejs/kit']) return 'svelte';
286
- if (deps['vue']) return 'vue';
287
- if (deps['react']) return 'react';
288
- if (deps['express']) return 'express';
289
- if (deps['fastify']) return 'fastify';
290
- if (deps['hono']) return 'hono';
291
- if (deps['elysia']) return 'elysia';
292
-
293
- return null;
294
- } catch (e) {
295
- return null;
296
- }
297
- }
298
-
299
- // ═══════════════════════════════════════════════════════════════════════════
300
- // CODING LEVEL
301
- // ═══════════════════════════════════════════════════════════════════════════
302
-
303
- /**
304
- * Get coding level style name mapping
305
- * @param {number} level - Coding level (0-5)
306
- * @returns {string} Style name
307
- */
308
- function getCodingLevelStyleName(level) {
309
- const styleMap = {
310
- 0: 'coding-level-0-eli5',
311
- 1: 'coding-level-1-junior',
312
- 2: 'coding-level-2-mid',
313
- 3: 'coding-level-3-senior',
314
- 4: 'coding-level-4-lead',
315
- 5: 'coding-level-5-god'
316
- };
317
- return styleMap[level] || 'coding-level-5-god';
318
- }
319
-
320
- /**
321
- * Get coding level guidelines by reading from output-styles .md files
322
- * @param {number} level - Coding level (-1 to 5)
323
- * @param {string} [configDir] - Config directory path
324
- * @returns {string|null} Guidelines text or null if disabled
325
- */
326
- function getCodingLevelGuidelines(level, configDir) {
327
- if (level === -1 || level === null || level === undefined) return null;
328
-
329
- const styleName = getCodingLevelStyleName(level);
330
- const basePath = configDir || path.join(process.cwd(), '.claude');
331
- const stylePath = path.join(basePath, 'output-styles', `${styleName}.md`);
332
-
333
- try {
334
- if (!fs.existsSync(stylePath)) return null;
335
- const content = fs.readFileSync(stylePath, 'utf8');
336
- const withoutFrontmatter = content.replace(/^---[\s\S]*?---\n*/, '').trim();
337
- return withoutFrontmatter;
338
- } catch (e) {
339
- return null;
340
- }
341
- }
342
-
343
- // ═══════════════════════════════════════════════════════════════════════════
344
- // CONTEXT OUTPUT
345
- // ═══════════════════════════════════════════════════════════════════════════
346
-
347
- /**
348
- * Build context summary for output (compact, single line)
349
- * @param {Object} config - Loaded config
350
- * @param {Object} detections - Project detections
351
- * @param {{ path: string|null, resolvedBy: string|null }} resolved - Plan resolution
352
- * @param {string|null} gitRoot - Git repository root
353
- * @returns {string}
354
- */
355
- function buildContextOutput(config, detections, resolved, gitRoot) {
356
- const lines = [`Project: ${detections.type || 'unknown'}`];
357
- if (detections.pm) lines.push(`PM: ${detections.pm}`);
358
- lines.push(`Plan naming: ${config.plan.namingFormat}`);
359
-
360
- if (gitRoot && gitRoot !== process.cwd()) {
361
- lines.push(`Root: ${gitRoot}`);
362
- }
363
-
364
- if (resolved.path) {
365
- if (resolved.resolvedBy === 'session') {
366
- lines.push(`Plan: ${resolved.path}`);
367
- } else {
368
- lines.push(`Suggested: ${resolved.path}`);
369
- }
370
- }
371
-
372
- return lines.join(' | ');
373
- }
374
-
375
- // ═══════════════════════════════════════════════════════════════════════════
376
- // MAIN ENTRY POINT
377
- // ═══════════════════════════════════════════════════════════════════════════
378
-
379
- /**
380
- * Detect all project information
381
- *
382
- * @param {Object} [options]
383
- * @param {Object} [options.configOverrides] - Override auto-detection
384
- * @returns {{
385
- * type: 'monorepo' | 'library' | 'single-repo',
386
- * packageManager: 'npm' | 'pnpm' | 'yarn' | 'bun' | null,
387
- * framework: string | null,
388
- * pythonVersion: string | null,
389
- * nodeVersion: string,
390
- * gitBranch: string | null,
391
- * gitRoot: string | null,
392
- * gitUrl: string | null,
393
- * osPlatform: string,
394
- * user: string,
395
- * locale: string,
396
- * timezone: string
397
- * }}
398
- */
399
- function detectProject(options = {}) {
400
- const { configOverrides = {} } = options;
401
-
402
- return {
403
- type: detectProjectType(configOverrides.type),
404
- packageManager: detectPackageManager(configOverrides.packageManager),
405
- framework: detectFramework(configOverrides.framework),
406
- pythonVersion: getPythonVersion(),
407
- nodeVersion: process.version,
408
- gitBranch: getGitBranch(),
409
- gitRoot: getGitRoot(),
410
- gitUrl: getGitRemoteUrl(),
411
- osPlatform: process.platform,
412
- user: process.env.USERNAME || process.env.USER || process.env.LOGNAME || os.userInfo().username,
413
- locale: process.env.LANG || '',
414
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
415
- };
416
- }
417
-
418
- /**
419
- * Build static environment info object
420
- * @param {string} [configDir] - Config directory path
421
- * @returns {Object} Static environment info
422
- */
423
- function buildStaticEnv(configDir) {
424
- return {
425
- nodeVersion: process.version,
426
- pythonVersion: getPythonVersion(),
427
- osPlatform: process.platform,
428
- gitUrl: getGitRemoteUrl(),
429
- gitBranch: getGitBranch(),
430
- gitRoot: getGitRoot(),
431
- user: process.env.USERNAME || process.env.USER || process.env.LOGNAME || os.userInfo().username,
432
- locale: process.env.LANG || '',
433
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
434
- configDir: configDir || path.join(process.cwd(), '.claude')
435
- };
436
- }
437
-
438
- // ═══════════════════════════════════════════════════════════════════════════
439
- // EXPORTS
440
- // ═══════════════════════════════════════════════════════════════════════════
441
-
442
- module.exports = {
443
- // Main entry points
444
- detectProject,
445
- buildStaticEnv,
446
-
447
- // Detection functions
448
- detectProjectType,
449
- detectPackageManager,
450
- detectFramework,
451
-
452
- // Python detection
453
- getPythonVersion,
454
- findPythonBinary,
455
- getPythonPaths,
456
- isValidPythonPath,
457
-
458
- // Git detection
459
- isGitRepo,
460
- getGitRemoteUrl,
461
- getGitBranch,
462
- getGitRoot,
463
-
464
- // Coding level
465
- getCodingLevelStyleName,
466
- getCodingLevelGuidelines,
467
-
468
- // Output
469
- buildContextOutput,
470
-
471
- // Helpers
472
- execSafe,
473
- execFileSafe
474
- };
@@ -1,154 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
7
- const { spawnSync } = require('child_process');
8
- const STATE = require('./completion-authority-state.cjs');
9
- const PATH_SAFETY = require('./lib/runtime-path-safety.cjs');
10
-
11
- const SCHEMA_VERSION = 1;
12
- const MARKER = 'CAFEKIT_SEMANTIC_REVIEW_ATTESTATION ';
13
- const SHA256_RE = /^sha256:[a-f0-9]{64}$/;
14
- const REVIEWER_ROLE_ALLOWLIST = new Set(['code-auditor', 'code_auditor']);
15
- const RECORD_FIELDS = ['schema_version', 'kind', 'identity', 'semantic_digest', 'event', 'verdict', 'host_session_id', 'reviewer_agent_id', 'reviewer_agent_type', 'issued_at', 'mac'];
16
-
17
- function plain(value) {
18
- return Boolean(value && typeof value === 'object' && !Array.isArray(value));
19
- }
20
-
21
- function exactKeys(value, fields) {
22
- return plain(value) && JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...fields].sort());
23
- }
24
-
25
- function inside(root, target) {
26
- const relative = path.relative(root, target);
27
- return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
28
- }
29
-
30
- function projectRoot(payload = {}) {
31
- const installedRoot = path.resolve(__dirname, '..', '..');
32
- if (fs.existsSync(runtimePath(installedRoot, 'hooks', path.basename(__filename)))) {
33
- try { return STATE.canonicalProjectRoot(installedRoot); } catch {}
34
- }
35
- for (const candidate of [process.env.CLAUDE_PROJECT_DIR, process.env.PROJECT_ROOT]) {
36
- if (typeof candidate === 'string' && candidate.trim()) {
37
- try { return STATE.canonicalProjectRoot(candidate); } catch { /* continue */ }
38
- }
39
- }
40
- const cwd = typeof payload.cwd === 'string' && payload.cwd.trim() ? payload.cwd : process.cwd();
41
- return STATE.canonicalProjectRoot(cwd);
42
- }
43
-
44
- function identityFor(root, specFile, featureName) {
45
- if (typeof featureName !== 'string' || !featureName.trim() || featureName.includes('/') || featureName.includes('\\') || featureName === '.' || featureName === '..' || featureName.includes('..')) throw new Error('feature identity is malformed');
46
- if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(featureName)) throw new Error('feature identity is malformed');
47
- const canonicalRoot = STATE.canonicalProjectRoot(root);
48
- const canonicalSpec = fs.realpathSync(path.resolve(specFile));
49
- if (!inside(canonicalRoot, canonicalSpec) || path.basename(canonicalSpec) !== 'spec.json') throw new Error('spec identity escapes the project');
50
- const relative = path.relative(canonicalRoot, canonicalSpec);
51
- const expected = path.join('specs', featureName, 'spec.json');
52
- if (relative !== expected) throw new Error(`spec identity must be ${expected}, not ${relative}`);
53
- const spec = JSON.parse(fs.readFileSync(canonicalSpec, 'utf8'));
54
- if (spec.feature_name !== featureName) throw new Error('feature identity does not match spec.json');
55
- return { project_root: canonicalRoot, spec_file: canonicalSpec, feature_name: featureName };
56
- }
57
-
58
- function recordPath(root, identity) {
59
- const name = STATE.digest({ namespace: 'cafekit-semantic-review-observation-v1', identity });
60
- return path.join(STATE.stateDir(root), 'semantic-reviews', `observation-${name}.json`);
61
- }
62
-
63
- function parseClaim(message) {
64
- const lines = String(message || '').split(/\r?\n/).filter((line) => line.startsWith(MARKER));
65
- if (lines.length === 0) return null;
66
- if (lines.length !== 1) throw new Error('exactly one semantic review attestation marker is required');
67
- const claim = JSON.parse(lines[0].slice(MARKER.length));
68
- if (!exactKeys(claim, ['feature_name', 'spec_file', 'semantic_digest', 'verdict'])) throw new Error('semantic review claim fields are malformed');
69
- if (claim.verdict !== 'PASS' || !SHA256_RE.test(claim.semantic_digest || '')) throw new Error('semantic review claim must contain literal PASS and a sha256 digest');
70
- return claim;
71
- }
72
-
73
- function validatorFor(root) {
74
- const installedRoot = runtimeDir(root);
75
- const installedHook = path.join(installedRoot, 'hooks', path.basename(__filename));
76
- if (fs.existsSync(installedHook)) {
77
- const canonicalHook = PATH_SAFETY.canonicalRegularFile(root, installedHook, 'installed semantic hook');
78
- if (canonicalHook !== fs.realpathSync(__filename)) throw new Error('installed semantic hook identity is invalid');
79
- const validator = path.join(installedRoot, 'scripts', 'validate-spec-output.cjs');
80
- PATH_SAFETY.verifyCommonJsClosure(installedRoot, validator);
81
- return PATH_SAFETY.canonicalRegularFile(installedRoot, validator, 'semantic validator');
82
- }
83
-
84
- const sourceRuntime = path.resolve(__dirname, '..');
85
- const sourceRoot = path.resolve(sourceRuntime, '..');
86
- if (`.${path.basename(sourceRuntime)}` !== runtimeDirName() || path.basename(sourceRoot) !== 'src') {
87
- throw new Error('semantic validator runtime root is not recognized');
88
- }
89
- const validator = path.join(sourceRuntime, 'scripts', 'validate-spec-output.cjs');
90
- PATH_SAFETY.verifyCommonJsClosure(sourceRoot, validator);
91
- return PATH_SAFETY.canonicalRegularFile(sourceRoot, validator, 'semantic validator');
92
- }
93
-
94
- function currentDigest(root, specFile) {
95
- const validator = validatorFor(root);
96
- const result = spawnSync(process.execPath, [validator, path.dirname(specFile), '--semantic-digest'], { cwd: root, encoding: 'utf8' });
97
- const digest = result.stdout.trim();
98
- if (result.status !== 0 || !SHA256_RE.test(digest)) throw new Error(`semantic digest could not be recomputed (${result.stderr.trim() || `exit ${result.status}`})`);
99
- return digest;
100
- }
101
-
102
- function recordFromSubagentStop(payload) {
103
- // Honest-agent guardrail: the host hook observes this SubagentStop payload.
104
- // The worker-authored claim is not authenticated input or a security boundary.
105
- if (!plain(payload) || payload.hook_event_name !== 'SubagentStop') return { recorded: false, reason: 'event' };
106
- for (const field of ['session_id', 'agent_id', 'agent_type', 'last_assistant_message']) {
107
- if (typeof payload[field] !== 'string' || !payload[field].trim()) return { recorded: false, reason: `missing-${field}` };
108
- }
109
- if (!REVIEWER_ROLE_ALLOWLIST.has(payload.agent_type)) return { recorded: false, reason: 'reviewer-role' };
110
- const root = projectRoot(payload);
111
- const claim = parseClaim(payload.last_assistant_message);
112
- if (claim === null) return { recorded: false, reason: 'no-claim' };
113
- const requestedSpec = path.resolve(root, claim.spec_file);
114
- const identity = identityFor(root, requestedSpec, claim.feature_name);
115
- const digest = currentDigest(root, identity.spec_file);
116
- if (digest !== claim.semantic_digest) throw new Error('claimed semantic digest does not match current artifacts');
117
- const key = STATE.ensureKey();
118
- const record = {
119
- schema_version: SCHEMA_VERSION, kind: 'semantic-review-observation', identity,
120
- semantic_digest: digest, event: 'SubagentStop', verdict: 'PASS',
121
- host_session_id: payload.session_id, reviewer_agent_id: payload.agent_id,
122
- reviewer_agent_type: payload.agent_type, issued_at: new Date().toISOString(),
123
- };
124
- record.mac = STATE.recordMac(record, key);
125
- STATE.atomicWrite(recordPath(root, identity), record);
126
- return { recorded: true, record };
127
- }
128
-
129
- function verifyAttestation(root, specFile, featureName, semanticDigest) {
130
- let identity;
131
- try { identity = identityFor(root, specFile, featureName); } catch (error) { return { ok: false, reason: error.message }; }
132
- const file = recordPath(root, identity);
133
- let record;
134
- try { record = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (error) { return { ok: false, reason: error.code === 'ENOENT' ? 'matching host-hook observation is missing' : 'host-hook observation is unreadable' }; }
135
- const key = STATE.readKey();
136
- if (key.missing) return { ok: false, reason: 'completion authority key is missing' };
137
- if (!exactKeys(record, RECORD_FIELDS) || !STATE.macMatches(record, key.key)) return { ok: false, reason: 'host-hook observation is malformed or its MAC is invalid' };
138
- 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' };
139
- if (STATE.stableStringify(record.identity) !== STATE.stableStringify(identity)) return { ok: false, reason: 'host-hook observation project, feature, or spec identity is invalid' };
140
- if (record.semantic_digest !== semanticDigest) return { ok: false, reason: 'host-hook observation semantic digest is stale' };
141
- return { ok: true, record };
142
- }
143
-
144
- if (require.main === module) {
145
- try {
146
- const raw = fs.readFileSync(0, 'utf8').trim();
147
- const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
148
- if (raw) recordFromSubagentStop(normalizeHookPayload(JSON.parse(raw)));
149
- } catch (error) {
150
- process.stderr.write(`CafeKit semantic review SubagentStop claim rejected: ${error.message}\n`);
151
- }
152
- }
153
-
154
- module.exports = { MARKER, REVIEWER_ROLE_ALLOWLIST, recordFromSubagentStop, verifyAttestation, identityFor, recordPath };