specweave 0.23.10 → 0.23.14

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 (135) hide show
  1. package/.claude-plugin/marketplace.json +7 -7
  2. package/CLAUDE.md +384 -1449
  3. package/dist/src/cli/commands/cleanup-cache.d.ts +14 -0
  4. package/dist/src/cli/commands/cleanup-cache.d.ts.map +1 -0
  5. package/dist/src/cli/commands/cleanup-cache.js +63 -0
  6. package/dist/src/cli/commands/cleanup-cache.js.map +1 -0
  7. package/dist/src/cli/commands/init.js +40 -0
  8. package/dist/src/cli/commands/init.js.map +1 -1
  9. package/dist/src/cli/helpers/async-project-loader.d.ts +148 -0
  10. package/dist/src/cli/helpers/async-project-loader.d.ts.map +1 -0
  11. package/dist/src/cli/helpers/async-project-loader.js +351 -0
  12. package/dist/src/cli/helpers/async-project-loader.js.map +1 -0
  13. package/dist/src/cli/helpers/cancelation-handler.d.ts +123 -0
  14. package/dist/src/cli/helpers/cancelation-handler.d.ts.map +1 -0
  15. package/dist/src/cli/helpers/cancelation-handler.js +187 -0
  16. package/dist/src/cli/helpers/cancelation-handler.js.map +1 -0
  17. package/dist/src/cli/helpers/import-strategy-prompter.d.ts +43 -0
  18. package/dist/src/cli/helpers/import-strategy-prompter.d.ts.map +1 -0
  19. package/dist/src/cli/helpers/import-strategy-prompter.js +136 -0
  20. package/dist/src/cli/helpers/import-strategy-prompter.js.map +1 -0
  21. package/dist/src/cli/helpers/issue-tracker/ado.d.ts +5 -2
  22. package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
  23. package/dist/src/cli/helpers/issue-tracker/ado.js +90 -40
  24. package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
  25. package/dist/src/cli/helpers/issue-tracker/jira.d.ts +2 -1
  26. package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
  27. package/dist/src/cli/helpers/issue-tracker/jira.js +120 -35
  28. package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
  29. package/dist/src/cli/helpers/progress-tracker.d.ts +121 -0
  30. package/dist/src/cli/helpers/progress-tracker.d.ts.map +1 -0
  31. package/dist/src/cli/helpers/progress-tracker.js +202 -0
  32. package/dist/src/cli/helpers/progress-tracker.js.map +1 -0
  33. package/dist/src/cli/helpers/project-count-fetcher.d.ts +69 -0
  34. package/dist/src/cli/helpers/project-count-fetcher.d.ts.map +1 -0
  35. package/dist/src/cli/helpers/project-count-fetcher.js +173 -0
  36. package/dist/src/cli/helpers/project-count-fetcher.js.map +1 -0
  37. package/dist/src/config/types.d.ts +14 -14
  38. package/dist/src/core/cache/cache-manager.d.ts +119 -0
  39. package/dist/src/core/cache/cache-manager.d.ts.map +1 -0
  40. package/dist/src/core/cache/cache-manager.js +304 -0
  41. package/dist/src/core/cache/cache-manager.js.map +1 -0
  42. package/dist/src/core/cache/rate-limit-checker.d.ts +92 -0
  43. package/dist/src/core/cache/rate-limit-checker.d.ts.map +1 -0
  44. package/dist/src/core/cache/rate-limit-checker.js +160 -0
  45. package/dist/src/core/cache/rate-limit-checker.js.map +1 -0
  46. package/dist/src/core/progress/cancelation-handler.d.ts +79 -0
  47. package/dist/src/core/progress/cancelation-handler.d.ts.map +1 -0
  48. package/dist/src/core/progress/cancelation-handler.js +111 -0
  49. package/dist/src/core/progress/cancelation-handler.js.map +1 -0
  50. package/dist/src/core/progress/error-logger.d.ts +58 -0
  51. package/dist/src/core/progress/error-logger.d.ts.map +1 -0
  52. package/dist/src/core/progress/error-logger.js +99 -0
  53. package/dist/src/core/progress/error-logger.js.map +1 -0
  54. package/dist/src/core/progress/import-state.d.ts +71 -0
  55. package/dist/src/core/progress/import-state.d.ts.map +1 -0
  56. package/dist/src/core/progress/import-state.js +96 -0
  57. package/dist/src/core/progress/import-state.js.map +1 -0
  58. package/dist/src/core/progress/progress-tracker.d.ts +139 -0
  59. package/dist/src/core/progress/progress-tracker.d.ts.map +1 -0
  60. package/dist/src/core/progress/progress-tracker.js +223 -0
  61. package/dist/src/core/progress/progress-tracker.js.map +1 -0
  62. package/dist/src/init/architecture/types.d.ts +6 -6
  63. package/dist/src/integrations/ado/ado-client.d.ts +25 -0
  64. package/dist/src/integrations/ado/ado-client.d.ts.map +1 -1
  65. package/dist/src/integrations/ado/ado-client.js +67 -0
  66. package/dist/src/integrations/ado/ado-client.js.map +1 -1
  67. package/dist/src/integrations/ado/ado-dependency-loader.d.ts +99 -0
  68. package/dist/src/integrations/ado/ado-dependency-loader.d.ts.map +1 -0
  69. package/dist/src/integrations/ado/ado-dependency-loader.js +207 -0
  70. package/dist/src/integrations/ado/ado-dependency-loader.js.map +1 -0
  71. package/dist/src/integrations/jira/jira-client.d.ts +32 -0
  72. package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
  73. package/dist/src/integrations/jira/jira-client.js +81 -0
  74. package/dist/src/integrations/jira/jira-client.js.map +1 -1
  75. package/dist/src/integrations/jira/jira-dependency-loader.d.ts +101 -0
  76. package/dist/src/integrations/jira/jira-dependency-loader.d.ts.map +1 -0
  77. package/dist/src/integrations/jira/jira-dependency-loader.js +200 -0
  78. package/dist/src/integrations/jira/jira-dependency-loader.js.map +1 -0
  79. package/package.json +1 -1
  80. package/plugins/specweave/.claude-plugin/plugin.json +20 -0
  81. package/plugins/specweave/agents/architect/AGENT.md +100 -602
  82. package/plugins/specweave/agents/pm/AGENT.md +96 -597
  83. package/plugins/specweave/agents/pm/AGENT.md.bak +1893 -0
  84. package/plugins/specweave/agents/pm/AGENT.md.bak2 +1754 -0
  85. package/plugins/specweave/commands/check-hooks.md +257 -0
  86. package/plugins/specweave/hooks/docs-changed.sh +9 -1
  87. package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
  88. package/plugins/specweave/hooks/human-input-required.sh +9 -1
  89. package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
  90. package/plugins/specweave/hooks/post-edit-spec.sh +202 -31
  91. package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
  92. package/plugins/specweave/hooks/post-increment-change.sh +6 -1
  93. package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
  94. package/plugins/specweave/hooks/post-increment-completion.sh +6 -1
  95. package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
  96. package/plugins/specweave/hooks/post-increment-planning.sh +6 -1
  97. package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
  98. package/plugins/specweave/hooks/post-increment-status-change.sh +6 -1
  99. package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
  100. package/plugins/specweave/hooks/post-metadata-change.sh +7 -1
  101. package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
  102. package/plugins/specweave/hooks/post-task-completion.sh +225 -228
  103. package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
  104. package/plugins/specweave/hooks/post-write-spec.sh +207 -31
  105. package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
  106. package/plugins/specweave/hooks/pre-edit-spec.sh +151 -0
  107. package/plugins/specweave/hooks/pre-implementation.sh +9 -1
  108. package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
  109. package/plugins/specweave/hooks/pre-task-completion.sh +14 -8
  110. package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
  111. package/plugins/specweave/hooks/pre-tool-use.sh +9 -1
  112. package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
  113. package/plugins/specweave/hooks/pre-write-spec.sh +151 -0
  114. package/plugins/specweave/hooks/test-pretooluse-env.sh +72 -0
  115. package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
  116. package/plugins/specweave/skills/compliance-architecture/SKILL.md +374 -0
  117. package/plugins/specweave/skills/external-sync-wizard/SKILL.md +610 -0
  118. package/plugins/specweave/skills/pm-closure-validation/SKILL.md +541 -0
  119. package/plugins/specweave/skills/roadmap-planner/SKILL.md +473 -0
  120. package/plugins/specweave-ado/commands/refresh-cache.js +25 -0
  121. package/plugins/specweave-ado/commands/refresh-cache.ts +40 -0
  122. package/plugins/specweave-ado/hooks/post-living-docs-update.sh +9 -2
  123. package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
  124. package/plugins/specweave-ado/hooks/post-task-completion.sh +10 -2
  125. package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
  126. package/plugins/specweave-github/hooks/post-task-completion.sh +10 -2
  127. package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
  128. package/plugins/specweave-jira/commands/refresh-cache.js +25 -0
  129. package/plugins/specweave-jira/commands/refresh-cache.ts +40 -0
  130. package/plugins/specweave-jira/hooks/post-task-completion.sh +10 -2
  131. package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
  132. package/plugins/specweave-kafka-streams/commands/topology.md +437 -0
  133. package/plugins/specweave-n8n/commands/workflow-template.md +262 -0
  134. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +252 -6465
  135. package/plugins/specweave-release/hooks/post-task-completion.sh.backup +110 -0
