specweave 1.0.550 → 1.0.552

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 (166) hide show
  1. package/CLAUDE.md +1 -1
  2. package/bin/specweave.js +23 -1
  3. package/dist/src/cli/commands/hook.d.ts +15 -0
  4. package/dist/src/cli/commands/hook.d.ts.map +1 -0
  5. package/dist/src/cli/commands/hook.js +61 -0
  6. package/dist/src/cli/commands/hook.js.map +1 -0
  7. package/dist/src/cli/commands/init.d.ts.map +1 -1
  8. package/dist/src/cli/commands/init.js +5 -0
  9. package/dist/src/cli/commands/init.js.map +1 -1
  10. package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
  11. package/dist/src/cli/commands/refresh-plugins.js +11 -1
  12. package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
  13. package/dist/src/cli/commands/sync-setup.d.ts.map +1 -1
  14. package/dist/src/cli/commands/sync-setup.js +7 -3
  15. package/dist/src/cli/commands/sync-setup.js.map +1 -1
  16. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts +9 -0
  17. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts.map +1 -1
  18. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js +9 -3
  19. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js.map +1 -1
  20. package/dist/src/config/types.d.ts +2 -2
  21. package/dist/src/core/config/types.d.ts +18 -2
  22. package/dist/src/core/config/types.d.ts.map +1 -1
  23. package/dist/src/core/config/types.js.map +1 -1
  24. package/dist/src/core/hooks/handlers/hook-router.d.ts +19 -0
  25. package/dist/src/core/hooks/handlers/hook-router.d.ts.map +1 -0
  26. package/dist/src/core/hooks/handlers/hook-router.js +75 -0
  27. package/dist/src/core/hooks/handlers/hook-router.js.map +1 -0
  28. package/dist/src/core/hooks/handlers/index.d.ts +10 -0
  29. package/dist/src/core/hooks/handlers/index.d.ts.map +1 -0
  30. package/dist/src/core/hooks/handlers/index.js +9 -0
  31. package/dist/src/core/hooks/handlers/index.js.map +1 -0
  32. package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts +11 -0
  33. package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts.map +1 -0
  34. package/dist/src/core/hooks/handlers/post-tool-use-analytics.js +73 -0
  35. package/dist/src/core/hooks/handlers/post-tool-use-analytics.js.map +1 -0
  36. package/dist/src/core/hooks/handlers/post-tool-use.d.ts +11 -0
  37. package/dist/src/core/hooks/handlers/post-tool-use.d.ts.map +1 -0
  38. package/dist/src/core/hooks/handlers/post-tool-use.js +76 -0
  39. package/dist/src/core/hooks/handlers/post-tool-use.js.map +1 -0
  40. package/dist/src/core/hooks/handlers/pre-compact.d.ts +11 -0
  41. package/dist/src/core/hooks/handlers/pre-compact.d.ts.map +1 -0
  42. package/dist/src/core/hooks/handlers/pre-compact.js +77 -0
  43. package/dist/src/core/hooks/handlers/pre-compact.js.map +1 -0
  44. package/dist/src/core/hooks/handlers/pre-tool-use.d.ts +11 -0
  45. package/dist/src/core/hooks/handlers/pre-tool-use.d.ts.map +1 -0
  46. package/dist/src/core/hooks/handlers/pre-tool-use.js +318 -0
  47. package/dist/src/core/hooks/handlers/pre-tool-use.js.map +1 -0
  48. package/dist/src/core/hooks/handlers/session-start.d.ts +9 -0
  49. package/dist/src/core/hooks/handlers/session-start.d.ts.map +1 -0
  50. package/dist/src/core/hooks/handlers/session-start.js +111 -0
  51. package/dist/src/core/hooks/handlers/session-start.js.map +1 -0
  52. package/dist/src/core/hooks/handlers/stop-auto.d.ts +16 -0
  53. package/dist/src/core/hooks/handlers/stop-auto.d.ts.map +1 -0
  54. package/dist/src/core/hooks/handlers/stop-auto.js +122 -0
  55. package/dist/src/core/hooks/handlers/stop-auto.js.map +1 -0
  56. package/dist/src/core/hooks/handlers/stop-reflect.d.ts +14 -0
  57. package/dist/src/core/hooks/handlers/stop-reflect.d.ts.map +1 -0
  58. package/dist/src/core/hooks/handlers/stop-reflect.js +43 -0
  59. package/dist/src/core/hooks/handlers/stop-reflect.js.map +1 -0
  60. package/dist/src/core/hooks/handlers/stop-sync.d.ts +15 -0
  61. package/dist/src/core/hooks/handlers/stop-sync.d.ts.map +1 -0
  62. package/dist/src/core/hooks/handlers/stop-sync.js +68 -0
  63. package/dist/src/core/hooks/handlers/stop-sync.js.map +1 -0
  64. package/dist/src/core/hooks/handlers/types.d.ts +63 -0
  65. package/dist/src/core/hooks/handlers/types.d.ts.map +1 -0
  66. package/dist/src/core/hooks/handlers/types.js +27 -0
  67. package/dist/src/core/hooks/handlers/types.js.map +1 -0
  68. package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts +14 -0
  69. package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts.map +1 -0
  70. package/dist/src/core/hooks/handlers/user-prompt-submit.js +173 -0
  71. package/dist/src/core/hooks/handlers/user-prompt-submit.js.map +1 -0
  72. package/dist/src/core/hooks/handlers/utils.d.ts +25 -0
  73. package/dist/src/core/hooks/handlers/utils.d.ts.map +1 -0
  74. package/dist/src/core/hooks/handlers/utils.js +64 -0
  75. package/dist/src/core/hooks/handlers/utils.js.map +1 -0
  76. package/dist/src/core/increment/completion-validator.d.ts.map +1 -1
  77. package/dist/src/core/increment/completion-validator.js +32 -0
  78. package/dist/src/core/increment/completion-validator.js.map +1 -1
  79. package/dist/src/init/research/types.d.ts +1 -1
  80. package/dist/src/sync/sync-target-resolver.js.map +1 -1
  81. package/dist/src/utils/lock-manager.d.ts.map +1 -1
  82. package/dist/src/utils/lock-manager.js +5 -0
  83. package/dist/src/utils/lock-manager.js.map +1 -1
  84. package/dist/src/utils/plugin-copier.d.ts +10 -0
  85. package/dist/src/utils/plugin-copier.d.ts.map +1 -1
  86. package/dist/src/utils/plugin-copier.js +63 -35
  87. package/dist/src/utils/plugin-copier.js.map +1 -1
  88. package/package.json +1 -1
  89. package/plugins/specweave/agents/sw-closer.md +3 -2
  90. package/plugins/specweave/hooks/hooks.json +10 -10
  91. package/plugins/specweave/skills/code-reviewer/SKILL.md +180 -16
  92. package/plugins/specweave/skills/code-reviewer/agents/reviewer-comments.md +83 -0
  93. package/plugins/specweave/skills/code-reviewer/agents/reviewer-silent-failures.md +19 -0
  94. package/plugins/specweave/skills/code-reviewer/agents/reviewer-spec-compliance.md +19 -0
  95. package/plugins/specweave/skills/code-reviewer/agents/reviewer-tests.md +101 -0
  96. package/plugins/specweave/skills/code-reviewer/agents/reviewer-types.md +20 -0
  97. package/plugins/specweave/skills/done/SKILL.md +56 -21
  98. package/plugins/specweave/skills/grill/SKILL.md +1 -1
  99. package/plugins/specweave/skills/team-lead/agents/reviewer-logic.md +19 -0
  100. package/plugins/specweave/skills/team-lead/agents/reviewer-performance.md +20 -0
  101. package/plugins/specweave/skills/team-lead/agents/reviewer-security.md +20 -0
  102. package/src/templates/CLAUDE.md.template +7 -4
  103. package/plugins/specweave/hooks/README.md +0 -493
  104. package/plugins/specweave/hooks/_archive/stop-auto-v4-legacy.sh +0 -1319
  105. package/plugins/specweave/hooks/lib/common-setup.sh +0 -144
  106. package/plugins/specweave/hooks/lib/hook-errors.sh +0 -414
  107. package/plugins/specweave/hooks/lib/migrate-increment-work.sh +0 -245
  108. package/plugins/specweave/hooks/lib/resolve-package.sh +0 -146
  109. package/plugins/specweave/hooks/lib/scheduler-startup.sh +0 -135
  110. package/plugins/specweave/hooks/lib/score-increment.sh +0 -87
  111. package/plugins/specweave/hooks/lib/sync-spec-content.sh +0 -193
  112. package/plugins/specweave/hooks/lib/update-active-increment.sh +0 -95
  113. package/plugins/specweave/hooks/lib/update-status-line.sh +0 -233
  114. package/plugins/specweave/hooks/lib/validate-spec-status.sh +0 -171
  115. package/plugins/specweave/hooks/llm-judge-validator.sh +0 -219
  116. package/plugins/specweave/hooks/log-decision.sh +0 -168
  117. package/plugins/specweave/hooks/pre-compact.sh +0 -64
  118. package/plugins/specweave/hooks/startup-health-check.sh +0 -64
  119. package/plugins/specweave/hooks/stop-auto-v5.sh +0 -276
  120. package/plugins/specweave/hooks/stop-reflect.sh +0 -336
  121. package/plugins/specweave/hooks/stop-sync.sh +0 -283
  122. package/plugins/specweave/hooks/tests/test-auto-context-integration.sh +0 -126
  123. package/plugins/specweave/hooks/tests/test-stop-auto-enriched.sh +0 -128
  124. package/plugins/specweave/hooks/universal/dispatcher.mjs +0 -336
  125. package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +0 -325
  126. package/plugins/specweave/hooks/universal/hook-wrapper.cmd +0 -26
  127. package/plugins/specweave/hooks/universal/hook-wrapper.sh +0 -69
  128. package/plugins/specweave/hooks/universal/run-hook.sh +0 -20
  129. package/plugins/specweave/hooks/universal/session-start.cmd +0 -16
  130. package/plugins/specweave/hooks/universal/session-start.ps1 +0 -16
  131. package/plugins/specweave/hooks/user-prompt-submit.sh +0 -2550
  132. package/plugins/specweave/hooks/v2/detectors/lifecycle-detector.sh +0 -87
  133. package/plugins/specweave/hooks/v2/detectors/us-completion-detector.sh +0 -186
  134. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use-analytics.sh +0 -83
  135. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +0 -447
  136. package/plugins/specweave/hooks/v2/dispatchers/pre-tool-use.sh +0 -104
  137. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +0 -270
  138. package/plugins/specweave/hooks/v2/guards/completion-guard.sh +0 -14
  139. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +0 -14
  140. package/plugins/specweave/hooks/v2/guards/increment-existence-guard.sh +0 -240
  141. package/plugins/specweave/hooks/v2/guards/interview-enforcement-guard.sh +0 -171
  142. package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +0 -14
  143. package/plugins/specweave/hooks/v2/guards/skill-chain-enforcement-guard.sh +0 -222
  144. package/plugins/specweave/hooks/v2/guards/spec-template-enforcement-guard.sh +0 -21
  145. package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +0 -14
  146. package/plugins/specweave/hooks/v2/guards/status-completion-guard.sh +0 -84
  147. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +0 -475
  148. package/plugins/specweave/hooks/v2/guards/tdd-enforcement-guard.sh +0 -268
  149. package/plugins/specweave/hooks/v2/handlers/ac-sync-dispatcher.sh +0 -332
  150. package/plugins/specweave/hooks/v2/handlers/ac-validation-handler.sh +0 -50
  151. package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +0 -347
  152. package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +0 -83
  153. package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +0 -268
  154. package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +0 -104
  155. package/plugins/specweave/hooks/v2/handlers/status-line-handler.sh +0 -165
  156. package/plugins/specweave/hooks/v2/handlers/status-update.sh +0 -61
  157. package/plugins/specweave/hooks/v2/handlers/universal-auto-create-dispatcher.sh +0 -270
  158. package/plugins/specweave/hooks/v2/integrations/ado-post-living-docs-update.sh +0 -367
  159. package/plugins/specweave/hooks/v2/integrations/ado-post-task.sh +0 -179
  160. package/plugins/specweave/hooks/v2/integrations/github-auto-create-handler.sh +0 -553
  161. package/plugins/specweave/hooks/v2/integrations/github-post-task.sh +0 -345
  162. package/plugins/specweave/hooks/v2/integrations/jira-post-task.sh +0 -180
  163. package/plugins/specweave/hooks/v2/lib/check-provider-enabled.sh +0 -52
  164. package/plugins/specweave/hooks/v2/queue/enqueue.sh +0 -81
  165. package/plugins/specweave/hooks/v2/session-end.sh +0 -139
  166. package/plugins/specweave/hooks/validate-skill-activations.sh +0 -227
