specweave 0.30.19 → 0.32.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.
- package/CLAUDE.md +176 -2
- package/README.md +22 -0
- package/bin/specweave.js +18 -1
- package/dist/src/cli/commands/cache.d.ts +17 -0
- package/dist/src/cli/commands/cache.d.ts.map +1 -0
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/cache.js.map +1 -0
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/plan/increment-detector.js +2 -2
- package/dist/src/cli/commands/plan/increment-detector.js.map +1 -1
- package/dist/src/cli/commands/sync-spec-commits.js +1 -1
- package/dist/src/cli/commands/sync-spec-commits.js.map +1 -1
- package/dist/src/cli/commands/sync-specs.js +2 -2
- package/dist/src/cli/commands/sync-specs.js.map +1 -1
- package/dist/src/cli/helpers/github/increment-profile-selector.js +1 -1
- package/dist/src/cli/helpers/github/increment-profile-selector.js.map +1 -1
- package/dist/src/cli/workers/living-docs-worker.js +66 -1
- package/dist/src/cli/workers/living-docs-worker.js.map +1 -1
- package/dist/src/config/types.d.ts +203 -1208
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/core/discrepancy/increment-generator.d.ts.map +1 -1
- package/dist/src/core/discrepancy/increment-generator.js +5 -2
- package/dist/src/core/discrepancy/increment-generator.js.map +1 -1
- package/dist/src/core/external-tools/external-items-counter.d.ts +62 -0
- package/dist/src/core/external-tools/external-items-counter.d.ts.map +1 -0
- package/dist/src/core/external-tools/external-items-counter.js +206 -0
- package/dist/src/core/external-tools/external-items-counter.js.map +1 -0
- package/dist/src/core/external-tools/external-items-display.d.ts +39 -0
- package/dist/src/core/external-tools/external-items-display.d.ts.map +1 -0
- package/dist/src/core/external-tools/external-items-display.js +185 -0
- package/dist/src/core/external-tools/external-items-display.js.map +1 -0
- package/dist/src/core/external-tools/index.d.ts +8 -0
- package/dist/src/core/external-tools/index.d.ts.map +1 -0
- package/dist/src/core/external-tools/index.js +8 -0
- package/dist/src/core/external-tools/index.js.map +1 -0
- package/dist/src/core/external-tools/providers/ado-items-adapter.d.ts +39 -0
- package/dist/src/core/external-tools/providers/ado-items-adapter.d.ts.map +1 -0
- package/dist/src/core/external-tools/providers/ado-items-adapter.js +188 -0
- package/dist/src/core/external-tools/providers/ado-items-adapter.js.map +1 -0
- package/dist/src/core/external-tools/providers/github-items-adapter.d.ts +38 -0
- package/dist/src/core/external-tools/providers/github-items-adapter.d.ts.map +1 -0
- package/dist/src/core/external-tools/providers/github-items-adapter.js +136 -0
- package/dist/src/core/external-tools/providers/github-items-adapter.js.map +1 -0
- package/dist/src/core/external-tools/providers/index.d.ts +7 -0
- package/dist/src/core/external-tools/providers/index.d.ts.map +1 -0
- package/dist/src/core/external-tools/providers/index.js +7 -0
- package/dist/src/core/external-tools/providers/index.js.map +1 -0
- package/dist/src/core/external-tools/providers/jira-items-adapter.d.ts +42 -0
- package/dist/src/core/external-tools/providers/jira-items-adapter.d.ts.map +1 -0
- package/dist/src/core/external-tools/providers/jira-items-adapter.js +153 -0
- package/dist/src/core/external-tools/providers/jira-items-adapter.js.map +1 -0
- package/dist/src/core/external-tools/types.d.ts +78 -0
- package/dist/src/core/external-tools/types.d.ts.map +1 -0
- package/dist/src/core/external-tools/types.js +19 -0
- package/dist/src/core/external-tools/types.js.map +1 -0
- package/dist/src/core/increment/duplicate-detector.js +2 -2
- package/dist/src/core/increment/duplicate-detector.js.map +1 -1
- package/dist/src/core/increment/increment-archiver.d.ts +24 -0
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
- package/dist/src/core/increment/increment-archiver.js +59 -2
- package/dist/src/core/increment/increment-archiver.js.map +1 -1
- package/dist/src/core/increment/increment-status.js +2 -2
- package/dist/src/core/increment/increment-status.js.map +1 -1
- package/dist/src/core/increment/increment-utils.d.ts +98 -37
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +119 -68
- package/dist/src/core/increment/increment-utils.js.map +1 -1
- package/dist/src/core/increment/metadata-validator.js +1 -1
- package/dist/src/core/increment/metadata-validator.js.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.js +4 -0
- package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
- package/dist/src/core/living-docs/hierarchy-mapper.js +3 -3
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts +18 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +247 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts +15 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js +138 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.d.ts +24 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.js +198 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.d.ts +17 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.js +241 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.d.ts +28 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.js +197 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts +18 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +154 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.d.ts +42 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.js +343 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +146 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.js +7 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.js.map +1 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts +5 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +36 -2
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/llm/providers/azure-openai-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/azure-openai-provider.js +1 -0
- package/dist/src/core/llm/providers/azure-openai-provider.js.map +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.js +2 -0
- package/dist/src/core/llm/providers/bedrock-provider.js.map +1 -1
- package/dist/src/core/llm/providers/openai-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/openai-provider.js +1 -0
- package/dist/src/core/llm/providers/openai-provider.js.map +1 -1
- package/dist/src/core/llm/providers/vertex-ai-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/vertex-ai-provider.js +1 -0
- package/dist/src/core/llm/providers/vertex-ai-provider.js.map +1 -1
- package/dist/src/core/sync/spec-increment-mapper.js +3 -3
- package/dist/src/core/sync/spec-increment-mapper.js.map +1 -1
- package/dist/src/importers/item-converter.d.ts +25 -0
- package/dist/src/importers/item-converter.d.ts.map +1 -1
- package/dist/src/importers/item-converter.js +135 -5
- package/dist/src/importers/item-converter.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +33 -140
- package/dist/src/init/architecture/types.d.ts.map +1 -1
- package/dist/src/init/compliance/types.d.ts +30 -27
- package/dist/src/init/compliance/types.d.ts.map +1 -1
- package/dist/src/init/repo/types.d.ts +11 -34
- package/dist/src/init/repo/types.d.ts.map +1 -1
- package/dist/src/init/research/src/config/types.d.ts +15 -82
- package/dist/src/init/research/src/config/types.d.ts.map +1 -1
- package/dist/src/init/research/types.d.ts +38 -93
- package/dist/src/init/research/types.d.ts.map +1 -1
- package/dist/src/init/team/types.d.ts +4 -42
- package/dist/src/init/team/types.d.ts.map +1 -1
- package/dist/src/types/dashboard-cache.d.ts +181 -0
- package/dist/src/types/dashboard-cache.d.ts.map +1 -0
- package/dist/src/types/dashboard-cache.js +65 -0
- package/dist/src/types/dashboard-cache.js.map +1 -0
- package/dist/src/utils/docs-validator.d.ts +131 -0
- package/dist/src/utils/docs-validator.d.ts.map +1 -0
- package/dist/src/utils/docs-validator.js +529 -0
- package/dist/src/utils/docs-validator.js.map +1 -0
- package/dist/src/utils/feature-id-collision.js +1 -1
- package/dist/src/utils/feature-id-collision.js.map +1 -1
- package/dist/src/utils/html-to-mdx.d.ts +1 -0
- package/dist/src/utils/html-to-mdx.d.ts.map +1 -1
- package/dist/src/utils/html-to-mdx.js +43 -5
- package/dist/src/utils/html-to-mdx.js.map +1 -1
- package/package.json +1 -5
- package/plugins/specweave/agents/pm/AGENT.md +10 -7
- package/plugins/specweave/commands/specweave-archive-features.md +5 -7
- package/plugins/specweave/commands/specweave-archive.md +2 -1
- package/plugins/specweave/commands/specweave-do.md +35 -1
- package/plugins/specweave/commands/specweave-done.md +96 -0
- package/plugins/specweave/commands/specweave-external.md +150 -0
- package/plugins/specweave/commands/specweave-import-external.md +45 -18
- package/plugins/specweave/commands/specweave-increment.md +331 -33
- package/plugins/specweave/commands/specweave-jobs.md +2 -2
- package/plugins/specweave/commands/specweave-progress.md +4 -4
- package/plugins/specweave/commands/specweave-restore-feature.md +5 -4
- package/plugins/specweave/commands/specweave-sync-docs.md +1 -1
- package/plugins/specweave/commands/specweave-sync-specs.md +216 -322
- package/plugins/specweave/commands/specweave-validate-features.md +13 -8
- package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
- package/plugins/specweave/hooks/hooks.json +33 -4
- package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
- package/plugins/specweave/hooks/lib/common-setup.sh +375 -0
- package/plugins/specweave/hooks/lib/crash-prevention.sh +336 -0
- package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
- package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
- package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
- package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
- package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
- package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
- package/plugins/specweave/hooks/post-task-completion.sh +4 -23
- package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +1 -6
- package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
- package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
- package/plugins/specweave/hooks/pre-task-completion.sh +8 -37
- package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
- package/plugins/specweave/hooks/pre-tool-use.sh +2 -11
- package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
- package/plugins/specweave/hooks/universal/dispatcher.mjs +135 -42
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +183 -0
- package/plugins/specweave/hooks/universal/hook-wrapper.cmd +26 -26
- package/plugins/specweave/hooks/universal/session-start.cmd +16 -16
- package/plugins/specweave/hooks/universal/session-start.ps1 +16 -16
- package/plugins/specweave/hooks/user-prompt-submit.sh +140 -38
- package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +12 -0
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +89 -0
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +211 -0
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +163 -0
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +26 -28
- package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +50 -0
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js +2 -2
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js.map +1 -1
- package/plugins/specweave/scripts/README.md +166 -0
- package/plugins/specweave/scripts/cleanup-state.sh +142 -0
- package/plugins/specweave/scripts/force-kill.sh +142 -0
- package/plugins/specweave/scripts/jobs.js +171 -0
- package/plugins/specweave/scripts/progress.js +170 -0
- package/plugins/specweave/scripts/read-costs.sh +132 -0
- package/plugins/specweave/scripts/read-jobs.sh +324 -0
- package/plugins/specweave/scripts/read-progress.sh +185 -0
- package/plugins/specweave/scripts/read-status.sh +146 -0
- package/plugins/specweave/scripts/read-workflow.sh +173 -0
- package/plugins/specweave/scripts/rebuild-dashboard-cache.sh +327 -0
- package/plugins/specweave/scripts/session-watchdog.sh +192 -0
- package/plugins/specweave/scripts/status.js +154 -0
- package/plugins/specweave/scripts/update-dashboard-cache.sh +281 -0
- package/plugins/specweave/skills/increment-planner/SKILL.md +333 -24
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +17 -9
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +6 -2
- package/plugins/specweave/skills/instant-status/SKILL.md +70 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
- package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-docs/commands/build.md +32 -4
- package/plugins/specweave-docs/commands/preview.md +43 -1
- package/plugins/specweave-docs/commands/validate.md +250 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1262 -626
- package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
- package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +134 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1254 -939
- package/plugins/specweave-release/hooks/post-task-completion.sh.backup +110 -0
- package/plugins/specweave/hooks/post-edit-spec.sh +0 -265
- package/plugins/specweave/hooks/post-write-spec.sh +0 -267
- package/plugins/specweave/hooks/pre-edit-spec.sh +0 -151
- package/plugins/specweave/hooks/pre-write-spec.sh +0 -151
|
@@ -167,11 +167,7 @@ fi
|
|
|
167
167
|
if [ "$COMPLETING_TASK" = "false" ]; then
|
|
168
168
|
echo "[$(date)] ⏭️ No tasks being completed, skipping validation" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
169
169
|
rm -f "$STDIN_DATA"
|
|
170
|
-
|
|
171
|
-
{
|
|
172
|
-
"continue": true
|
|
173
|
-
}
|
|
174
|
-
EOF
|
|
170
|
+
echo '{"continue":true}'
|
|
175
171
|
exit 0
|
|
176
172
|
fi
|
|
177
173
|
|
|
@@ -184,11 +180,7 @@ CURRENT_INCREMENT=$(ls -td .specweave/increments/*/ 2>/dev/null | xargs -n1 base
|
|
|
184
180
|
if [ -z "$CURRENT_INCREMENT" ]; then
|
|
185
181
|
echo "[$(date)] ℹ️ No active increment found, skipping validation" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
186
182
|
rm -f "$STDIN_DATA"
|
|
187
|
-
|
|
188
|
-
{
|
|
189
|
-
"continue": true
|
|
190
|
-
}
|
|
191
|
-
EOF
|
|
183
|
+
echo '{"continue":true}'
|
|
192
184
|
exit 0
|
|
193
185
|
fi
|
|
194
186
|
|
|
@@ -197,11 +189,7 @@ TASKS_MD=".specweave/increments/$CURRENT_INCREMENT/tasks.md"
|
|
|
197
189
|
if [ ! -f "$TASKS_MD" ]; then
|
|
198
190
|
echo "[$(date)] ℹ️ tasks.md not found for $CURRENT_INCREMENT (increment may be in planning stage)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
199
191
|
rm -f "$STDIN_DATA"
|
|
200
|
-
|
|
201
|
-
{
|
|
202
|
-
"continue": true
|
|
203
|
-
}
|
|
204
|
-
EOF
|
|
192
|
+
echo '{"continue":true}'
|
|
205
193
|
exit 0
|
|
206
194
|
fi
|
|
207
195
|
|
|
@@ -231,12 +219,7 @@ fi
|
|
|
231
219
|
if [ -z "$VALIDATOR_SCRIPT" ] || ! command -v node &> /dev/null; then
|
|
232
220
|
echo "[$(date)] ⚠️ AC test validator not found or Node.js missing" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
233
221
|
rm -f "$STDIN_DATA"
|
|
234
|
-
|
|
235
|
-
{
|
|
236
|
-
"continue": true,
|
|
237
|
-
"systemMessage": "⚠️ Warning: AC test validator not available. Task completion validation skipped. Install Node.js and rebuild SpecWeave to enable validation."
|
|
238
|
-
}
|
|
239
|
-
EOF
|
|
222
|
+
echo '{"continue":true,"systemMessage":"⚠️ Warning: AC test validator not available. Task completion validation skipped. Install Node.js and rebuild SpecWeave to enable validation."}'
|
|
240
223
|
exit 0
|
|
241
224
|
fi
|
|
242
225
|
|
|
@@ -262,16 +245,11 @@ if [ "$VALIDATION_EXIT_CODE" = "0" ]; then
|
|
|
262
245
|
# Reset circuit breaker on success
|
|
263
246
|
echo "0" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true
|
|
264
247
|
|
|
265
|
-
VALIDATION_SUMMARY=$(cat "$VALIDATION_OUTPUT" | tail -5 | tr '\n' ' ')
|
|
248
|
+
VALIDATION_SUMMARY=$(cat "$VALIDATION_OUTPUT" | tail -5 | tr '\n' ' ' | sed 's/"/\\"/g')
|
|
266
249
|
|
|
267
250
|
rm -f "$VALIDATION_OUTPUT"
|
|
268
251
|
|
|
269
|
-
|
|
270
|
-
{
|
|
271
|
-
"continue": true,
|
|
272
|
-
"systemMessage": "✅ AC Test Validation Passed: All acceptance criteria have passing tests. Task completion allowed. ${VALIDATION_SUMMARY}"
|
|
273
|
-
}
|
|
274
|
-
EOF
|
|
252
|
+
printf '{"continue":true,"systemMessage":"✅ AC Test Validation Passed: All acceptance criteria have passing tests. Task completion allowed. %s"}\n' "$VALIDATION_SUMMARY"
|
|
275
253
|
else
|
|
276
254
|
# Validation failed - block completion
|
|
277
255
|
echo "[$(date)] ❌ AC test validation failed" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
@@ -280,18 +258,11 @@ else
|
|
|
280
258
|
CURRENT_FAILURES=$(cat "$CIRCUIT_BREAKER_FILE" 2>/dev/null || echo 0)
|
|
281
259
|
echo "$((CURRENT_FAILURES + 1))" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true
|
|
282
260
|
|
|
283
|
-
VALIDATION_ERROR=$(cat "$VALIDATION_OUTPUT" | grep -A 10 "VALIDATION FAILED" | tr '\n' ' ' | cut -c 1-300)
|
|
261
|
+
VALIDATION_ERROR=$(cat "$VALIDATION_OUTPUT" | grep -A 10 "VALIDATION FAILED" | tr '\n' ' ' | cut -c 1-300 | sed 's/"/\\"/g')
|
|
284
262
|
|
|
285
263
|
rm -f "$VALIDATION_OUTPUT"
|
|
286
264
|
|
|
287
|
-
|
|
288
|
-
{
|
|
289
|
-
"continue": false,
|
|
290
|
-
"systemMessage": "❌ AC TEST VALIDATION FAILED: Cannot mark task as complete until all acceptance criteria have passing tests. ${VALIDATION_ERROR}
|
|
291
|
-
|
|
292
|
-
Fix the failing tests and try again. Run tests manually: npm test"
|
|
293
|
-
}
|
|
294
|
-
EOF
|
|
265
|
+
printf '{"continue":false,"systemMessage":"❌ AC TEST VALIDATION FAILED: Cannot mark task as complete until all acceptance criteria have passing tests. %s\\n\\nFix the failing tests and try again. Run tests manually: npm test"}\n' "$VALIDATION_ERROR"
|
|
295
266
|
fi
|
|
296
267
|
|
|
297
268
|
# ALWAYS exit 0 - NEVER let hook errors crash Claude Code
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# SpecWeave Pre-Task-Completion Hook
|
|
4
|
+
# CRITICAL QUALITY GATE: Validates AC tests before allowing task completion
|
|
5
|
+
#
|
|
6
|
+
# Runs automatically BEFORE any task is marked complete via TodoWrite
|
|
7
|
+
#
|
|
8
|
+
# WORKFLOW:
|
|
9
|
+
# =========
|
|
10
|
+
# 1. TodoWrite called with status="completed"
|
|
11
|
+
# 2. This hook fires (pre-completion validation)
|
|
12
|
+
# 3. Extract task ID from TodoWrite input
|
|
13
|
+
# 4. Find task in tasks.md
|
|
14
|
+
# 5. Run AC test validator
|
|
15
|
+
# 6. If tests PASS → Allow completion (continue: true)
|
|
16
|
+
# 7. If tests FAIL → Block completion (continue: false, show error)
|
|
17
|
+
#
|
|
18
|
+
# ENFORCEMENT:
|
|
19
|
+
# ============
|
|
20
|
+
# This is the ONLY way to mark tasks complete in SpecWeave.
|
|
21
|
+
# Manual edits to tasks.md are detected and flagged by pre-commit hooks.
|
|
22
|
+
|
|
23
|
+
set -e
|
|
24
|
+
|
|
25
|
+
# Find project root
|
|
26
|
+
find_project_root() {
|
|
27
|
+
local dir="$1"
|
|
28
|
+
while [ "$dir" != "/" ]; do
|
|
29
|
+
if [ -d "$dir/.specweave" ]; then
|
|
30
|
+
echo "$dir"
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
dir="$(dirname "$dir")"
|
|
34
|
+
done
|
|
35
|
+
pwd
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
|
|
39
|
+
cd "$PROJECT_ROOT" 2>/dev/null || true
|
|
40
|
+
|
|
41
|
+
# ============================================================================
|
|
42
|
+
# CONFIGURATION
|
|
43
|
+
# ============================================================================
|
|
44
|
+
|
|
45
|
+
LOGS_DIR=".specweave/logs"
|
|
46
|
+
DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
|
|
47
|
+
|
|
48
|
+
mkdir -p "$LOGS_DIR" 2>/dev/null || true
|
|
49
|
+
|
|
50
|
+
echo "[$(date)] 🔒 Pre-task-completion hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
51
|
+
|
|
52
|
+
# ============================================================================
|
|
53
|
+
# CAPTURE INPUT
|
|
54
|
+
# ============================================================================
|
|
55
|
+
|
|
56
|
+
STDIN_DATA=$(mktemp)
|
|
57
|
+
cat > "$STDIN_DATA"
|
|
58
|
+
|
|
59
|
+
echo "[$(date)] Input JSON:" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
60
|
+
cat "$STDIN_DATA" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
61
|
+
|
|
62
|
+
# ============================================================================
|
|
63
|
+
# CHECK FOR TASK COMPLETION
|
|
64
|
+
# ============================================================================
|
|
65
|
+
|
|
66
|
+
# Only validate if a task is being marked complete
|
|
67
|
+
COMPLETING_TASK=false
|
|
68
|
+
|
|
69
|
+
if command -v jq >/dev/null 2>&1; then
|
|
70
|
+
# Check if any task is transitioning to "completed" status
|
|
71
|
+
COMPLETED_COUNT=$(jq -r '.tool_input.todos // [] | map(select(.status == "completed")) | length' "$STDIN_DATA" 2>/dev/null || echo "0")
|
|
72
|
+
|
|
73
|
+
if [ "$COMPLETED_COUNT" != "0" ]; then
|
|
74
|
+
COMPLETING_TASK=true
|
|
75
|
+
echo "[$(date)] ✓ Detected task completion (${COMPLETED_COUNT} tasks)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# If no tasks being completed, allow without validation
|
|
80
|
+
if [ "$COMPLETING_TASK" = "false" ]; then
|
|
81
|
+
echo "[$(date)] ⏭️ No tasks being completed, skipping validation" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
82
|
+
rm -f "$STDIN_DATA"
|
|
83
|
+
cat <<EOF
|
|
84
|
+
{
|
|
85
|
+
"continue": true
|
|
86
|
+
}
|
|
87
|
+
EOF
|
|
88
|
+
exit 0
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# ============================================================================
|
|
92
|
+
# DETECT CURRENT INCREMENT
|
|
93
|
+
# ============================================================================
|
|
94
|
+
|
|
95
|
+
CURRENT_INCREMENT=$(ls -td .specweave/increments/*/ 2>/dev/null | xargs -n1 basename | grep -v "_backlog" | grep -v "_archive" | grep -v "_working" | head -1)
|
|
96
|
+
|
|
97
|
+
if [ -z "$CURRENT_INCREMENT" ]; then
|
|
98
|
+
echo "[$(date)] ℹ️ No active increment found, skipping validation" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
99
|
+
rm -f "$STDIN_DATA"
|
|
100
|
+
cat <<EOF
|
|
101
|
+
{
|
|
102
|
+
"continue": true
|
|
103
|
+
}
|
|
104
|
+
EOF
|
|
105
|
+
exit 0
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
TASKS_MD=".specweave/increments/$CURRENT_INCREMENT/tasks.md"
|
|
109
|
+
|
|
110
|
+
if [ ! -f "$TASKS_MD" ]; then
|
|
111
|
+
echo "[$(date)] ℹ️ tasks.md not found for $CURRENT_INCREMENT (increment may be in planning stage)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
112
|
+
rm -f "$STDIN_DATA"
|
|
113
|
+
cat <<EOF
|
|
114
|
+
{
|
|
115
|
+
"continue": true
|
|
116
|
+
}
|
|
117
|
+
EOF
|
|
118
|
+
exit 0
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# ============================================================================
|
|
122
|
+
# RUN AC TEST VALIDATION
|
|
123
|
+
# ============================================================================
|
|
124
|
+
|
|
125
|
+
echo "[$(date)] 🧪 Running AC test validation for $CURRENT_INCREMENT" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
126
|
+
|
|
127
|
+
# Determine which validation script to use
|
|
128
|
+
VALIDATOR_SCRIPT=""
|
|
129
|
+
if [ -f "$PROJECT_ROOT/dist/src/core/ac-test-validator-cli.js" ]; then
|
|
130
|
+
VALIDATOR_SCRIPT="$PROJECT_ROOT/dist/src/core/ac-test-validator-cli.js"
|
|
131
|
+
elif [ -f "$PROJECT_ROOT/node_modules/specweave/dist/src/core/ac-test-validator-cli.js" ]; then
|
|
132
|
+
VALIDATOR_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/src/core/ac-test-validator-cli.js"
|
|
133
|
+
elif [ -n "${CLAUDE_PLUGIN_ROOT}" ] && [ -f "${CLAUDE_PLUGIN_ROOT}/dist/src/core/ac-test-validator-cli.js" ]; then
|
|
134
|
+
VALIDATOR_SCRIPT="${CLAUDE_PLUGIN_ROOT}/dist/src/core/ac-test-validator-cli.js"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [ -z "$VALIDATOR_SCRIPT" ] || ! command -v node &> /dev/null; then
|
|
138
|
+
echo "[$(date)] ⚠️ AC test validator not found or Node.js missing" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
139
|
+
rm -f "$STDIN_DATA"
|
|
140
|
+
cat <<EOF
|
|
141
|
+
{
|
|
142
|
+
"continue": true,
|
|
143
|
+
"systemMessage": "⚠️ Warning: AC test validator not available. Task completion validation skipped. Install Node.js and rebuild SpecWeave to enable validation."
|
|
144
|
+
}
|
|
145
|
+
EOF
|
|
146
|
+
exit 0
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# Run validator (captures exit code)
|
|
150
|
+
VALIDATION_OUTPUT=$(mktemp)
|
|
151
|
+
VALIDATION_EXIT_CODE=0
|
|
152
|
+
|
|
153
|
+
(cd "$PROJECT_ROOT" && node "$VALIDATOR_SCRIPT" "$CURRENT_INCREMENT") > "$VALIDATION_OUTPUT" 2>&1 || VALIDATION_EXIT_CODE=$?
|
|
154
|
+
|
|
155
|
+
echo "[$(date)] Validator exit code: $VALIDATION_EXIT_CODE" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
156
|
+
cat "$VALIDATION_OUTPUT" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
157
|
+
|
|
158
|
+
rm -f "$STDIN_DATA"
|
|
159
|
+
|
|
160
|
+
# ============================================================================
|
|
161
|
+
# DECISION LOGIC
|
|
162
|
+
# ============================================================================
|
|
163
|
+
|
|
164
|
+
if [ "$VALIDATION_EXIT_CODE" = "0" ]; then
|
|
165
|
+
# Validation passed - allow completion
|
|
166
|
+
echo "[$(date)] ✅ AC test validation passed" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
167
|
+
|
|
168
|
+
VALIDATION_SUMMARY=$(cat "$VALIDATION_OUTPUT" | tail -5 | tr '\n' ' ')
|
|
169
|
+
|
|
170
|
+
rm -f "$VALIDATION_OUTPUT"
|
|
171
|
+
|
|
172
|
+
cat <<EOF
|
|
173
|
+
{
|
|
174
|
+
"continue": true,
|
|
175
|
+
"systemMessage": "✅ AC Test Validation Passed: All acceptance criteria have passing tests. Task completion allowed. ${VALIDATION_SUMMARY}"
|
|
176
|
+
}
|
|
177
|
+
EOF
|
|
178
|
+
else
|
|
179
|
+
# Validation failed - block completion
|
|
180
|
+
echo "[$(date)] ❌ AC test validation failed" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
181
|
+
|
|
182
|
+
VALIDATION_ERROR=$(cat "$VALIDATION_OUTPUT" | grep -A 10 "VALIDATION FAILED" | tr '\n' ' ' | cut -c 1-300)
|
|
183
|
+
|
|
184
|
+
rm -f "$VALIDATION_OUTPUT"
|
|
185
|
+
|
|
186
|
+
cat <<EOF
|
|
187
|
+
{
|
|
188
|
+
"continue": false,
|
|
189
|
+
"systemMessage": "❌ AC TEST VALIDATION FAILED: Cannot mark task as complete until all acceptance criteria have passing tests. ${VALIDATION_ERROR}
|
|
190
|
+
|
|
191
|
+
Fix the failing tests and try again. Run tests manually: npm test"
|
|
192
|
+
}
|
|
193
|
+
EOF
|
|
194
|
+
fi
|
|
@@ -128,18 +128,9 @@ rm -f "$STDIN_DATA"
|
|
|
128
128
|
# ============================================================================
|
|
129
129
|
|
|
130
130
|
if [ "$TOOL_NAME" = "AskUserQuestion" ]; then
|
|
131
|
-
|
|
132
|
-
{
|
|
133
|
-
"continue": true,
|
|
134
|
-
"systemMessage": "🔔 Sound played - user notified of question request"
|
|
135
|
-
}
|
|
136
|
-
EOF
|
|
131
|
+
echo '{"continue":true,"systemMessage":"🔔 Sound played - user notified of question request"}'
|
|
137
132
|
else
|
|
138
|
-
|
|
139
|
-
{
|
|
140
|
-
"continue": true
|
|
141
|
-
}
|
|
142
|
-
EOF
|
|
133
|
+
echo '{"continue":true}'
|
|
143
134
|
fi
|
|
144
135
|
|
|
145
136
|
# ALWAYS exit 0 - NEVER let hook errors crash Claude Code
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# SpecWeave Pre-Tool-Use Hook
|
|
4
|
+
# Runs BEFORE Claude calls any tool (PreToolUse event)
|
|
5
|
+
#
|
|
6
|
+
# PURPOSE: Detect when Claude asks questions via AskUserQuestion
|
|
7
|
+
# - Plays sound IMMEDIATELY when question is about to be asked
|
|
8
|
+
# - Complements post-task-completion hook (which only fires after TodoWrite)
|
|
9
|
+
# - Ensures user is always notified when Claude needs input
|
|
10
|
+
#
|
|
11
|
+
# SCOPE:
|
|
12
|
+
# - This hook fires for ALL tool calls (Read, Edit, Write, AskUserQuestion, etc.)
|
|
13
|
+
# - We filter for AskUserQuestion specifically to play sound
|
|
14
|
+
# - Non-blocking and fast (<10ms overhead)
|
|
15
|
+
|
|
16
|
+
set -e
|
|
17
|
+
|
|
18
|
+
# ============================================================================
|
|
19
|
+
# CONFIGURATION
|
|
20
|
+
# ============================================================================
|
|
21
|
+
|
|
22
|
+
# Find project root
|
|
23
|
+
find_project_root() {
|
|
24
|
+
local dir="$1"
|
|
25
|
+
while [ "$dir" != "/" ]; do
|
|
26
|
+
if [ -d "$dir/.specweave" ]; then
|
|
27
|
+
echo "$dir"
|
|
28
|
+
return 0
|
|
29
|
+
fi
|
|
30
|
+
dir="$(dirname "$dir")"
|
|
31
|
+
done
|
|
32
|
+
# Fallback
|
|
33
|
+
if [ -d "$(pwd)/.specweave" ]; then
|
|
34
|
+
pwd
|
|
35
|
+
else
|
|
36
|
+
echo "$(pwd)"
|
|
37
|
+
fi
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
|
|
41
|
+
cd "$PROJECT_ROOT" 2>/dev/null || true
|
|
42
|
+
|
|
43
|
+
LOGS_DIR=".specweave/logs"
|
|
44
|
+
DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
|
|
45
|
+
|
|
46
|
+
mkdir -p "$LOGS_DIR" 2>/dev/null || true
|
|
47
|
+
|
|
48
|
+
# ============================================================================
|
|
49
|
+
# CAPTURE INPUT (Tool Call Details)
|
|
50
|
+
# ============================================================================
|
|
51
|
+
|
|
52
|
+
STDIN_DATA=$(mktemp)
|
|
53
|
+
cat > "$STDIN_DATA"
|
|
54
|
+
|
|
55
|
+
# Log the tool call for debugging
|
|
56
|
+
echo "[$(date)] 🔧 PreToolUse hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
57
|
+
echo "[$(date)] Tool call JSON:" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
58
|
+
cat "$STDIN_DATA" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
59
|
+
echo "" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
60
|
+
|
|
61
|
+
# ============================================================================
|
|
62
|
+
# DETECT AskUserQuestion TOOL
|
|
63
|
+
# ============================================================================
|
|
64
|
+
|
|
65
|
+
TOOL_NAME=""
|
|
66
|
+
|
|
67
|
+
if command -v jq >/dev/null 2>&1; then
|
|
68
|
+
# Use jq if available (most reliable)
|
|
69
|
+
TOOL_NAME=$(jq -r '.tool_name // empty' "$STDIN_DATA" 2>/dev/null)
|
|
70
|
+
else
|
|
71
|
+
# Fallback: grep-based detection
|
|
72
|
+
if grep -q '"tool_name"' "$STDIN_DATA" 2>/dev/null; then
|
|
73
|
+
TOOL_NAME=$(grep -o '"tool_name":"[^"]*"' "$STDIN_DATA" | cut -d'"' -f4)
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
echo "[$(date)] Tool name: $TOOL_NAME" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
78
|
+
|
|
79
|
+
# ============================================================================
|
|
80
|
+
# PLAY SOUND IF AskUserQuestion
|
|
81
|
+
# ============================================================================
|
|
82
|
+
|
|
83
|
+
play_sound() {
|
|
84
|
+
case "$(uname -s)" in
|
|
85
|
+
Darwin)
|
|
86
|
+
# macOS: Use afplay with a distinctive sound for questions
|
|
87
|
+
afplay /System/Library/Sounds/Tink.aiff 2>/dev/null || true
|
|
88
|
+
;;
|
|
89
|
+
Linux)
|
|
90
|
+
# Linux: Use paplay or aplay
|
|
91
|
+
paplay /usr/share/sounds/freedesktop/stereo/dialog-question.oga 2>/dev/null || \
|
|
92
|
+
paplay /usr/share/sounds/freedesktop/stereo/message-new-instant.oga 2>/dev/null || \
|
|
93
|
+
aplay /usr/share/sounds/alsa/Front_Center.wav 2>/dev/null || true
|
|
94
|
+
;;
|
|
95
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
96
|
+
# Windows: Use PowerShell
|
|
97
|
+
powershell -c "(New-Object Media.SoundPlayer 'C:\Windows\Media\Windows Notify.wav').PlaySync();" 2>/dev/null || true
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if [ "$TOOL_NAME" = "AskUserQuestion" ]; then
|
|
103
|
+
echo "[$(date)] 🔔 QUESTION DETECTED! Playing notification sound" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
104
|
+
play_sound
|
|
105
|
+
|
|
106
|
+
# Log this event
|
|
107
|
+
echo "[$(date)] Claude is asking for user input via AskUserQuestion" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# ============================================================================
|
|
111
|
+
# CLEANUP
|
|
112
|
+
# ============================================================================
|
|
113
|
+
|
|
114
|
+
rm -f "$STDIN_DATA"
|
|
115
|
+
|
|
116
|
+
# ============================================================================
|
|
117
|
+
# OUTPUT TO CLAUDE (Always continue)
|
|
118
|
+
# ============================================================================
|
|
119
|
+
|
|
120
|
+
if [ "$TOOL_NAME" = "AskUserQuestion" ]; then
|
|
121
|
+
cat <<EOF
|
|
122
|
+
{
|
|
123
|
+
"continue": true,
|
|
124
|
+
"systemMessage": "🔔 Sound played - user notified of question request"
|
|
125
|
+
}
|
|
126
|
+
EOF
|
|
127
|
+
else
|
|
128
|
+
cat <<EOF
|
|
129
|
+
{
|
|
130
|
+
"continue": true
|
|
131
|
+
}
|
|
132
|
+
EOF
|
|
133
|
+
fi
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* - session-start
|
|
13
13
|
* - post-tool-use
|
|
14
14
|
* - completion-guard
|
|
15
|
+
* - bash-file-guard
|
|
15
16
|
*
|
|
16
17
|
* @module hooks/universal/dispatcher
|
|
17
18
|
*/
|
|
@@ -27,18 +28,82 @@ const __dirname = dirname(__filename);
|
|
|
27
28
|
// Hook type from arguments
|
|
28
29
|
const hookType = process.argv[2] || 'unknown';
|
|
29
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
|
+
|
|
30
75
|
/**
|
|
31
|
-
*
|
|
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)
|
|
32
97
|
*/
|
|
33
98
|
function findHooksDir() {
|
|
34
99
|
// Try multiple locations
|
|
35
100
|
const candidates = [
|
|
36
|
-
// Production: node_modules/specweave/dist/hooks
|
|
37
|
-
join(__dirname, '..', '..', '..', '..', 'node_modules', 'specweave', 'dist', 'hooks'),
|
|
38
|
-
// Development: project root dist/hooks
|
|
101
|
+
// Production: node_modules/specweave/dist/src/hooks
|
|
102
|
+
join(__dirname, '..', '..', '..', '..', 'node_modules', 'specweave', 'dist', 'src', 'hooks'),
|
|
103
|
+
// Development: project root dist/src/hooks
|
|
104
|
+
join(__dirname, '..', '..', '..', '..', 'dist', 'src', 'hooks'),
|
|
105
|
+
// Fallback: older path without src/ (backward compatibility)
|
|
39
106
|
join(__dirname, '..', '..', '..', '..', 'dist', 'hooks'),
|
|
40
|
-
// Relative to this file
|
|
41
|
-
join(__dirname, '..', '..', '..', '..', 'src', 'hooks'),
|
|
42
107
|
];
|
|
43
108
|
|
|
44
109
|
for (const candidate of candidates) {
|
|
@@ -52,7 +117,7 @@ function findHooksDir() {
|
|
|
52
117
|
}
|
|
53
118
|
|
|
54
119
|
/**
|
|
55
|
-
* Run a hook script
|
|
120
|
+
* Run a hook script with timeout protection
|
|
56
121
|
*/
|
|
57
122
|
async function runHook(scriptName) {
|
|
58
123
|
const hooksDir = findHooksDir();
|
|
@@ -74,13 +139,26 @@ async function runHook(scriptName) {
|
|
|
74
139
|
windowsHide: true,
|
|
75
140
|
});
|
|
76
141
|
|
|
77
|
-
|
|
142
|
+
const execPromise = new Promise((resolve) => {
|
|
78
143
|
child.on('exit', (code) => resolve(code || 0));
|
|
79
144
|
child.on('error', () => {
|
|
80
145
|
console.log(JSON.stringify({ continue: true }));
|
|
81
146
|
resolve(1);
|
|
82
147
|
});
|
|
83
148
|
});
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
return await withTimeout(execPromise, HOOK_TIMEOUT_MS, () => {
|
|
152
|
+
// Kill the child process on timeout
|
|
153
|
+
try {
|
|
154
|
+
child.kill('SIGTERM');
|
|
155
|
+
setTimeout(() => child.kill('SIGKILL'), 1000);
|
|
156
|
+
} catch { /* ignore */ }
|
|
157
|
+
});
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.log(JSON.stringify({ continue: true, error: err.message }));
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
84
162
|
}
|
|
85
163
|
|
|
86
164
|
/**
|
|
@@ -124,7 +202,39 @@ function findGitBash() {
|
|
|
124
202
|
}
|
|
125
203
|
|
|
126
204
|
/**
|
|
127
|
-
*
|
|
205
|
+
* Helper to spawn a bash script with timeout protection
|
|
206
|
+
*/
|
|
207
|
+
async function spawnBashWithTimeout(bashExe, args, options = {}) {
|
|
208
|
+
const child = spawn(bashExe, args, {
|
|
209
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
210
|
+
windowsHide: true,
|
|
211
|
+
...options,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const execPromise = new Promise((resolve) => {
|
|
215
|
+
child.on('exit', (code) => resolve(code || 0));
|
|
216
|
+
child.on('error', () => {
|
|
217
|
+
console.log(JSON.stringify({ continue: true }));
|
|
218
|
+
resolve(1);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
return await withTimeout(execPromise, HOOK_TIMEOUT_MS, () => {
|
|
224
|
+
// Kill the child process on timeout
|
|
225
|
+
try {
|
|
226
|
+
child.kill('SIGTERM');
|
|
227
|
+
setTimeout(() => child.kill('SIGKILL'), 1000);
|
|
228
|
+
} catch { /* ignore */ }
|
|
229
|
+
});
|
|
230
|
+
} catch (err) {
|
|
231
|
+
console.log(JSON.stringify({ continue: true, error: err.message }));
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Fallback to bash script if TypeScript not built (with timeout protection)
|
|
128
238
|
*
|
|
129
239
|
* @param bashScript - Name of the bash script (e.g., 'post-tool-use.sh')
|
|
130
240
|
* @param subdir - Subdirectory under v2 (e.g., 'dispatchers', 'guards')
|
|
@@ -143,17 +253,7 @@ async function fallbackToBash(bashScript, subdir = 'dispatchers') {
|
|
|
143
253
|
// Strategy 1: Git Bash (preferred - most common)
|
|
144
254
|
const bashExe = findGitBash();
|
|
145
255
|
if (bashExe) {
|
|
146
|
-
|
|
147
|
-
stdio: ['inherit', 'inherit', 'inherit'],
|
|
148
|
-
windowsHide: true,
|
|
149
|
-
});
|
|
150
|
-
return new Promise((resolve) => {
|
|
151
|
-
child.on('exit', (code) => resolve(code || 0));
|
|
152
|
-
child.on('error', () => {
|
|
153
|
-
console.log(JSON.stringify({ continue: true }));
|
|
154
|
-
resolve(1);
|
|
155
|
-
});
|
|
156
|
-
});
|
|
256
|
+
return spawnBashWithTimeout(bashExe, [scriptPath]);
|
|
157
257
|
}
|
|
158
258
|
|
|
159
259
|
// Strategy 2: WSL (if Git Bash not available) - FALLBACK only
|
|
@@ -166,17 +266,7 @@ async function fallbackToBash(bashScript, subdir = 'dispatchers') {
|
|
|
166
266
|
const wslScriptPath = scriptPath
|
|
167
267
|
.replace(/\\/g, '/')
|
|
168
268
|
.replace(/^([A-Za-z]):/, (_, d) => `/mnt/${d.toLowerCase()}`);
|
|
169
|
-
|
|
170
|
-
stdio: ['inherit', 'inherit', 'inherit'],
|
|
171
|
-
windowsHide: true,
|
|
172
|
-
});
|
|
173
|
-
return new Promise((resolve) => {
|
|
174
|
-
child.on('exit', (code) => resolve(code || 0));
|
|
175
|
-
child.on('error', () => {
|
|
176
|
-
console.log(JSON.stringify({ continue: true }));
|
|
177
|
-
resolve(1);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
269
|
+
return spawnBashWithTimeout('wsl', ['bash', wslScriptPath]);
|
|
180
270
|
}
|
|
181
271
|
|
|
182
272
|
// Strategy 3: No bash available - output warning and continue
|
|
@@ -188,21 +278,18 @@ async function fallbackToBash(bashScript, subdir = 'dispatchers') {
|
|
|
188
278
|
return;
|
|
189
279
|
}
|
|
190
280
|
|
|
191
|
-
// POSIX (macOS, Linux) - run directly
|
|
192
|
-
|
|
193
|
-
stdio: ['inherit', 'inherit', 'inherit'],
|
|
194
|
-
});
|
|
195
|
-
return new Promise((resolve) => {
|
|
196
|
-
child.on('exit', (code) => resolve(code || 0));
|
|
197
|
-
child.on('error', () => {
|
|
198
|
-
console.log(JSON.stringify({ continue: true }));
|
|
199
|
-
resolve(1);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
281
|
+
// POSIX (macOS, Linux) - run directly with timeout
|
|
282
|
+
return spawnBashWithTimeout('bash', [scriptPath]);
|
|
202
283
|
}
|
|
203
284
|
|
|
204
285
|
// Main routing
|
|
205
286
|
async function main() {
|
|
287
|
+
// CRITICAL: Skip if we're in dev environment with duplicate hooks
|
|
288
|
+
// This prevents marketplace hooks from running when local hooks are available
|
|
289
|
+
if (shouldSkipDueToDevEnvironment()) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
206
293
|
const isWindows = process.platform === 'win32';
|
|
207
294
|
|
|
208
295
|
try {
|
|
@@ -235,6 +322,12 @@ async function main() {
|
|
|
235
322
|
await fallbackToBash('completion-guard.sh', 'guards');
|
|
236
323
|
break;
|
|
237
324
|
|
|
325
|
+
case 'bash-file-guard':
|
|
326
|
+
// CRITICAL: Prevents infinite hangs from heredoc/echo file creation
|
|
327
|
+
// See CLAUDE.md Rule 9 for why this is essential
|
|
328
|
+
await fallbackToBash('bash-file-guard.sh', 'guards');
|
|
329
|
+
break;
|
|
330
|
+
|
|
238
331
|
default:
|
|
239
332
|
console.log(JSON.stringify({ continue: true, error: `Unknown hook type: ${hookType}` }));
|
|
240
333
|
}
|