@@ -0,0 +1,179 @@
1
+ #!/bin/bash
2
+
3
+ ###############################################################################
4
+ # SpecWeave Post-User-Story-Complete Hook
5
+ #
6
+ # CRITICAL ARCHITECTURE:
7
+ # - Fires when user story marked complete in spec.md (AC checkbox checked)
8
+ # - Updates external PM tool (GitHub Issue/Jira Story/ADO User Story)
9
+ # - Moves GitHub card to "Done" / Closes Jira story / Completes ADO story
10
+ #
11
+ # Trigger Points:
12
+ # 1. After user manually checks AC checkbox in spec.md
13
+ # 2. After bidirectional sync updates AC status
14
+ # 3. After increment completion syncs to spec
15
+ #
16
+ # What It Does:
17
+ # - Detects which user story was completed (all AC checkboxes checked)
18
+ # - Finds corresponding external item (GitHub Issue/Jira Story/ADO User Story)
19
+ # - Updates item status to "Done" / "Closed"
20
+ # - Adds completion comment with timestamp
21
+ #
22
+ # Usage:
23
+ # post-user-story-complete.sh <spec-id> <user-story-id>
24
+ #
25
+ # Example:
26
+ # post-user-story-complete.sh spec-001 US-001
27
+ #
28
+ ###############################################################################
29
+
30
+ set -euo pipefail
31
+
32
+ # Arguments
33
+ SPEC_ID="${1:-}"
34
+ USER_STORY_ID="${2:-}"
35
+
36
+ # Validate arguments
37
+ if [[ -z "$SPEC_ID" || -z "$USER_STORY_ID" ]]; then
38
+ echo "āŒ Error: Spec ID and User Story ID required"
39
+ echo "Usage: post-user-story-complete.sh <spec-id> <user-story-id>"
40
+ exit 1
41
+ fi
42
+
43
+ echo ""
44
+ echo "šŸŽ‰ Post-User-Story-Complete Hook"
45
+ echo " Spec: $SPEC_ID"
46
+ echo " User Story: $USER_STORY_ID"
47
+
48
+ # Find spec file
49
+ SPEC_FILE=""
50
+ if [[ -f ".specweave/docs/internal/specs/$SPEC_ID.md" ]]; then
51
+ SPEC_FILE=".specweave/docs/internal/specs/$SPEC_ID.md"
52
+ elif [[ -f ".specweave/docs/internal/projects/default/specs/$SPEC_ID.md" ]]; then
53
+ SPEC_FILE=".specweave/docs/internal/projects/default/specs/$SPEC_ID.md"
54
+ else
55
+ echo "āŒ Error: Spec file not found for $SPEC_ID"
56
+ exit 1
57
+ fi
58
+
59
+ # Load config to check if auto-sync is enabled
60
+ CONFIG_FILE=".specweave/config.json"
61
+ if [[ ! -f "$CONFIG_FILE" ]]; then
62
+ echo " ā„¹ļø No config file found, skipping auto-sync"
63
+ exit 0
64
+ fi
65
+
66
+ # Check if auto-sync is enabled
67
+ AUTO_SYNC=$(jq -r '.hooks.post_user_story_complete.auto_sync // true' "$CONFIG_FILE")
68
+
69
+ if [[ "$AUTO_SYNC" != "true" ]]; then
70
+ echo " ā„¹ļø Auto-sync disabled in config, skipping"
71
+ exit 0
72
+ fi
73
+
74
+ # Parse spec frontmatter to detect external links
75
+ # Check if GitHub link exists
76
+ GITHUB_LINK=$(grep -A 10 "^externalLinks:" "$SPEC_FILE" | grep -A 5 "github:" | grep "projectId:" | sed 's/.*projectId: *//; s/ *$//' || echo "")
77
+
78
+ # Check if Jira link exists
79
+ JIRA_LINK=$(grep -A 10 "^externalLinks:" "$SPEC_FILE" | grep -A 5 "jira:" | grep "epicKey:" | sed 's/.*epicKey: *//; s/ *$//' || echo "")
80
+
81
+ # Check if ADO link exists
82
+ ADO_LINK=$(grep -A 10 "^externalLinks:" "$SPEC_FILE" | grep -A 5 "ado:" | grep "featureId:" | sed 's/.*featureId: *//; s/ *$//' || echo "")
83
+
84
+ # Determine which provider to sync
85
+ PROVIDER=""
86
+ if [[ -n "$GITHUB_LINK" ]]; then
87
+ PROVIDER="github"
88
+ EXTERNAL_ID="$GITHUB_LINK"
89
+ elif [[ -n "$JIRA_LINK" ]]; then
90
+ PROVIDER="jira"
91
+ EXTERNAL_ID="$JIRA_LINK"
92
+ elif [[ -n "$ADO_LINK" ]]; then
93
+ PROVIDER="ado"
94
+ EXTERNAL_ID="$ADO_LINK"
95
+ fi
96
+
97
+ # No external link found - skip sync
98
+ if [[ -z "$PROVIDER" ]]; then
99
+ echo " ā„¹ļø Spec not linked to external tool, skipping sync"
100
+ exit 0
101
+ fi
102
+
103
+ echo " šŸ”— Detected external link: $PROVIDER"
104
+
105
+ # Update external tool based on provider
106
+ case "$PROVIDER" in
107
+ github)
108
+ echo " šŸ”„ Updating GitHub Issue for $USER_STORY_ID..."
109
+
110
+ # Check if GitHub CLI is available
111
+ if ! command -v gh &> /dev/null; then
112
+ echo " āš ļø GitHub CLI (gh) not found, skipping sync"
113
+ exit 0
114
+ fi
115
+
116
+ # Find GitHub Issue for this user story
117
+ # Search for issue with title pattern "[USER_STORY_ID]"
118
+ REPO=$(git remote get-url origin | sed -E 's/.*github\.com[:/]([^/]+\/[^/]+)(\.git)?$/\1/')
119
+
120
+ # Search for issue
121
+ ISSUE_NUMBER=$(gh issue list --repo "$REPO" --search "\"[$USER_STORY_ID]\" in:title" --json number --jq '.[0].number' 2>/dev/null || echo "")
122
+
123
+ if [[ -z "$ISSUE_NUMBER" ]]; then
124
+ echo " āš ļø GitHub Issue not found for $USER_STORY_ID"
125
+ exit 0
126
+ fi
127
+
128
+ echo " šŸ“ Found GitHub Issue #$ISSUE_NUMBER"
129
+
130
+ # Close issue
131
+ gh issue close "$ISSUE_NUMBER" --repo "$REPO" --comment "āœ… User story completed
132
+
133
+ šŸ¤– Auto-closed by SpecWeave hook
134
+ Completed at: $(date -u +%Y-%m-%dT%H:%M:%SZ)" 2>/dev/null || {
135
+ echo " āš ļø Failed to close issue"
136
+ exit 0
137
+ }
138
+
139
+ echo " āœ… GitHub Issue #$ISSUE_NUMBER closed"
140
+ ;;
141
+
142
+ jira)
143
+ echo " šŸ”„ Updating Jira Story for $USER_STORY_ID..."
144
+
145
+ # Check if Jira config exists
146
+ if [[ -z "${JIRA_DOMAIN:-}" ]]; then
147
+ echo " āš ļø Jira not configured (.env), skipping sync"
148
+ exit 0
149
+ fi
150
+
151
+ # TODO: Find Jira Story by title pattern
152
+ # TODO: Transition story to "Done" status
153
+ echo " āœ… Jira story transition queued (implementation pending)"
154
+ ;;
155
+
156
+ ado)
157
+ echo " šŸ”„ Updating ADO User Story for $USER_STORY_ID..."
158
+
159
+ # Check if ADO config exists
160
+ if [[ -z "${ADO_ORGANIZATION:-}" ]]; then
161
+ echo " āš ļø ADO not configured (.env), skipping sync"
162
+ exit 0
163
+ fi
164
+
165
+ # TODO: Find ADO User Story by title pattern
166
+ # TODO: Update state to "Closed"
167
+ echo " āœ… ADO user story update queued (implementation pending)"
168
+ ;;
169
+
170
+ *)
171
+ echo " āš ļø Unknown provider: $PROVIDER"
172
+ exit 0
173
+ ;;
174
+ esac
175
+
176
+ echo " āœ… Post-user-story-complete hook complete"
177
+ echo ""
178
+
179
+ exit 0
@@ -5,12 +5,30 @@
5
5
  # Triggers: After Write tool creates/replaces spec.md or tasks.md