@@ -1,336 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Universal Hook Dispatcher (Cross-Platform)
4
- *
5
- * Routes hook calls to the appropriate TypeScript implementation.
6
- * Works on Windows, macOS, and Linux.
7
- *
8
- * Usage:
9
- * node dispatcher.mjs <hook-type>
10
- *
11
- * Where hook-type is one of:
12
- * - session-start
13
- * - post-tool-use
14
- * - completion-guard
15
- * - increment-duplicate-guard
16
- *
17
- * @module hooks/universal/dispatcher
18
- */
19
-
20
- import { spawn } from 'child_process';
21
- import { fileURLToPath } from 'url';
22
- import { dirname, join, resolve } from 'path';
23
- import { existsSync } from 'fs';
24
-
25
- const __filename = fileURLToPath(import.meta.url);
26
- const __dirname = dirname(__filename);
27
-
28
- // Hook type from arguments
29
- const hookType = process.argv[2] || 'unknown';
30
-
31
- // Global timeout for hook execution (30 seconds max)
32
- const HOOK_TIMEOUT_MS = 30000;
33
-
34
- /**
35
- * Detect if we're in a development environment with duplicate hooks
36
- *
37
- * Problem: When working IN the specweave project AND having the marketplace
38
- * plugin installed, Claude Code loads hooks from BOTH sources, causing:
39
- * - Duplicate hook execution
40
- * - Potential race conditions
41
- * - Confusing output
42
- *
43
- * Solution: If we detect we're running from the marketplace copy while
44
- * the CWD is the specweave project itself, skip execution (let local hooks handle it).
45
- *
46
- * @returns {boolean} true if we should skip this hook execution
47
- */
48
- function shouldSkipDueToDevEnvironment() {
49
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || '';
50
- const cwd = process.cwd();
51
-
52
- // Check if running from marketplace (installed plugin)
53
- const isFromMarketplace = pluginRoot.includes('.claude/plugins/marketplaces/');
54
-
55
- // Check if CWD is the specweave project itself
56
- const isInSpecweaveProject = existsSync(join(cwd, 'plugins', 'specweave', 'hooks', 'hooks.json'));
57
-
58
- // Skip marketplace hooks when developing specweave locally
59
- if (isFromMarketplace && isInSpecweaveProject) {
60
- // Output diagnostic only for session-start (once per session)
61
- if (hookType === 'session-start') {
62
- console.log(JSON.stringify({
63
- continue: true,
64
- systemMessage: 'SpecWeave dev environment: Using local hooks (marketplace hooks skipped)'
65
- }));
66
- } else {
67
- console.log(JSON.stringify({ continue: true }));
68
- }
69
- return true;
70
- }
71
-
72
- return false;
73
- }
74
-
75
- /**
76
- * Wrap a promise with a timeout
77
- * @param promise - Promise to wrap
78
- * @param timeoutMs - Timeout in milliseconds
79
- * @param cleanup - Optional cleanup function (e.g., to kill child process)
80
- */
81
- function withTimeout(promise, timeoutMs, cleanup) {
82
- let timeoutId;
83
- const timeoutPromise = new Promise((_, reject) => {
84
- timeoutId = setTimeout(() => {
85
- if (cleanup) cleanup();
86
- reject(new Error(`Hook timeout after ${timeoutMs}ms`));
87
- }, timeoutMs);
88
- });
89
-
90
- return Promise.race([promise, timeoutPromise]).finally(() => {
91
- clearTimeout(timeoutId);
92
- });
93
- }
94
-
95
- /**
96
- * Find the dist/src/hooks directory (TypeScript compiled hooks)
97
- */
98
- function findHooksDir() {
99
- // Try multiple locations
100
- const candidates = [
101
- // Development: project root dist/src/hooks
102
- join(__dirname, '..', '..', '..', '..', 'dist', 'src', 'hooks'),
103
- // Fallback: older path without src/ (backward compatibility)
104
- join(__dirname, '..', '..', '..', '..', 'dist', 'hooks'),
105
- ];
106
-
107
- for (const candidate of candidates) {
108
- const resolved = resolve(candidate);
109
- if (existsSync(resolved)) {
110
- return resolved;
111
- }
112
- }
113
-
114
- return null;
115
- }
116
-
117
- /**
118
- * Run a hook script with timeout protection
119
- */
120
- async function runHook(scriptName) {
121
- const hooksDir = findHooksDir();
122
- if (!hooksDir) {
123
- // No hooks directory - just continue
124
- console.log(JSON.stringify({ continue: true }));
125
- return;
126
- }
127
-
128
- const scriptPath = join(hooksDir, scriptName);
129
- if (!existsSync(scriptPath) && !existsSync(scriptPath + '.js')) {
130
- console.log(JSON.stringify({ continue: true }));
131
- return;
132
- }
133
-
134
- // Spawn node to run the script
135
- const child = spawn(process.execPath, [scriptPath], {
136
- stdio: ['inherit', 'inherit', 'inherit'],
137
- windowsHide: true,
138
- });
139
-
140
- const execPromise = new Promise((resolve) => {
141
- child.on('exit', (code) => resolve(code || 0));
142
- child.on('error', () => {
143
- console.log(JSON.stringify({ continue: true }));
144
- resolve(1);
145
- });
146
- });
147
-
148
- try {
149
- return await withTimeout(execPromise, HOOK_TIMEOUT_MS, () => {
150
- // Kill the child process on timeout
151
- try {
152
- child.kill('SIGTERM');
153
- setTimeout(() => child.kill('SIGKILL'), 1000);
154
- } catch { /* ignore */ }
155
- });
156
- } catch (err) {
157
- console.log(JSON.stringify({ continue: true, error: err.message }));
158
- return 1;
159
- }
160
- }
161
-
162
- /**
163
- * Cached Git Bash path (computed once per session)
164
- * undefined = not yet checked, null = checked and not found, string = path found
165
- */
166
- let gitBashCache = undefined;
167
-
168
- /**
169
- * Find Git Bash on Windows (with caching)
170
- *
171
- * Checks common Git for Windows installation paths.
172
- * Caches result to avoid repeated filesystem checks.
173
- */
174
- function findGitBash() {
175
- // Return cached result if available
176
- if (gitBashCache !== undefined) {
177
- return gitBashCache;
178
- }
179
-
180
- const gitBashPaths = [
181
- // Environment variable paths with proper fallbacks
182
- join(process.env.PROGRAMFILES || 'C:\\Program Files', 'Git', 'bin', 'bash.exe'),
183
- join(process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)', 'Git', 'bin', 'bash.exe'),
184
- join(process.env.LOCALAPPDATA || '', 'Programs', 'Git', 'bin', 'bash.exe'),
185
- // Hardcoded fallbacks for edge cases where env vars are missing
186
- 'C:\\Program Files\\Git\\bin\\bash.exe',
187
- 'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
188
- 'C:\\Git\\bin\\bash.exe',
189
- ];
190
-
191
- for (const p of gitBashPaths) {
192
- if (p && existsSync(p)) {
193
- gitBashCache = p;
194
- return p;
195
- }
196
- }
197
-
198
- gitBashCache = null;
199
- return null;
200
- }
201
-
202
- /**
203
- * Helper to spawn a bash script with timeout protection
204
- */
205
- async function spawnBashWithTimeout(bashExe, args, options = {}) {
206
- const child = spawn(bashExe, args, {
207
- stdio: ['inherit', 'inherit', 'inherit'],
208
- windowsHide: true,
209
- ...options,
210
- });
211
-
212
- const execPromise = new Promise((resolve) => {
213
- child.on('exit', (code) => resolve(code || 0));
214
- child.on('error', () => {
215
- console.log(JSON.stringify({ continue: true }));
216
- resolve(1);
217
- });
218
- });
219
-
220
- try {
221
- return await withTimeout(execPromise, HOOK_TIMEOUT_MS, () => {
222
- // Kill the child process on timeout
223
- try {
224
- child.kill('SIGTERM');
225
- setTimeout(() => child.kill('SIGKILL'), 1000);
226
- } catch { /* ignore */ }
227
- });
228
- } catch (err) {
229
- console.log(JSON.stringify({ continue: true, error: err.message }));
230
- return 1;
231
- }
232
- }
233
-
234
- /**
235
- * Fallback to bash script if TypeScript not built (with timeout protection)
236
- *
237
- * @param bashScript - Name of the bash script (e.g., 'post-tool-use.sh')
238
- * @param subdir - Subdirectory under v2 (e.g., 'dispatchers', 'guards')
239
- */
240
- async function fallbackToBash(bashScript, subdir = 'dispatchers') {
241
- const isWindows = process.platform === 'win32';
242
- const bashDir = join(__dirname, '..', 'v2', subdir);
243
- const scriptPath = join(bashDir, bashScript);
244
-
245
- if (!existsSync(scriptPath)) {
246
- console.log(JSON.stringify({ continue: true }));
247
- return;
248
- }
249
-
250
- if (isWindows) {
251
- // Strategy 1: Git Bash (preferred - most common)
252
- const bashExe = findGitBash();
253
- if (bashExe) {
254
- return spawnBashWithTimeout(bashExe, [scriptPath]);
255
- }
256
-
257
- // Strategy 2: WSL (if Git Bash not available) - FALLBACK only
258
- const wslPath = join(process.env.SYSTEMROOT || 'C:\\Windows', 'System32', 'wsl.exe');
259
- if (existsSync(wslPath)) {
260
- // Convert Windows path to WSL path:
261
- // 1. Backslashes to forward slashes: C:\path\file -> C:/path/file
262
- // 2. Drive letter to /mnt/: C:/path/file -> /mnt/c/path/file
263
- // NOTE: Handle BOTH uppercase and lowercase drive letters (C: and c:)
264
- const wslScriptPath = scriptPath
265
- .replace(/\\/g, '/')
266
- .replace(/^([A-Za-z]):/, (_, d) => `/mnt/${d.toLowerCase()}`);
267
- return spawnBashWithTimeout('wsl', ['bash', wslScriptPath]);
268
- }
269
-
270
- // Strategy 3: No bash available - output warning and continue
271
- // Hooks should not block Claude Code operation
272
- console.log(JSON.stringify({
273
- continue: true,
274
- systemMessage: 'SpecWeave hooks require Git Bash on Windows. Install from https://git-scm.com'
275
- }));
276
- return;
277
- }
278
-
279
- // POSIX (macOS, Linux) - run directly with timeout
280
- return spawnBashWithTimeout('bash', [scriptPath]);
281
- }
282
-
283
- // Main routing
284
- async function main() {
285
- // CRITICAL: Skip if we're in dev environment with duplicate hooks
286
- // This prevents marketplace hooks from running when local hooks are available
287
- if (shouldSkipDueToDevEnvironment()) {
288
- return;
289
- }
290
-
291
- const isWindows = process.platform === 'win32';
292
-
293
- try {
294
- switch (hookType) {
295
- case 'session-start':
296
- // CRITICAL: On Windows, ALWAYS use TypeScript implementation
297
- // Bash nohup/disown is unreliable in Git Bash (process may terminate early)
298
- // TypeScript uses proper Windows detached spawn with windowsHide
299
- if (isWindows) {
300
- await runHook('session-start.js');
301
- } else {
302
- // POSIX: try TypeScript first (preferred), fallback to bash for compatibility
303
- try {
304
- await runHook('session-start.js');
305
- } catch {
306
- await fallbackToBash('session-start.sh', 'dispatchers');
307
- }
308
- }
309
- break;
310
-
311
- case 'post-tool-use':
312
- // Currently only bash implementation
313
- // On Windows without Git Bash, will gracefully skip
314
- await fallbackToBash('post-tool-use.sh', 'dispatchers');
315
- break;
316
-
317
- case 'completion-guard':
318
- // Guards are in a different subdirectory
319
- // On Windows without Git Bash, will gracefully skip
320
- await fallbackToBash('completion-guard.sh', 'guards');
321
- break;
322
-
323
- case 'increment-duplicate-guard':
324
- // Prevents duplicate increment IDs
325
- await fallbackToBash('increment-duplicate-guard.sh', 'guards');
326
- break;
327
-
328
- default:
329
- console.log(JSON.stringify({ continue: true, error: `Unknown hook type: ${hookType}` }));
330
- }
331
- } catch (err) {
332
- console.log(JSON.stringify({ continue: true, error: String(err) }));
333
- }
334
- }
335
-
336
- main();
@@ -1,325 +0,0 @@
1
- #!/bin/bash
2
- # fail-fast-wrapper.sh - Non-blocking timeout wrapper for hooks
3
- #
4
- # v1.0.153: Hook-specific timeouts (user-prompt-submit: 30s for LLM detection)
5
- # v1.0.102+: Cross-platform support (Linux, macOS, BSD, Windows Git Bash)
6
- #
7
- # CRITICAL DESIGN PRINCIPLE:
8
- # - NEVER block tool operations
9
- # - All errors become warnings shown to user
10
- # - Safe JSON output even on catastrophic failures
11
- # - Universal timeout support (Perl fallback for all platforms)
12
- #
13
- # Usage: bash fail-fast-wrapper.sh <hook-script> [args...]
14
- #
15
- # Environment:
16
- # HOOK_TIMEOUT - max seconds (default: 5)
17
- # SPECWEAVE_HOOK_VERBOSE - show all warnings (default: 1)
18
- #
19
- # Returns safe JSON on timeout or error. Never hangs.
20
-
21
- set +e # CRITICAL: Never exit on error
22
-
23
- HOOK_TIMEOUT="${HOOK_TIMEOUT:-5}"
24
- WRAPPER_VERSION="1.0.153"
25
-
26
- # ============================================================================
27
- # HOOK-SPECIFIC TIMEOUT OVERRIDES (v1.0.153)
28
- # ============================================================================
29
- # Some hooks require longer timeouts due to LLM calls or network operations.
30
- # user-prompt-submit.sh: LLM-based detect-intent (~15s) + plugin installs (~4s)
31
- # Override defaults here while keeping fast timeouts for simple hooks.
32
- #
33
- # Environment overrides (if you need custom values):
34
- # HOOK_TIMEOUT_USER_PROMPT - timeout for user-prompt-submit (default: 30)
35
- # HOOK_TIMEOUT_SESSION_START - timeout for session-start (default: 20)
36
-
37
- script_name=$(basename "${1:-}" 2>/dev/null)
38
- case "$script_name" in
39
- user-prompt-submit.sh)
40
- # LLM detection can timeout (30s) + keyword fallback + plugin installs (~10s)
41
- # Total worst case: 30s LLM timeout + 10s installs + 5s buffer = 45s
42
- HOOK_TIMEOUT="${HOOK_TIMEOUT_USER_PROMPT:-45}"
43
- ;;
44
- session-start.sh)
45
- # Project detection may involve file scanning and optional LLM
46
- HOOK_TIMEOUT="${HOOK_TIMEOUT_SESSION_START:-20}"
47
- ;;
48
- stop-auto.sh)
49
- # May run tests + LLM evaluation + validation
50
- HOOK_TIMEOUT="${HOOK_TIMEOUT_STOP_AUTO:-120}"
51
- ;;
52
- post-tool-use.sh)
53
- # Dispatcher runs task-ac-sync (awk/sed/jq) + spawns background detectors
54
- # 5s is too tight for synchronous file processing + background spawning
55
- HOOK_TIMEOUT="${HOOK_TIMEOUT_POST_TOOL_USE:-15}"
56
- ;;
57
- pre-compact.sh)
58
- # Pressure signal is ultra-fast (just writes a JSON file)
59
- HOOK_TIMEOUT="${HOOK_TIMEOUT_PRE_COMPACT:-5}"
60
- ;;
61
- stop-*.sh)
62
- # Other stop hooks (reflect, sync, grill) are usually fast
63
- HOOK_TIMEOUT="${HOOK_TIMEOUT_STOP:-15}"
64
- ;;
65
- esac
66
-
67
- # ============================================================================
68
- # PROJECT ROOT DETECTION (for logging) - CRITICAL: must NOT fallback to pwd!
69
- # ============================================================================
70
-
71
- find_project_root() {
72
- local dir="$PWD"
73
- while [[ "$dir" != "/" ]]; do
74
- if [[ -f "$dir/.specweave/config.json" ]]; then
75
- echo "$dir"
76
- return 0
77
- fi
78
- dir=$(dirname "$dir")
79
- done
80
- # Return empty - NOT pwd (prevents .specweave pollution)
81
- return 1
82
- }
83
-
84
- PROJECT_ROOT=$(find_project_root)
85
-
86
- # Exit early if not a SpecWeave project (prevents .specweave pollution)
87
- if [[ -z "$PROJECT_ROOT" ]] || [[ ! -f "$PROJECT_ROOT/.specweave/config.json" ]]; then
88
- # Not a SpecWeave project - output success JSON and exit
89
- # UserPromptSubmit and Stop hooks need {"decision":"approve"}, others need {"continue":true}
90
- if [[ "$script_name" == stop-* ]] || [[ "$script_name" == user-prompt-submit* ]]; then
91
- echo '{"decision":"approve"}'
92
- else
93
- echo '{"continue": true}'
94
- fi
95
- exit 0
96
- fi
97
-
98
- LOGS_DIR="$PROJECT_ROOT/.specweave/logs"
99
- WARNING_LOG="$LOGS_DIR/hook-warnings.log"
100
- mkdir -p "$LOGS_DIR" 2>/dev/null || true
101
-
102
- # ============================================================================
103
- # JSON UTILITIES (v1.0.102+)
104
- # ============================================================================
105
-
106
- # Escape string for safe JSON embedding
107
- escape_json() {
108
- local str="$1"
109
- # Escape backslash, double quote, newline, carriage return, tab
110
- str="${str//\\/\\\\}" # Backslash
111
- str="${str//\"/\\\"}" # Double quote
112
- str="${str//$'\n'/\\n}" # Newline
113
- str="${str//$'\r'/\\r}" # Carriage return
114
- str="${str//$'\t'/\\t}" # Tab
115
- echo "$str"
116
- }
117
-
118
- # ============================================================================
119
- # LOGGING
120
- # ============================================================================
121
-
122
- log_warning() {
123
- local script="$1"
124
- local message="$2"
125
- local details="${3:-}"
126
-
127
- local script_name
128
- script_name=$(basename "$script" 2>/dev/null || echo "unknown")
129
-
130
- # Log to file only (v1.0.102+: warnings now in JSON response)
131
- {
132
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING [fail-fast-wrapper]: $message"
133
- echo " Script: $script_name"
134
- [[ -n "$details" ]] && echo " Details: $details"
135
- } >> "$WARNING_LOG" 2>/dev/null || true
136
-
137
- # NOTE: No console output - warnings are in JSON response
138
- }
139
-
140
- # ============================================================================
141
- # SAFE OUTPUT GENERATION (v1.0.102+)
142
- # ============================================================================
143
-
144
- # Safe output with warnings (JSON-escaped)
145
- get_safe_output_with_warnings() {
146
- local script="$1"
147
- local severity="$2"
148
- local message="$3"
149
- local recommendation="$4"
150
-
151
- local script_name
152
- script_name=$(basename "$script" 2>/dev/null || echo "unknown")
153
-
154
- # Escape all strings for JSON safety
155
- local escaped_name
156
- local escaped_message
157
- local escaped_recommendation
158
- escaped_name=$(escape_json "$script_name")
159
- escaped_message=$(escape_json "$message")
160
- escaped_recommendation=$(escape_json "$recommendation")
161
-
162
- # Build JSON with warnings array
163
- # Detect correct response format based on hook lifecycle phase:
164
- # - PreToolUse dispatchers/guards: {"decision":"allow"} or {"decision":"block"}
165
- # - PostToolUse dispatchers: {"continue":true}
166
- # - Stop/UserPromptSubmit: {"decision":"approve"}
167
- local base_script
168
- base_script=$(basename "$script" 2>/dev/null)
169
- if [[ "$script" == *"guard"* ]] || [[ "$script" == *"validator"* ]] || [[ "$base_script" == pre-tool-use* ]]; then
170
- cat <<EOF
171
- {"decision":"allow","warnings":[{"severity":"${severity}","message":"${escaped_name}: ${escaped_message}","recommendation":"${escaped_recommendation}"}]}
172
- EOF
173
- elif [[ "$base_script" == stop-* ]] || [[ "$base_script" == user-prompt-submit* ]]; then
174
- cat <<EOF
175
- {"decision":"approve","warnings":[{"severity":"${severity}","message":"${escaped_name}: ${escaped_message}","recommendation":"${escaped_recommendation}"}]}
176
- EOF
177
- else
178
- cat <<EOF
179
- {"continue":true,"warnings":[{"severity":"${severity}","message":"${escaped_name}: ${escaped_message}","recommendation":"${escaped_recommendation}"}]}
180
- EOF
181
- fi
182
- }
183
-
184
- # Safe output without warnings (success case)
185
- get_safe_output() {
186
- local script="$1"
187
- local base_script
188
- base_script=$(basename "$script" 2>/dev/null)
189
- if [[ "$script" == *"guard"* ]] || [[ "$script" == *"validator"* ]] || [[ "$base_script" == pre-tool-use* ]]; then
190
- echo '{"decision":"allow","warnings":[]}'
191
- elif [[ "$base_script" == stop-* ]] || [[ "$base_script" == user-prompt-submit* ]]; then
192
- echo '{"decision":"approve","warnings":[]}'
193
- else
194
- echo '{"continue":true,"warnings":[]}'
195
- fi
196
- }
197
-
198
- # ============================================================================
199
- # MAIN EXECUTION
200
- # ============================================================================
201
-
202
- script="$1"
203
- shift
204
-
205
- # No script = safe default
206
- if [[ -z "$script" ]]; then
207
- echo '{"continue":true}'
208
- exit 0
209
- fi
210
-
211
- # Script doesn't exist = safe default with warning
212
- if [[ ! -f "$script" ]]; then
213
- log_warning "$script" "Script not found (may be refreshing)" "Path: $script"
214
- get_safe_output_with_warnings "$script" "WARNING" "Script not found (may be refreshing)" "Run 'specweave refresh-marketplace' to update plugins"
215
- exit 0
216
- fi
217
-
218
- # Read stdin with non-blocking timeout (v1.0.102+)
219
- # Uses Perl for universal cross-platform support
220
- stdin_content=""
221
- if command -v perl >/dev/null 2>&1; then
222
- # Perl-based timeout (works on all Unix systems)
223
- stdin_content=$(perl -e '
224
- use strict;
225
- use warnings;
226
-
227
- eval {
228
- local $SIG{ALRM} = sub { die "timeout\n" };
229
- alarm(1);
230
- local $/;
231
- my $input = <STDIN>;
232
- alarm(0);
233
- print $input;
234
- };
235
- if ($@) {
236
- print "{}";
237
- }
238
- ' 2>/dev/null || echo '{}')
239
- elif command -v gtimeout >/dev/null 2>&1; then
240
- stdin_content=$(gtimeout 1 cat 2>/dev/null || echo '{}')
241
- elif command -v timeout >/dev/null 2>&1; then
242
- stdin_content=$(timeout 1 cat 2>/dev/null || echo '{}')
243
- else
244
- # Last resort: try reading without blocking (may hang if stdin has data)
245
- stdin_content=$(cat 2>/dev/null || echo '{}')
246
- fi
247
-
248
- # Run with timeout (v1.0.102+: Universal cross-platform support)
249
- tmp_out=$(mktemp -t hook-out.XXXXXX 2>/dev/null || echo "/tmp/hook-out-$$")
250
- tmp_err=$(mktemp -t hook-err.XXXXXX 2>/dev/null || echo "/tmp/hook-err-$$")
251
-
252
- # Try GNU timeout (Linux), then BSD timeout (macOS with coreutils), then Perl fallback
253
- if command -v gtimeout >/dev/null 2>&1; then
254
- # GNU timeout (Linux, coreutils on macOS)
255
- echo "$stdin_content" | gtimeout --kill-after=2 "$HOOK_TIMEOUT" bash "$script" "$@" > "$tmp_out" 2> "$tmp_err"
256
- exit_code=$?
257
- elif command -v timeout >/dev/null 2>&1; then
258
- # BSD timeout (some systems)
259
- echo "$stdin_content" | timeout --kill-after=2 "$HOOK_TIMEOUT" bash "$script" "$@" > "$tmp_out" 2> "$tmp_err"
260
- exit_code=$?
261
- else
262
- # Bash-only fallback (for systems without timeout/gtimeout - e.g., macOS)
263
- # Run hook in background
264
- echo "$stdin_content" | bash "$script" "$@" > "$tmp_out" 2> "$tmp_err" &
265
- script_pid=$!
266
-
267
- # Wait with timeout using simple loop
268
- elapsed=0
269
- while kill -0 $script_pid 2>/dev/null && [ $elapsed -lt $HOOK_TIMEOUT ]; do
270
- sleep 1
271
- elapsed=$((elapsed + 1))
272
- done
273
-
274
- # Check if still running (timeout occurred)
275
- if kill -0 $script_pid 2>/dev/null; then
276
- # Timeout - kill the process
277
- kill -TERM $script_pid 2>/dev/null
278
- sleep 2
279
- kill -KILL $script_pid 2>/dev/null
280
- wait $script_pid 2>/dev/null
281
- exit_code=124 # Timeout exit code
282
- else
283
- # Process finished - get exit code
284
- wait $script_pid
285
- exit_code=$?
286
- fi
287
- fi
288
-
289
- output=$(cat "$tmp_out" 2>/dev/null)
290
- errors=$(cat "$tmp_err" 2>/dev/null)
291
- rm -f "$tmp_out" "$tmp_err" 2>/dev/null
292
-
293
- # Handle different exit scenarios
294
- case $exit_code in
295
- 0)
296
- # Success - return output or safe default with empty warnings
297
- if [[ -n "$output" ]]; then
298
- echo "$output"
299
- else
300
- get_safe_output "$script"
301
- fi
302
- ;;
303
-
304
- 124|137)
305
- # Timeout (124 = timeout, 137 = SIGKILL)
306
- log_warning "$script" "Hook timed out after ${HOOK_TIMEOUT}s" "Consider optimizing or increasing HOOK_TIMEOUT"
307
- get_safe_output_with_warnings "$script" "WARNING" "Hook timed out after ${HOOK_TIMEOUT}s" "Consider optimizing or increasing HOOK_TIMEOUT"
308
- ;;
309
-
310
- *)
311
- # Other error
312
- error_msg="Hook failed (exit $exit_code)"
313
- recommendation="Run 'specweave check-hooks' to diagnose issues"
314
-
315
- if [[ -n "$errors" ]]; then
316
- log_warning "$script" "$error_msg" "$errors"
317
- get_safe_output_with_warnings "$script" "ERROR" "$error_msg: $errors" "$recommendation"
318
- else
319
- log_warning "$script" "$error_msg"
320
- get_safe_output_with_warnings "$script" "ERROR" "$error_msg" "$recommendation"
321
- fi
322
- ;;
323
- esac
324
-
325
- exit 0
@@ -1,26 +0,0 @@
1
- @echo off
2
- REM hook-wrapper.cmd - Windows resilient hook launcher
3
- REM Prevents crashes when dispatcher.mjs is temporarily unavailable
4
-
5
- setlocal enabledelayedexpansion
6
-
7
- set "HOOK_TYPE=%~1"
8
- if "%HOOK_TYPE%"=="" set "HOOK_TYPE=unknown"
9
-
10
- set "SCRIPT_DIR=%~dp0"
11
- set "DISPATCHER=%SCRIPT_DIR%dispatcher.mjs"
12
-
13
- REM Check if dispatcher exists
14
- if not exist "%DISPATCHER%" (
15
- echo {"continue":true,"systemMessage":"Hook skipped: dispatcher.mjs not found"}
16
- exit /b 0
17
- )
18
-
19
- REM Run dispatcher with error suppression
20
- node "%DISPATCHER%" "%HOOK_TYPE%" 2>nul
21
- if errorlevel 1 (
22
- echo {"continue":true,"systemMessage":"Hook error, continuing"}
23
- exit /b 0
24
- )
25
-
26
- exit /b 0