@haposoft/cafekit 0.18.0 → 0.18.2

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 (162) hide show
  1. package/README.md +12 -1
  2. package/bin/__tests__/claude-settings.test.js +123 -0
  3. package/bin/__tests__/codex-ask-guard.test.js +123 -0
  4. package/bin/__tests__/codex-ask-projection.test.js +51 -0
  5. package/bin/__tests__/codex-hooks-ownership.test.js +80 -1
  6. package/bin/__tests__/codex-hooks.test.js +938 -237
  7. package/bin/__tests__/codex-native-hook-boundaries.test.js +136 -0
  8. package/bin/__tests__/codex-native.test.js +222 -149
  9. package/bin/__tests__/codex-projection-parity.test.js +314 -0
  10. package/bin/__tests__/codex-quiet-hooks.test.js +170 -0
  11. package/bin/__tests__/codex-research-projection.test.js +70 -0
  12. package/bin/__tests__/codex-specs-projection.test.js +76 -0
  13. package/bin/__tests__/develop-contract.test.js +102 -1362
  14. package/bin/__tests__/docs-sync-on-demand.test.js +25 -20
  15. package/bin/__tests__/inspect-paths.test.js +183 -0
  16. package/bin/__tests__/installer-safety.test.js +335 -0
  17. package/bin/__tests__/legacy-authority-decoupled.test.js +14 -19
  18. package/bin/__tests__/legacy-hooks-retired.test.js +18 -8
  19. package/bin/__tests__/omp-bridge.test.js +66 -12
  20. package/bin/__tests__/omp-hooks.test.js +11 -5
  21. package/bin/__tests__/orca-session.test.js +7 -0
  22. package/bin/__tests__/package-inventory.test.js +1 -395
  23. package/bin/__tests__/privacy-paths.test.js +317 -0
  24. package/bin/__tests__/spec-narrowing.test.js +30 -134
  25. package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -372
  26. package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
  27. package/bin/__tests__/usage-hook-retired.test.js +5 -3
  28. package/bin/__tests__/validator-grounding.test.js +17 -2929
  29. package/bin/install.js +25 -0
  30. package/bin/lib/codex-install.js +134 -12
  31. package/bin/lib/context.js +1 -1
  32. package/bin/lib/managed-writer.js +2 -3
  33. package/bin/phases/claude-runtime.js +91 -25
  34. package/bin/phases/claude-settings.js +22 -1
  35. package/bin/phases/codex-hooks.js +96 -33
  36. package/bin/phases/codex-runtime.js +45 -49
  37. package/bin/phases/copy-payload.js +11 -28
  38. package/bin/phases/omp-runtime.js +6 -3
  39. package/package.json +2 -3
  40. package/src/claude/CLAUDE.md +4 -0
  41. package/src/claude/agents/code-auditor.md +9 -6
  42. package/src/claude/agents/debugger.md +3 -9
  43. package/src/claude/agents/docs-keeper.md +4 -7
  44. package/src/claude/agents/git-ops.md +2 -4
  45. package/src/claude/agents/implementer.md +6 -7
  46. package/src/claude/agents/inspector.md +2 -0
  47. package/src/claude/agents/project-manager.md +2 -2
  48. package/src/claude/agents/spec-maker.md +4 -4
  49. package/src/claude/agents/test-runner.md +8 -11
  50. package/src/claude/agents/ui-ux-designer.md +0 -1
  51. package/src/claude/gitignore +3 -0
  52. package/src/claude/hooks/__tests__/installed-root.test.js +111 -0
  53. package/src/claude/hooks/__tests__/precompact.test.js +206 -10
  54. package/src/claude/hooks/__tests__/privacy-block.test.js +27 -0
  55. package/src/claude/hooks/__tests__/session-writeenv.test.js +25 -0
  56. package/src/claude/hooks/__tests__/spec-gate.test.js +311 -340
  57. package/src/claude/hooks/__tests__/spec-state-touched.test.js +230 -8
  58. package/src/claude/hooks/__tests__/state.test.js +207 -7
  59. package/src/claude/hooks/__tests__/update-check.test.js +88 -0
  60. package/src/claude/hooks/agent.cjs +7 -6
  61. package/src/claude/hooks/inspect-block.cjs +15 -63
  62. package/src/claude/hooks/lib/inspect-paths.cjs +304 -0
  63. package/src/claude/hooks/lib/parser.cjs +1 -1
  64. package/src/claude/hooks/lib/privacy-command-analysis.cjs +191 -17
  65. package/src/claude/hooks/lib/privacy-paths.cjs +313 -0
  66. package/src/claude/hooks/lib/project-root.cjs +15 -0
  67. package/src/claude/hooks/lib/rules-reservation.cjs +36 -0
  68. package/src/claude/hooks/lib/spec-touch.cjs +67 -0
  69. package/src/claude/hooks/lib/update-check.cjs +77 -0
  70. package/src/claude/hooks/precompact.cjs +8 -15
  71. package/src/claude/hooks/privacy-block.cjs +23 -205
  72. package/src/claude/hooks/rules.cjs +6 -28
  73. package/src/claude/hooks/session.cjs +34 -117
  74. package/src/claude/hooks/spec-gate.cjs +17 -89
  75. package/src/claude/hooks/spec-state.cjs +89 -96
  76. package/src/claude/hooks/state.cjs +96 -51
  77. package/src/claude/migration-manifest.json +20 -8
  78. package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
  79. package/src/claude/output-styles/coding-level-1-junior.md +3 -3
  80. package/src/claude/output-styles/coding-level-2-mid.md +3 -3
  81. package/src/claude/output-styles/coding-level-3-senior.md +2 -2
  82. package/src/claude/output-styles/coding-level-4-lead.md +5 -5
  83. package/src/claude/output-styles/coding-level-5-god.md +6 -6
  84. package/src/claude/rules/ai-dev-rules.md +1 -2
  85. package/src/claude/rules/hook-protocols.md +1 -1
  86. package/src/claude/rules/workflow.md +1 -1
  87. package/src/claude/runtime.json +0 -3
  88. package/src/claude/scripts/generate-skill-catalog.cjs +2 -2
  89. package/src/claude/scripts/provenance.cjs +14 -0
  90. package/src/claude/scripts/spec-receipt.cjs +0 -114
  91. package/src/claude/scripts/spec-resolver.cjs +41 -457
  92. package/src/claude/scripts/workflow-policy.cjs +3 -1801
  93. package/src/claude/settings/settings.json +1 -1
  94. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  95. package/src/claude/skills/ai-multimodal/scripts/check_setup.py +8 -68
  96. package/src/claude/skills/ai-multimodal/scripts/gemini_batch_process.py +6 -28
  97. package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
  98. package/src/claude/skills/code-review/SKILL.md +25 -108
  99. package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -7
  100. package/src/claude/skills/develop/SKILL.md +22 -70
  101. package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
  102. package/src/claude/skills/git/SKILL.md +1 -1
  103. package/src/claude/skills/git/references/worktree-blueprint.md +1 -1
  104. package/src/claude/skills/research/SKILL.md +24 -55
  105. package/src/claude/skills/sync/SKILL.md +12 -34
  106. package/src/claude/skills/sync/references/rebind-and-audit.md +2 -2
  107. package/src/claude/skills/test/SKILL.md +39 -123
  108. package/src/claude/skills/test/references/execution-strategy.md +11 -22
  109. package/src/claude/skills/ui-ux/SKILL.md +1 -1
  110. package/src/claude/skills/ui-ux-pro-max/SKILL.md +24 -79
  111. package/src/claude/skills/web-testing/SKILL.md +2 -20
  112. package/src/codex/AGENTS.md +8 -0
  113. package/src/codex/hooks/ask-guard.cjs +54 -0
  114. package/src/codex/hooks/inspect-block.cjs +11 -7
  115. package/src/codex/hooks/lib/inspect-paths.cjs +1 -199
  116. package/src/codex/hooks/lib/privacy-paths.cjs +1 -0
  117. package/src/codex/hooks/lib/privacy-state.cjs +98 -16
  118. package/src/codex/hooks/lib/rules-reservation.cjs +38 -0
  119. package/src/codex/hooks/lib/secret-keywords.cjs +1 -0
  120. package/src/codex/hooks/lib/spec-receipt.cjs +2 -30
  121. package/src/codex/hooks/lib/spec-session-touch.cjs +100 -0
  122. package/src/codex/hooks/lib/spec-touch.cjs +3 -0
  123. package/src/codex/hooks/lib/spec-utils.cjs +6 -45
  124. package/src/codex/hooks/lib/update-check.cjs +1 -0
  125. package/src/codex/hooks/precompact.cjs +39 -0
  126. package/src/codex/hooks/privacy-approval.cjs +28 -17
  127. package/src/codex/hooks/privacy-block.cjs +9 -215
  128. package/src/codex/hooks/rules.cjs +4 -30
  129. package/src/codex/hooks/secret-output-guardrail.cjs +1 -1
  130. package/src/codex/hooks/session.cjs +47 -11
  131. package/src/codex/hooks/spec-gate.cjs +32 -74
  132. package/src/codex/hooks/spec-state.cjs +68 -46
  133. package/src/codex/hooks/state.cjs +25 -4
  134. package/src/codex/hooks.json +39 -7
  135. package/src/codex/rules/hook-protocols.md +13 -6
  136. package/src/common/AGENTS.md +4 -10
  137. package/src/omp/extensions/cafekit-bridge.mjs +12 -4
  138. package/src/omp/hooks/privacy-block.cjs +24 -206
  139. package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
  140. package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
  141. package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
  142. package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
  143. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
  144. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
  145. package/src/claude/hooks/lib/context.cjs +0 -527
  146. package/src/claude/hooks/lib/detect.cjs +0 -474
  147. package/src/claude/scripts/change-firewall.cjs +0 -1748
  148. package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
  149. package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
  150. package/src/claude/scripts/spec-final-state.cjs +0 -290
  151. package/src/claude/scripts/spec-ground.cjs +0 -650
  152. package/src/claude/scripts/spec-readiness.cjs +0 -425
  153. package/src/claude/scripts/spec-scaffold.cjs +0 -2317
  154. package/src/claude/scripts/spec-semantic-model.cjs +0 -396
  155. package/src/claude/scripts/validate-spec-output.cjs +0 -3914
  156. package/src/claude/skills/code-review/references/verification-gate.md +0 -41
  157. package/src/claude/skills/specs/templates/design.md +0 -82
  158. package/src/claude/skills/specs/templates/requirements-init.md +0 -8
  159. package/src/claude/skills/specs/templates/requirements.md +0 -42
  160. package/src/claude/skills/specs/templates/spec-state.json +0 -56
  161. package/src/claude/skills/specs/templates/task.md +0 -47
  162. package/src/codex/rules/state-sync.md +0 -60
@@ -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
- };