6
6
  # Action: Updates status line cache to reflect latest AC/task progress
7
7
  #
8
- # CRITICAL FIX (v0.24.1): Enhanced file detection to handle increment completion
8
+ # EMERGENCY FIXES (v0.24.3):
9
+ # - Kill switch: Set SPECWEAVE_DISABLE_HOOKS=1 to disable ALL hooks
10
+ # - Circuit breaker: Auto-disable after 3 consecutive failures
11
+ # - File locking: Prevent concurrent executions (max 1 at a time)
12
+ # - Aggressive debouncing: Increased from 1s to 5s
13
+ # - Complete error isolation: Never let errors reach Claude Code
14
+ #
15
+ # TIER 1 IMPROVEMENTS (v0.24.2):
16
+ # - Debouncing: Skip if updated less than 1 second ago (90% overhead reduction)
17
+ # - File mtime detection: Check recently modified spec.md/tasks.md as fallback
18
+ # - Non-blocking: Run update-status-line.sh in background
19
+ # - Smart detection: Only update if spec/tasks files actually changed
20
+ #
21
+ # Previous fix (v0.24.1): Enhanced file detection for increment completion
9
22
  # - Detects writes via TOOL_USE_CONTENT, TOOL_RESULT, and argument parsing
10
23
  # - Always updates status line for ANY spec.md/tasks.md write in increments folder
