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.
- package/.claude-plugin/marketplace.json +7 -7
- package/CLAUDE.md +384 -1449
- package/dist/src/cli/commands/cleanup-cache.d.ts +14 -0
- package/dist/src/cli/commands/cleanup-cache.d.ts.map +1 -0
- package/dist/src/cli/commands/cleanup-cache.js +63 -0
- package/dist/src/cli/commands/cleanup-cache.js.map +1 -0
- package/dist/src/cli/commands/init.js +40 -0
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/async-project-loader.d.ts +148 -0
- package/dist/src/cli/helpers/async-project-loader.d.ts.map +1 -0
- package/dist/src/cli/helpers/async-project-loader.js +351 -0
- package/dist/src/cli/helpers/async-project-loader.js.map +1 -0
- package/dist/src/cli/helpers/cancelation-handler.d.ts +123 -0
- package/dist/src/cli/helpers/cancelation-handler.d.ts.map +1 -0
- package/dist/src/cli/helpers/cancelation-handler.js +187 -0
- package/dist/src/cli/helpers/cancelation-handler.js.map +1 -0
- package/dist/src/cli/helpers/import-strategy-prompter.d.ts +43 -0
- package/dist/src/cli/helpers/import-strategy-prompter.d.ts.map +1 -0
- package/dist/src/cli/helpers/import-strategy-prompter.js +136 -0
- package/dist/src/cli/helpers/import-strategy-prompter.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts +5 -2
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado.js +90 -40
- package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/jira.d.ts +2 -1
- package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/jira.js +120 -35
- package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
- package/dist/src/cli/helpers/progress-tracker.d.ts +121 -0
- package/dist/src/cli/helpers/progress-tracker.d.ts.map +1 -0
- package/dist/src/cli/helpers/progress-tracker.js +202 -0
- package/dist/src/cli/helpers/progress-tracker.js.map +1 -0
- package/dist/src/cli/helpers/project-count-fetcher.d.ts +69 -0
- package/dist/src/cli/helpers/project-count-fetcher.d.ts.map +1 -0
- package/dist/src/cli/helpers/project-count-fetcher.js +173 -0
- package/dist/src/cli/helpers/project-count-fetcher.js.map +1 -0
- package/dist/src/config/types.d.ts +14 -14
- package/dist/src/core/cache/cache-manager.d.ts +119 -0
- package/dist/src/core/cache/cache-manager.d.ts.map +1 -0
- package/dist/src/core/cache/cache-manager.js +304 -0
- package/dist/src/core/cache/cache-manager.js.map +1 -0
- package/dist/src/core/cache/rate-limit-checker.d.ts +92 -0
- package/dist/src/core/cache/rate-limit-checker.d.ts.map +1 -0
- package/dist/src/core/cache/rate-limit-checker.js +160 -0
- package/dist/src/core/cache/rate-limit-checker.js.map +1 -0
- package/dist/src/core/progress/cancelation-handler.d.ts +79 -0
- package/dist/src/core/progress/cancelation-handler.d.ts.map +1 -0
- package/dist/src/core/progress/cancelation-handler.js +111 -0
- package/dist/src/core/progress/cancelation-handler.js.map +1 -0
- package/dist/src/core/progress/error-logger.d.ts +58 -0
- package/dist/src/core/progress/error-logger.d.ts.map +1 -0
- package/dist/src/core/progress/error-logger.js +99 -0
- package/dist/src/core/progress/error-logger.js.map +1 -0
- package/dist/src/core/progress/import-state.d.ts +71 -0
- package/dist/src/core/progress/import-state.d.ts.map +1 -0
- package/dist/src/core/progress/import-state.js +96 -0
- package/dist/src/core/progress/import-state.js.map +1 -0
- package/dist/src/core/progress/progress-tracker.d.ts +139 -0
- package/dist/src/core/progress/progress-tracker.d.ts.map +1 -0
- package/dist/src/core/progress/progress-tracker.js +223 -0
- package/dist/src/core/progress/progress-tracker.js.map +1 -0
- package/dist/src/init/architecture/types.d.ts +6 -6
- package/dist/src/integrations/ado/ado-client.d.ts +25 -0
- package/dist/src/integrations/ado/ado-client.d.ts.map +1 -1
- package/dist/src/integrations/ado/ado-client.js +67 -0
- package/dist/src/integrations/ado/ado-client.js.map +1 -1
- package/dist/src/integrations/ado/ado-dependency-loader.d.ts +99 -0
- package/dist/src/integrations/ado/ado-dependency-loader.d.ts.map +1 -0
- package/dist/src/integrations/ado/ado-dependency-loader.js +207 -0
- package/dist/src/integrations/ado/ado-dependency-loader.js.map +1 -0
- package/dist/src/integrations/jira/jira-client.d.ts +32 -0
- package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-client.js +81 -0
- package/dist/src/integrations/jira/jira-client.js.map +1 -1
- package/dist/src/integrations/jira/jira-dependency-loader.d.ts +101 -0
- package/dist/src/integrations/jira/jira-dependency-loader.d.ts.map +1 -0
- package/dist/src/integrations/jira/jira-dependency-loader.js +200 -0
- package/dist/src/integrations/jira/jira-dependency-loader.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +20 -0
- package/plugins/specweave/agents/architect/AGENT.md +100 -602
- package/plugins/specweave/agents/pm/AGENT.md +96 -597
- package/plugins/specweave/agents/pm/AGENT.md.bak +1893 -0
- package/plugins/specweave/agents/pm/AGENT.md.bak2 +1754 -0
- package/plugins/specweave/commands/check-hooks.md +257 -0
- package/plugins/specweave/hooks/docs-changed.sh +9 -1
- package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
- package/plugins/specweave/hooks/human-input-required.sh +9 -1
- package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
- package/plugins/specweave/hooks/post-edit-spec.sh +202 -31
- package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
- package/plugins/specweave/hooks/post-increment-change.sh +6 -1
- package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
- package/plugins/specweave/hooks/post-increment-completion.sh +6 -1
- package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
- package/plugins/specweave/hooks/post-increment-planning.sh +6 -1
- package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
- package/plugins/specweave/hooks/post-increment-status-change.sh +6 -1
- package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
- package/plugins/specweave/hooks/post-metadata-change.sh +7 -1
- package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
- package/plugins/specweave/hooks/post-task-completion.sh +225 -228
- package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
- package/plugins/specweave/hooks/post-write-spec.sh +207 -31
- package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
- package/plugins/specweave/hooks/pre-edit-spec.sh +151 -0
- package/plugins/specweave/hooks/pre-implementation.sh +9 -1
- package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
- package/plugins/specweave/hooks/pre-task-completion.sh +14 -8
- package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
- package/plugins/specweave/hooks/pre-tool-use.sh +9 -1
- package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
- package/plugins/specweave/hooks/pre-write-spec.sh +151 -0
- package/plugins/specweave/hooks/test-pretooluse-env.sh +72 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
- package/plugins/specweave/skills/compliance-architecture/SKILL.md +374 -0
- package/plugins/specweave/skills/external-sync-wizard/SKILL.md +610 -0
- package/plugins/specweave/skills/pm-closure-validation/SKILL.md +541 -0
- package/plugins/specweave/skills/roadmap-planner/SKILL.md +473 -0
- package/plugins/specweave-ado/commands/refresh-cache.js +25 -0
- package/plugins/specweave-ado/commands/refresh-cache.ts +40 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh +9 -2
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
- package/plugins/specweave-ado/hooks/post-task-completion.sh +10 -2
- package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-github/hooks/post-task-completion.sh +10 -2
- package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
- package/plugins/specweave-jira/commands/refresh-cache.js +25 -0
- package/plugins/specweave-jira/commands/refresh-cache.ts +40 -0
- package/plugins/specweave-jira/hooks/post-task-completion.sh +10 -2
- package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-kafka-streams/commands/topology.md +437 -0
- package/plugins/specweave-n8n/commands/workflow-template.md +262 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +252 -6465
- 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
|
-
#
|
|
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
|
|
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
|
-
#
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
73
|
-
#
|
|
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:
|
|
76
|
-
|
|
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
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|