11
- # - Matches post-edit-spec.sh robustness improvements
12
24
 
13
- set -e
25
+ # CRITICAL: Remove set -e to prevent hook errors from crashing Claude Code
26
+ set +e
27
+
28
+ # EMERGENCY KILL SWITCH: Disable all hooks if env variable set
29
+ if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
30
+ exit 0
31
+ fi
14
32
 
15
33
  # Find project root
16
34
  find_project_root() {
@@ -29,31 +47,134 @@ PROJECT_ROOT=$(find_project_root)
29
47
  LOGS_DIR="$PROJECT_ROOT/.specweave/logs"
30
48
  DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
31
49
 
32
- # Ensure logs directory exists
33
- mkdir -p "$LOGS_DIR" 2>/dev/null || true
50
+ # Ensure state and logs directories exist
51
+ mkdir -p "$PROJECT_ROOT/.specweave/state" "$LOGS_DIR" 2>/dev/null || true
34
52
 
35
- # Extract written file from multiple sources (Claude Code provides this in various ways)
36
- WRITTEN_FILE=""
53
+ # EMERGENCY CIRCUIT BREAKER: Track consecutive failures
54
+ CIRCUIT_BREAKER_FILE="$PROJECT_ROOT/.specweave/state/.hook-circuit-breaker"
55
+ CIRCUIT_BREAKER_THRESHOLD=3
56
+
57
+ if [[ -f "$CIRCUIT_BREAKER_FILE" ]]; then
58
+ FAILURE_COUNT=$(cat "$CIRCUIT_BREAKER_FILE" 2>/dev/null || echo 0)
59
+ if (( FAILURE_COUNT >= CIRCUIT_BREAKER_THRESHOLD )); then
60
+ echo "[$(date)] CIRCUIT BREAKER OPEN: Hooks disabled after $FAILURE_COUNT failures. Run: rm $CIRCUIT_BREAKER_FILE" >> "$DEBUG_LOG" 2>/dev/null || true
61
+ exit 0
62
+ fi
63
+ fi
64
+
65
+ # EMERGENCY FILE LOCK: Prevent concurrent executions
66
+ LOCK_FILE="$PROJECT_ROOT/.specweave/state/.hook-post-write.lock"
67
+ LOCK_TIMEOUT=5 # seconds
68
+
69
+ # Try to acquire lock with timeout
70
+ LOCK_ACQUIRED=false
71
+ for i in {1..5}; do
72
+ if mkdir "$LOCK_FILE" 2>/dev/null; then
73
+ LOCK_ACQUIRED=true
74
+ trap 'rmdir "$LOCK_FILE" 2>/dev/null || true' EXIT
75
+ break
76
+ fi
77
+
78
+ # Check if lock is stale (older than LOCK_TIMEOUT seconds)
79
+ if [[ -d "$LOCK_FILE" ]]; then
80
+ LOCK_AGE=$(($(date +%s) - $(stat -f "%m" "$LOCK_FILE" 2>/dev/null || echo 0)))
81
+ if (( LOCK_AGE > LOCK_TIMEOUT )); then
82
+ rmdir "$LOCK_FILE" 2>/dev/null || true
83
+ continue
84
+ fi
85
+ fi
86
+
87
+ sleep 0.2
88
+ done
89
+
90
+ if [[ "$LOCK_ACQUIRED" == "false" ]]; then
91
+ echo "[$(date)] post-write-spec: Could not acquire lock, skipping" >> "$DEBUG_LOG" 2>/dev/null || true
92
+ exit 0
93
+ fi
94
+
95
+ # Log rotation: Keep debug log under 100KB
96
+ if [[ -f "$DEBUG_LOG" ]] && [[ $(wc -c < "$DEBUG_LOG" 2>/dev/null || echo 0) -gt 102400 ]]; then
97
+ tail -100 "$DEBUG_LOG" > "$DEBUG_LOG.tmp" 2>/dev/null || true
98
+ mv "$DEBUG_LOG.tmp" "$DEBUG_LOG" 2>/dev/null || true
99
+ echo "[$(date)] Log rotated" >> "$DEBUG_LOG" 2>/dev/null || true
100
+ fi
101
+
102
+ # ============================================================================
103
+ # TIER 1 FIX: Debouncing (Prevent Redundant Updates)
104
+ # ============================================================================
105
+ # Skip update if we updated less than 5 seconds ago (INCREASED FROM 1s)
106
+ # This handles rapid consecutive writes (e.g., spec.md regeneration)
107
+ LAST_UPDATE_FILE="$PROJECT_ROOT/.specweave/state/.last-status-update"
108
+ DEBOUNCE_SECONDS=5
109
+
110
+ if [[ -f "$LAST_UPDATE_FILE" ]]; then
111
+ LAST_UPDATE=$(cat "$LAST_UPDATE_FILE" 2>/dev/null || echo 0)
112
+ NOW=$(date +%s)
113
+ TIME_SINCE_UPDATE=$((NOW - LAST_UPDATE))
37
114
 
38
- # Method 1: TOOL_USE_CONTENT environment variable (primary)
39
- if [[ -n "${TOOL_USE_CONTENT:-}" ]]; then
40
- WRITTEN_FILE="$TOOL_USE_CONTENT"
115
+ if (( TIME_SINCE_UPDATE < DEBOUNCE_SECONDS )); then
116
+ echo "[$(date)] post-write-spec: Debounced (${TIME_SINCE_UPDATE}s since last update)" >> "$DEBUG_LOG" 2>/dev/null || true
117
+ exit 0 # Skip this update
118
+ fi
41
119
  fi
42
120
 
43
- # Method 2: TOOL_RESULT environment variable (fallback)
44
- if [[ -z "$WRITTEN_FILE" ]] && [[ -n "${TOOL_RESULT:-}" ]]; then
45
- # Extract file_path from tool result JSON
46
- WRITTEN_FILE=$(echo "$TOOL_RESULT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "")
121
+ # ============================================================================
122
+ # TIER 2: Check for PreToolUse Signal (Primary Detection Method)
123
+ # ============================================================================
124
+ PENDING_FILE="$PROJECT_ROOT/.specweave/state/.pending-status-update"
125
+ METRICS_FILE="$PROJECT_ROOT/.specweave/state/hook-metrics.jsonl"
126
+ WRITTEN_FILE=""
127
+ DETECTION_METHOD="none"
128
+
129
+ # First, check if PreToolUse hook left a signal
130
+ if [[ -f "$PENDING_FILE" ]]; then
131
+ WRITTEN_FILE=$(cat "$PENDING_FILE" 2>/dev/null || echo "")
132
+ # Delete pending file immediately (consume signal)
133
+ rm "$PENDING_FILE" 2>/dev/null || true
134
+
135
+ if [[ -n "$WRITTEN_FILE" ]]; then
136
+ DETECTION_METHOD="pretooluse"
137
+ echo "[$(date)] post-write-spec: File from PreToolUse signal: $WRITTEN_FILE" >> "$DEBUG_LOG" 2>/dev/null || true
138
+
139
+ # Record Tier 2 success metric
140
+ TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
141
+ echo "{\"timestamp\":\"$TIMESTAMP\",\"hook\":\"post-write-spec\",\"event\":\"tier2_success\",\"method\":\"pretooluse\"}" >> "$METRICS_FILE" 2>/dev/null || true
142
+ fi
47
143
  fi
48
144
 
49
- # Method 3: Parse tool use arguments (last resort)
50
- if [[ -z "$WRITTEN_FILE" ]] && [[ -n "${TOOL_USE_ARGS:-}" ]]; then
51
- # Extract file_path from tool arguments
52
- WRITTEN_FILE=$(echo "$TOOL_USE_ARGS" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "")
145
+ # ============================================================================
146
+ # TIER 1 FALLBACK: Environment Variable Detection
147
+ # ============================================================================
148
+ # If PreToolUse didn't provide signal, fall back to Tier 1 methods
149
+ if [[ -z "$WRITTEN_FILE" ]]; then
150
+ # Method 1: TOOL_USE_CONTENT environment variable
151
+ if [[ -n "${TOOL_USE_CONTENT:-}" ]]; then
152
+ WRITTEN_FILE="$TOOL_USE_CONTENT"
153
+ DETECTION_METHOD="env_content"
154
+ fi
155
+
156
+ # Method 2: TOOL_RESULT environment variable
157
+ if [[ -z "$WRITTEN_FILE" ]] && [[ -n "${TOOL_RESULT:-}" ]]; then
158
+ WRITTEN_FILE=$(echo "$TOOL_RESULT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "")
159
+ DETECTION_METHOD="env_result"
160
+ fi
161
+
162
+ # Method 3: TOOL_USE_ARGS
163
+ if [[ -z "$WRITTEN_FILE" ]] && [[ -n "${TOOL_USE_ARGS:-}" ]]; then
164
+ WRITTEN_FILE=$(echo "$TOOL_USE_ARGS" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "")
165
+ DETECTION_METHOD="env_args"
166
+ fi
167
+
168
+ # Log env var detection (for metrics)
169
+ if [[ -n "$WRITTEN_FILE" ]]; then
170
+ echo "[$(date)] post-write-spec: File from env vars ($DETECTION_METHOD): $WRITTEN_FILE" >> "$DEBUG_LOG" 2>/dev/null || true
171
+ fi
53
172
  fi
54
173
 
55
- # Log detection attempt
56
- echo "[$(date)] post-write-spec: Detected file: ${WRITTEN_FILE:-<none>}" >> "$DEBUG_LOG" 2>/dev/null || true
174
+ # Log detection attempt (only log if we actually detected a file, to reduce noise)
175
+ if [[ -n "$WRITTEN_FILE" ]]; then
176
+ echo "[$(date)] post-write-spec: Detected file: $WRITTEN_FILE" >> "$DEBUG_LOG" 2>/dev/null || true
177
+ fi
57
178
 
58
179
  # Check if we detected a spec.md or tasks.md write in increments folder
59
180
  SHOULD_UPDATE=false
@@ -69,23 +190,78 @@ if [[ -n "$WRITTEN_FILE" ]]; then
69
190
  fi
70
191
  fi
71
192
 
72
- # If we couldn't detect the file via environment variables, always update status line
73
- # This ensures we don't miss updates during increment closure
193
+ # ============================================================================
194
+ # TIER 1 FIX: File Modification Time Detection (Fallback)
195
+ # ============================================================================
196
+ # If we couldn't detect the file via environment variables, check which files
197
+ # were modified recently (within last 2 seconds) instead of blindly updating
74
198
  if [[ -z "$WRITTEN_FILE" ]]; then
75
- echo "[$(date)] post-write-spec: No file detected - updating status line as safety measure" >> "$DEBUG_LOG" 2>/dev/null || true
76
- SHOULD_UPDATE=true
199
+ echo "[$(date)] post-write-spec: Env vars empty - checking file mtimes" >> "$DEBUG_LOG" 2>/dev/null || true
200
+
201
+ NOW=$(date +%s)
202
+ INCREMENTS_DIR="$PROJECT_ROOT/.specweave/increments"
203
+
204
+ # Check for recently modified spec.md or tasks.md files
205
+ if [[ -d "$INCREMENTS_DIR" ]]; then
206
+ for file in "$INCREMENTS_DIR"/*/spec.md "$INCREMENTS_DIR"/*/tasks.md; do
207
+ if [[ -f "$file" ]]; then
208
+ # Get file modification time (platform-specific)
209
+ if [[ "$(uname)" == "Darwin" ]]; then
210
+ MTIME=$(stat -f "%m" "$file" 2>/dev/null || echo 0)
211
+ else
212
+ MTIME=$(stat -c "%Y" "$file" 2>/dev/null || echo 0)
213
+ fi
214
+
215
+ # If file was modified in last 2 seconds, consider it the written file
216
+ TIME_DIFF=$((NOW - MTIME))
217
+ if (( TIME_DIFF <= 2 )); then
218
+ WRITTEN_FILE="$file"
219
+ echo "[$(date)] post-write-spec: Detected recent modification: $file (${TIME_DIFF}s ago)" >> "$DEBUG_LOG" 2>/dev/null || true
220
+ SHOULD_UPDATE=true
221
+ break
222
+ fi
223
+ fi
224
+ done
225
+ fi
226
+
227
+ # If still no file detected, skip update (not a spec/tasks write)
228
+ if [[ -z "$WRITTEN_FILE" ]]; then
229
+ echo "[$(date)] post-write-spec: No spec/tasks modifications detected - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
230
+ exit 0
231
+ fi
77
232
  fi
78
233
 
234
+ # ============================================================================
235
+ # TIER 1 FIX: Non-Blocking Background Update with COMPLETE ERROR ISOLATION
236
+ # ============================================================================
79
237
  # Update status line if needed
80
238
  if [[ "$SHOULD_UPDATE" == "true" ]]; then
81
- echo "[$(date)] post-write-spec: Running update-status-line.sh" >> "$DEBUG_LOG" 2>/dev/null || true
239
+ echo "[$(date)] post-write-spec: Running update-status-line.sh (background)" >> "$DEBUG_LOG" 2>/dev/null || true
82
240
 
83
- # Run status line update (capture errors for debugging)
84
- if "$PROJECT_ROOT/plugins/specweave/hooks/lib/update-status-line.sh" 2>&1 | tee -a "$DEBUG_LOG" >/dev/null; then
85
- echo "[$(date)] post-write-spec: Status line updated successfully" >> "$DEBUG_LOG" 2>/dev/null || true
86
- else
87
- echo "[$(date)] post-write-spec: Warning - status line update failed (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
88
- fi
241
+ # Record update time BEFORE spawning background process
242
+ # This ensures debouncing works even if update hasn't completed yet
243
+ echo "$(date +%s)" > "$LAST_UPDATE_FILE"
244
+
245
+ # Run status line update in background with COMPLETE error isolation
246
+ # This prevents Write tool from waiting for status line computation
247
+ (
248
+ set +e # Disable error propagation
249
+
250
+ if "$PROJECT_ROOT/plugins/specweave/hooks/lib/update-status-line.sh" 2>&1 | tee -a "$DEBUG_LOG" >/dev/null; then
251
+ echo "[$(date)] post-write-spec: Status line updated successfully" >> "$DEBUG_LOG" 2>/dev/null || true
252
+ # Reset circuit breaker on success
253
+ echo "0" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true
254
+ else
255
+ echo "[$(date)] post-write-spec: Warning - status line update failed (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
256
+ # Increment circuit breaker
257
+ CURRENT_FAILURES=$(cat "$CIRCUIT_BREAKER_FILE" 2>/dev/null || echo 0)
258
+ echo "$((CURRENT_FAILURES + 1))" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true
259
+ fi
260
+ ) &
261
+
262
+ # Disown the background process so it's not killed when hook exits
263
+ disown 2>/dev/null || true
89
264
  fi
90
265
 
266
+ # Always exit 0 to prevent hook errors from crashing Claude Code
91
267
  exit 0
@@ -0,0 +1,83 @@
1
+ #!/bin/bash
2
+
3
+ # SpecWeave Pre-Command Deduplication Hook
4
+ # Fires BEFORE any command executes (UserPromptSubmit hook)
5
+ # Purpose: Prevent duplicate command invocations within configurable time window
6
+
7
+ set -euo pipefail
8
+
9
+ # ==============================================================================
10
+ # PROJECT ROOT DETECTION
11
+ # ==============================================================================
12
+
13
+ # Find project root by searching upward for .specweave/ directory
14
+ find_project_root() {
15
+ local dir="$1"
16
+ while [ "$dir" != "/" ]; do
17
+ if [ -d "$dir/.specweave" ]; then
18
+ echo "$dir"
19
+ return 0
20
+ fi
21
+ dir="$(dirname "$dir")"
22
+ done
23
+ # Fallback: try current directory
24
+ if [ -d "$(pwd)/.specweave" ]; then
25
+ pwd
26
+ else
27
+ echo "$(pwd)"
28
+ fi
29
+ }
30
+
31
+ PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
32
+ cd "$PROJECT_ROOT" 2>/dev/null || true
33
+
34
+ # Read input JSON from stdin
35
+ INPUT=$(cat)
36
+
37
+ # ==============================================================================
38
+ # DEDUPLICATION CHECK: Block duplicate commands within 1 second
39
+ # ==============================================================================
40
+
41
+ # Check if deduplication module is available
42
+ if command -v node >/dev/null 2>&1 && [[ -f "dist/src/core/deduplication/command-deduplicator.js" ]]; then
43
+ # Use dedicated wrapper script for ES module compatibility
44
+ DEDUP_RESULT=$(echo "$INPUT" | node scripts/check-deduplication.js 2>/dev/null || echo "OK")
45
+
46
+ # Parse result
47
+ STATUS=$(echo "$DEDUP_RESULT" | head -1)
48
+
49
+ if [[ "$STATUS" == "DUPLICATE" ]]; then
50
+ # Get stats
51
+ STATS=$(echo "$DEDUP_RESULT" | tail -1)
52
+
53
+ # Extract command and stats for readable message
54
+ COMMAND=$(echo "$STATS" | grep -o '"lastCommand":"[^"]*"' | cut -d'"' -f4 || echo "unknown")
55
+ TOTAL_BLOCKED=$(echo "$STATS" | grep -o '"totalDuplicatesBlocked":[0-9]*' | cut -d':' -f2 || echo "1")
56
+ CACHE_SIZE=$(echo "$STATS" | grep -o '"currentCacheSize":[0-9]*' | cut -d':' -f2 || echo "1")
57
+
58
+ # Build error message WITHOUT embedding JSON (avoid escaping issues)
59
+ MESSAGE=$(cat <<'EOF'
60
+ {
61
+ "decision": "block",
62
+ "reason": "🚫 DUPLICATE COMMAND DETECTED\n\nCommand: `COMMAND_PLACEHOLDER`\nTime window: 1 second\n\nThis command was just executed! To prevent unintended duplicates, this invocation has been blocked.\n\nšŸ’” If you meant to run this command again:\n 1. Wait 1 second\n 2. Run the command again\n\nDeduplication Stats:\n- Total duplicates blocked: BLOCKED_PLACEHOLDER\n- Commands in cache: CACHE_PLACEHOLDER"
63
+ }
64
+ EOF
65
+ )
66
+ # Replace placeholders (avoids JSON escaping issues)
67
+ # Use | as sed delimiter to avoid conflicts with / in command names
68
+ echo "$MESSAGE" | sed "s|COMMAND_PLACEHOLDER|$COMMAND|g" | sed "s|BLOCKED_PLACEHOLDER|$TOTAL_BLOCKED|g" | sed "s|CACHE_PLACEHOLDER|$CACHE_SIZE|g"
69
+ exit 0
70
+ fi
71
+ fi
72
+
73
+ # ==============================================================================
74
+ # PASS THROUGH: No duplicate detected, proceed with command
75
+ # ==============================================================================
76
+
77
+ cat <<EOF
78
+ {
79
+ "decision": "approve"
80
+ }
81
+ EOF
82
+
83
+ exit 0