specweave 0.26.10 → 0.26.13
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 +95 -520
- package/dist/plugins/specweave-github/lib/completion-calculator.d.ts +4 -1
- package/dist/plugins/specweave-github/lib/completion-calculator.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/completion-calculator.js +49 -29
- package/dist/plugins/specweave-github/lib/completion-calculator.js.map +1 -1
- package/dist/src/cli/commands/init.js +2 -2
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/core/increment/increment-archiver.d.ts +3 -0
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
- package/dist/src/core/increment/increment-archiver.js +35 -4
- package/dist/src/core/increment/increment-archiver.js.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.d.ts +5 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.js +66 -18
- package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/commands/specweave-archive.md +10 -1
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/lib/update-active-increment.sh +96 -0
- package/plugins/specweave/hooks/lib/update-status-line.sh +153 -189
- package/plugins/specweave/hooks/post-edit-write-consolidated.sh +6 -0
- package/plugins/specweave/hooks/post-metadata-change.sh +9 -0
- package/plugins/specweave/hooks/post-task-completion.sh +8 -0
- package/plugins/specweave/hooks/post-task-edit.sh +37 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +43 -53
- package/plugins/specweave/hooks/pre-tool-use.sh +5 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +143 -289
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +18 -0
- package/plugins/specweave-github/lib/completion-calculator.js +34 -16
- package/plugins/specweave-github/lib/completion-calculator.ts +54 -32
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +27 -0
- package/src/templates/AGENTS.md.template +301 -2452
- package/src/templates/CLAUDE.md.template +99 -667
|
@@ -1,225 +1,189 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
# update-status-line.sh (
|
|
3
|
+
# update-status-line.sh (v0.26.13 - ULTRA-OPTIMIZED for crash prevention)
|
|
4
4
|
#
|
|
5
5
|
# Updates status line cache with current increment progress.
|
|
6
6
|
# Shows: [increment-name] ████░░░░ X/Y tasks | A/B ACs (Z open)
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
# 1.
|
|
10
|
-
# 2.
|
|
11
|
-
# 3.
|
|
12
|
-
# 4.
|
|
13
|
-
# 5.
|
|
14
|
-
# 6.
|
|
8
|
+
# OPTIMIZATIONS (v0.26.13):
|
|
9
|
+
# 1. TTL-based throttling (10s) - longer cache = fewer runs
|
|
10
|
+
# 2. Mtime checking via find -newer (no stat loops!)
|
|
11
|
+
# 3. Pure bash counting + JSON generation (NO jq!)
|
|
12
|
+
# 4. Single-pass awk for all counting (1 process vs 5 greps)
|
|
13
|
+
# 5. Exclude _archive/ with find -not -path
|
|
14
|
+
# 6. Lock file to prevent concurrent runs
|
|
15
15
|
#
|
|
16
|
-
# Performance:
|
|
16
|
+
# Performance: <5ms (cached) / 15-25ms (full scan)
|
|
17
17
|
#
|
|
18
|
-
# EMERGENCY FIX (v0.24.4): Changed from set -euo pipefail to set +e
|
|
19
|
-
# CRITICAL: Hooks MUST use set +e to prevent Claude Code crashes!
|
|
20
|
-
# See: CLAUDE.md section 9a - Hook Performance & Safety
|
|
21
|
-
|
|
22
18
|
set +e
|
|
23
19
|
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# PROJECT ROOT (FAST - cached in env if available)
|
|
22
|
+
# ============================================================================
|
|
23
|
+
if [[ -n "$SPECWEAVE_PROJECT_ROOT" ]] && [[ -d "$SPECWEAVE_PROJECT_ROOT/.specweave" ]]; then
|
|
24
|
+
PROJECT_ROOT="$SPECWEAVE_PROJECT_ROOT"
|
|
25
|
+
else
|
|
26
|
+
PROJECT_ROOT="$PWD"
|
|
27
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
28
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
33
29
|
done
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
PROJECT_ROOT=$(find_project_root)
|
|
30
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && PROJECT_ROOT="$PWD"
|
|
31
|
+
fi
|
|
38
32
|
|
|
39
33
|
# ============================================================================
|
|
40
|
-
#
|
|
34
|
+
# ULTRA-FAST EXITS
|
|
41
35
|
# ============================================================================
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# This is a CRITICAL part of Fix #4 in the root cause analysis:
|
|
47
|
-
# See: .specweave/increments/0051-*/reports/GITHUB-COMMENT-RECURSION-ROOT-CAUSE-2025-11-24.md
|
|
36
|
+
STATE_DIR="$PROJECT_ROOT/.specweave/state"
|
|
37
|
+
CACHE_FILE="$STATE_DIR/status-line.json"
|
|
38
|
+
INCREMENTS_DIR="$PROJECT_ROOT/.specweave/increments"
|
|
39
|
+
LOCK_FILE="$STATE_DIR/.status-update.lock"
|
|
48
40
|
|
|
49
|
-
|
|
41
|
+
# No .specweave? Exit immediately
|
|
42
|
+
[[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
# Recursion guard
|
|
45
|
+
[[ -f "$STATE_DIR/.hook-recursion-guard" ]] && exit 0
|
|
46
|
+
|
|
47
|
+
# Lock check (prevent concurrent runs - causes crashes!)
|
|
48
|
+
if [[ -f "$LOCK_FILE" ]]; then
|
|
49
|
+
# Check if lock is stale (>30s)
|
|
50
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
51
|
+
LOCK_AGE=$(( $(date +%s) - $(stat -f %m "$LOCK_FILE" 2>/dev/null || echo 0) ))
|
|
52
|
+
else
|
|
53
|
+
LOCK_AGE=$(( $(date +%s) - $(stat -c %Y "$LOCK_FILE" 2>/dev/null || echo 0) ))
|
|
54
|
+
fi
|
|
55
|
+
[[ $LOCK_AGE -lt 30 ]] && exit 0
|
|
55
56
|
fi
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if [[ -d "$INCREMENTS_DIR" ]]; then
|
|
71
|
-
for spec_file in "$INCREMENTS_DIR"/*/spec.md; do
|
|
72
|
-
if [[ -f "$spec_file" ]]; then
|
|
73
|
-
# Parse YAML frontmatter for status (source of truth)
|
|
74
|
-
status=$(grep -m1 "^status:" "$spec_file" 2>/dev/null | cut -d: -f2 | tr -d ' ' || echo "")
|
|
75
|
-
|
|
76
|
-
# Check if increment is open (active, planning, or in-progress)
|
|
77
|
-
# ONLY accepts official IncrementStatus enum values
|
|
78
|
-
if [[ "$status" == "active" ]] || [[ "$status" == "planning" ]] || [[ "$status" == "in-progress" ]]; then
|
|
79
|
-
increment_id=$(basename "$(dirname "$spec_file")")
|
|
80
|
-
# Parse created date from spec.md YAML frontmatter
|
|
81
|
-
created=$(grep -m1 "^created:" "$spec_file" 2>/dev/null | cut -d: -f2- | tr -d ' ' || echo "1970-01-01")
|
|
82
|
-
|
|
83
|
-
# Write to temp file
|
|
84
|
-
echo "$created $increment_id" >> "$TMP_FILE"
|
|
85
|
-
fi
|
|
86
|
-
fi
|
|
87
|
-
done
|
|
58
|
+
# ============================================================================
|
|
59
|
+
# TTL CHECK (10 seconds - balanced for UX vs performance)
|
|
60
|
+
# ============================================================================
|
|
61
|
+
TTL_SECONDS=10
|
|
62
|
+
|
|
63
|
+
if [[ -f "$CACHE_FILE" ]]; then
|
|
64
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
65
|
+
CACHE_AGE=$(( $(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0) ))
|
|
66
|
+
else
|
|
67
|
+
CACHE_AGE=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) ))
|
|
68
|
+
fi
|
|
69
|
+
[[ $CACHE_AGE -lt $TTL_SECONDS ]] && exit 0
|
|
88
70
|
fi
|
|
89
71
|
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if [[
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
current: null,
|
|
97
|
-
openCount: 0,
|
|
98
|
-
lastUpdate: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
|
|
99
|
-
}' > "$CACHE_FILE"
|
|
100
|
-
rm -f "$TMP_FILE"
|
|
72
|
+
# ============================================================================
|
|
73
|
+
# NO INCREMENTS? Write empty cache and exit
|
|
74
|
+
# ============================================================================
|
|
75
|
+
if [[ ! -d "$INCREMENTS_DIR" ]]; then
|
|
76
|
+
mkdir -p "$STATE_DIR"
|
|
77
|
+
printf '{"current":null,"openCount":0,"lastUpdate":"%s"}' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$CACHE_FILE"
|
|
101
78
|
exit 0
|
|
102
79
|
fi
|
|
103
80
|
|
|
104
|
-
#
|
|
105
|
-
|
|
81
|
+
# ============================================================================
|
|
82
|
+
# ACQUIRE LOCK
|
|
83
|
+
# ============================================================================
|
|
84
|
+
mkdir -p "$STATE_DIR"
|
|
85
|
+
echo $$ > "$LOCK_FILE"
|
|
86
|
+
trap 'rm -f "$LOCK_FILE"' EXIT
|
|
87
|
+
|
|
88
|
+
# ============================================================================
|
|
89
|
+
# FIND ACTIVE INCREMENTS (single find, no xargs)
|
|
90
|
+
# ============================================================================
|
|
91
|
+
ACTIVE_FILES=""
|
|
92
|
+
OPEN_COUNT=0
|
|
93
|
+
OLDEST_DATE="9999-99-99"
|
|
94
|
+
CURRENT_INCREMENT=""
|
|
106
95
|
|
|
107
|
-
|
|
108
|
-
|
|
96
|
+
while IFS= read -r spec_file; do
|
|
97
|
+
[[ -z "$spec_file" ]] && continue
|
|
109
98
|
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
PERCENTAGE=0
|
|
99
|
+
# Quick status check with head + grep (faster than full file grep)
|
|
100
|
+
if head -20 "$spec_file" 2>/dev/null | grep -qE '^status:\s*(active|planning|in-progress)'; then
|
|
101
|
+
OPEN_COUNT=$((OPEN_COUNT + 1))
|
|
102
|
+
increment_id=$(basename "$(dirname "$spec_file")")
|
|
115
103
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
COUNT_TASKS_CLI="$PROJECT_ROOT/dist/src/cli/count-tasks.js"
|
|
104
|
+
# Get created date (first 30 lines only)
|
|
105
|
+
created=$(head -30 "$spec_file" 2>/dev/null | grep -m1 "^created:" | cut -d: -f2- | tr -d ' "' || echo "9999-99-99")
|
|
119
106
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
TOTAL_TASKS=$(echo "$TASK_COUNTS" | jq -r '.total' 2>/dev/null || echo 0)
|
|
124
|
-
COMPLETED_TASKS=$(echo "$TASK_COUNTS" | jq -r '.completed' 2>/dev/null || echo 0)
|
|
125
|
-
PERCENTAGE=$(echo "$TASK_COUNTS" | jq -r '.percentage' 2>/dev/null || echo 0)
|
|
126
|
-
else
|
|
127
|
-
# Fallback to legacy counting if CLI not available (graceful degradation)
|
|
128
|
-
# Count total tasks (## T- or ### T- headings)
|
|
129
|
-
TOTAL_TASKS=$(grep -cE '^##+ T-' "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
130
|
-
TOTAL_TASKS=$(echo "$TOTAL_TASKS" | tr -d '\n\r ' || echo 0)
|
|
131
|
-
|
|
132
|
-
# Count completed tasks - recognize all three completion marker formats:
|
|
133
|
-
# 1. **Completed**: <date> (preferred format)
|
|
134
|
-
# 2. **Status**: [x] (legacy format)
|
|
135
|
-
# 3. [x] at line start (legacy checkbox format)
|
|
136
|
-
COMPLETED_TASKS=$(grep -cE '(\*\*Completed\*\*:|\*\*Status\*\*:\s*\[x\]|^\[x\])' "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
137
|
-
COMPLETED_TASKS=$(echo "$COMPLETED_TASKS" | tr -d '\n\r ' || echo 0)
|
|
138
|
-
|
|
139
|
-
# Calculate percentage
|
|
140
|
-
if [[ $TOTAL_TASKS -gt 0 ]]; then
|
|
141
|
-
PERCENTAGE=$((COMPLETED_TASKS * 100 / TOTAL_TASKS))
|
|
107
|
+
if [[ "$created" < "$OLDEST_DATE" ]]; then
|
|
108
|
+
OLDEST_DATE="$created"
|
|
109
|
+
CURRENT_INCREMENT="$increment_id"
|
|
142
110
|
fi
|
|
111
|
+
|
|
112
|
+
ACTIVE_FILES="$ACTIVE_FILES $spec_file"
|
|
143
113
|
fi
|
|
144
|
-
|
|
114
|
+
done < <(find "$INCREMENTS_DIR" -maxdepth 2 -name "spec.md" -not -path "*/_archive/*" 2>/dev/null)
|
|
145
115
|
|
|
146
|
-
#
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
OPEN_ACS=0
|
|
151
|
-
|
|
152
|
-
if [[ -f "$SPEC_FILE" ]]; then
|
|
153
|
-
# Count total ACs: both checked and unchecked
|
|
154
|
-
# Pattern: - [ ] **AC- OR - [x] **AC-
|
|
155
|
-
TOTAL_ACS=$(grep -cE '^- \[(x| )\] \*\*AC-' "$SPEC_FILE" 2>/dev/null || echo 0)
|
|
156
|
-
TOTAL_ACS=$(echo "$TOTAL_ACS" | tr -d '\n\r ' || echo 0)
|
|
157
|
-
|
|
158
|
-
# Count completed ACs (checked)
|
|
159
|
-
# Pattern: - [x] **AC-
|
|
160
|
-
COMPLETED_ACS=$(grep -cE '^- \[x\] \*\*AC-' "$SPEC_FILE" 2>/dev/null || echo 0)
|
|
161
|
-
COMPLETED_ACS=$(echo "$COMPLETED_ACS" | tr -d '\n\r ' || echo 0)
|
|
162
|
-
|
|
163
|
-
# Count open ACs (unchecked)
|
|
164
|
-
# Pattern: - [ ] **AC-
|
|
165
|
-
OPEN_ACS=$(grep -cE '^- \[ \] \*\*AC-' "$SPEC_FILE" 2>/dev/null || echo 0)
|
|
166
|
-
OPEN_ACS=$(echo "$OPEN_ACS" | tr -d '\n\r ' || echo 0)
|
|
116
|
+
# No active increments?
|
|
117
|
+
if [[ -z "$CURRENT_INCREMENT" ]]; then
|
|
118
|
+
printf '{"current":null,"openCount":0,"lastUpdate":"%s"}' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$CACHE_FILE"
|
|
119
|
+
exit 0
|
|
167
120
|
fi
|
|
168
121
|
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
[[ "$
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
[[ "$OPEN_COUNT" =~ ^[0-9]+$ ]] || OPEN_COUNT=0
|
|
186
|
-
|
|
187
|
-
# Generate cache to temp file first (atomic operation)
|
|
188
|
-
if jq -n \
|
|
189
|
-
--arg id "$CURRENT_INCREMENT" \
|
|
190
|
-
--arg name "$INCREMENT_NAME" \
|
|
191
|
-
--argjson completed "$COMPLETED_TASKS" \
|
|
192
|
-
--argjson total "$TOTAL_TASKS" \
|
|
193
|
-
--argjson percentage "$PERCENTAGE" \
|
|
194
|
-
--argjson acsCompleted "$COMPLETED_ACS" \
|
|
195
|
-
--argjson acsTotal "$TOTAL_ACS" \
|
|
196
|
-
--argjson openCount "$OPEN_COUNT" \
|
|
197
|
-
'{
|
|
198
|
-
current: {
|
|
199
|
-
id: $id,
|
|
200
|
-
name: $name,
|
|
201
|
-
completed: $completed,
|
|
202
|
-
total: $total,
|
|
203
|
-
percentage: $percentage,
|
|
204
|
-
acsCompleted: $acsCompleted,
|
|
205
|
-
acsTotal: $acsTotal
|
|
206
|
-
},
|
|
207
|
-
openCount: $openCount,
|
|
208
|
-
lastUpdate: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
|
|
209
|
-
}' > "$TMP_CACHE_FILE" 2>/dev/null; then
|
|
210
|
-
|
|
211
|
-
# Validate generated JSON before replacing cache (corruption prevention)
|
|
212
|
-
if jq empty "$TMP_CACHE_FILE" 2>/dev/null; then
|
|
213
|
-
mv "$TMP_CACHE_FILE" "$CACHE_FILE"
|
|
214
|
-
else
|
|
215
|
-
# Invalid JSON generated - keep old cache
|
|
216
|
-
echo "[$(date)] ERROR: Generated invalid JSON, keeping old cache" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
217
|
-
rm -f "$TMP_CACHE_FILE"
|
|
122
|
+
# ============================================================================
|
|
123
|
+
# MTIME CHECK (using find -newer - single syscall!)
|
|
124
|
+
# ============================================================================
|
|
125
|
+
MTIME_FILE="$STATE_DIR/.status-mtime-$CURRENT_INCREMENT"
|
|
126
|
+
|
|
127
|
+
if [[ -f "$MTIME_FILE" ]] && [[ -f "$CACHE_FILE" ]]; then
|
|
128
|
+
# Check if any relevant files are newer than our marker
|
|
129
|
+
TASKS_FILE="$INCREMENTS_DIR/$CURRENT_INCREMENT/tasks.md"
|
|
130
|
+
SPEC_FILE="$INCREMENTS_DIR/$CURRENT_INCREMENT/spec.md"
|
|
131
|
+
|
|
132
|
+
NEWER_FILES=$(find "$SPEC_FILE" "$TASKS_FILE" -newer "$MTIME_FILE" 2>/dev/null | head -1)
|
|
133
|
+
|
|
134
|
+
if [[ -z "$NEWER_FILES" ]]; then
|
|
135
|
+
# No changes - just touch cache to reset TTL
|
|
136
|
+
touch "$CACHE_FILE"
|
|
137
|
+
exit 0
|
|
218
138
|
fi
|
|
219
|
-
else
|
|
220
|
-
# jq generation failed - keep old cache
|
|
221
|
-
echo "[$(date)] ERROR: jq failed to generate status cache (exit $?)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
222
|
-
rm -f "$TMP_CACHE_FILE"
|
|
223
139
|
fi
|
|
224
140
|
|
|
141
|
+
# ============================================================================
|
|
142
|
+
# SINGLE-PASS COUNTING WITH AWK (replaces 5 grep calls!)
|
|
143
|
+
# ============================================================================
|
|
144
|
+
TASKS_FILE="$INCREMENTS_DIR/$CURRENT_INCREMENT/tasks.md"
|
|
145
|
+
SPEC_FILE="$INCREMENTS_DIR/$CURRENT_INCREMENT/spec.md"
|
|
146
|
+
|
|
147
|
+
# Count tasks with single awk call
|
|
148
|
+
read -r TOTAL_TASKS COMPLETED_TASKS < <(
|
|
149
|
+
awk '
|
|
150
|
+
/^###? T-/ { total++ }
|
|
151
|
+
/\*\*Completed\*\*:|\*\*Status\*\*:[ \t]*\[x\]/ { completed++ }
|
|
152
|
+
END { print total+0, completed+0 }
|
|
153
|
+
' "$TASKS_FILE" 2>/dev/null || echo "0 0"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Count ACs with single awk call
|
|
157
|
+
# Supports both formats: "- [ ] AC-US1-01:" and "- [ ] **AC-US1-01**:"
|
|
158
|
+
read -r TOTAL_ACS COMPLETED_ACS < <(
|
|
159
|
+
awk '
|
|
160
|
+
/^- \[(x| )\] (\*\*)?AC-/ { total++ }
|
|
161
|
+
/^- \[x\] (\*\*)?AC-/ { completed++ }
|
|
162
|
+
END { print total+0, completed+0 }
|
|
163
|
+
' "$SPEC_FILE" 2>/dev/null || echo "0 0"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Calculate percentage (pure bash)
|
|
167
|
+
PERCENTAGE=0
|
|
168
|
+
[[ ${TOTAL_TASKS:-0} -gt 0 ]] && PERCENTAGE=$((${COMPLETED_TASKS:-0} * 100 / TOTAL_TASKS))
|
|
169
|
+
|
|
170
|
+
# ============================================================================
|
|
171
|
+
# WRITE CACHE (PURE BASH - NO jq!)
|
|
172
|
+
# ============================================================================
|
|
173
|
+
# Sanitize values
|
|
174
|
+
TOTAL_TASKS=${TOTAL_TASKS:-0}
|
|
175
|
+
COMPLETED_TASKS=${COMPLETED_TASKS:-0}
|
|
176
|
+
TOTAL_ACS=${TOTAL_ACS:-0}
|
|
177
|
+
COMPLETED_ACS=${COMPLETED_ACS:-0}
|
|
178
|
+
OPEN_COUNT=${OPEN_COUNT:-0}
|
|
179
|
+
PERCENTAGE=${PERCENTAGE:-0}
|
|
180
|
+
|
|
181
|
+
# Generate JSON directly (avoids jq subprocess entirely!)
|
|
182
|
+
cat > "$CACHE_FILE" << EOF
|
|
183
|
+
{"current":{"id":"$CURRENT_INCREMENT","name":"$CURRENT_INCREMENT","completed":$COMPLETED_TASKS,"total":$TOTAL_TASKS,"percentage":$PERCENTAGE,"acsCompleted":$COMPLETED_ACS,"acsTotal":$TOTAL_ACS},"openCount":$OPEN_COUNT,"lastUpdate":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
|
|
184
|
+
EOF
|
|
185
|
+
|
|
186
|
+
# Update mtime marker
|
|
187
|
+
touch "$MTIME_FILE"
|
|
188
|
+
|
|
225
189
|
exit 0
|
|
@@ -49,6 +49,12 @@ find_project_root() {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
PROJECT_ROOT=$(find_project_root)
|
|
52
|
+
|
|
53
|
+
# ULTRA-FAST EARLY EXIT FOR NON-SPECWEAVE PROJECTS (T-006 - v0.26.15)
|
|
54
|
+
if [[ ! -d "$PROJECT_ROOT/.specweave" ]]; then
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
|
|
52
58
|
LOGS_DIR="$PROJECT_ROOT/.specweave/logs"
|
|
53
59
|
DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
|
|
54
60
|
|
|
@@ -192,6 +192,15 @@ case "$CURRENT_STATUS" in
|
|
|
192
192
|
fi
|
|
193
193
|
;;
|
|
194
194
|
|
|
195
|
+
active|planning|in-progress)
|
|
196
|
+
# Increment became active - MUST register in active-increment.json!
|
|
197
|
+
# CRITICAL FIX (v0.26.15): post-task-completion.sh depends on this file
|
|
198
|
+
# Without registration, ALL sync operations are skipped!
|
|
199
|
+
echo "[$(date)] post-metadata-change: Status is $CURRENT_STATUS - registering as active + updating status line" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
200
|
+
bash "$HOOK_DIR/lib/update-active-increment.sh" 2>/dev/null || true
|
|
201
|
+
bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
|
|
202
|
+
;;
|
|
203
|
+
|
|
195
204
|
*)
|
|
196
205
|
# Other metadata changes (e.g., task completion count, AC count)
|
|
197
206
|
# Just update status line to reflect new progress
|
|
@@ -66,6 +66,14 @@ find_project_root() {
|
|
|
66
66
|
PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
|
|
67
67
|
cd "$PROJECT_ROOT" 2>/dev/null || true
|
|
68
68
|
|
|
69
|
+
# ============================================================================
|
|
70
|
+
# ULTRA-FAST EARLY EXIT FOR NON-SPECWEAVE PROJECTS (T-006 - v0.26.15)
|
|
71
|
+
# ============================================================================
|
|
72
|
+
# Skip ALL processing if not a SpecWeave project - saves ~50-100ms
|
|
73
|
+
if [[ ! -d "$PROJECT_ROOT/.specweave" ]]; then
|
|
74
|
+
exit 0
|
|
75
|
+
fi
|
|
76
|
+
|
|
69
77
|
# ============================================================================
|
|
70
78
|
# RECURSION PREVENTION (CRITICAL - v0.26.0 - FILE-BASED GUARD)
|
|
71
79
|
# ============================================================================
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# post-task-edit.sh (v0.26.16)
|
|
4
|
+
#
|
|
5
|
+
# Lightweight hook triggered after Edit on tasks.md
|
|
6
|
+
# ONLY updates status line cache - no heavy processing!
|
|
7
|
+
#
|
|
8
|
+
# Purpose: Keep status line in sync when tasks are marked complete via Edit
|
|
9
|
+
#
|
|
10
|
+
# Performance target: <20ms (just calls update-status-line.sh)
|
|
11
|
+
#
|
|
12
|
+
set +e
|
|
13
|
+
|
|
14
|
+
# EMERGENCY KILL SWITCH
|
|
15
|
+
[[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
|
|
16
|
+
|
|
17
|
+
# Find hook directory
|
|
18
|
+
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
|
|
20
|
+
# Consume stdin (required for PostToolUse hooks)
|
|
21
|
+
cat > /dev/null
|
|
22
|
+
|
|
23
|
+
# Update status line cache (force refresh by removing mtime marker)
|
|
24
|
+
PROJECT_ROOT="$PWD"
|
|
25
|
+
while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
|
|
26
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
27
|
+
done
|
|
28
|
+
|
|
29
|
+
if [[ -d "$PROJECT_ROOT/.specweave" ]]; then
|
|
30
|
+
# Remove mtime markers to force full refresh
|
|
31
|
+
rm -f "$PROJECT_ROOT/.specweave/state/.status-mtime-"* 2>/dev/null || true
|
|
32
|
+
|
|
33
|
+
# Update status line
|
|
34
|
+
bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
exit 0
|
|
@@ -1,83 +1,73 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
# SpecWeave Pre-Command Deduplication Hook
|
|
3
|
+
# SpecWeave Pre-Command Deduplication Hook (v0.26.14 - OPTIMIZED)
|
|
4
4
|
# Fires BEFORE any command executes (UserPromptSubmit hook)
|
|
5
5
|
# Purpose: Prevent duplicate command invocations within configurable time window
|
|
6
|
+
#
|
|
7
|
+
# OPTIMIZATIONS (v0.26.14):
|
|
8
|
+
# 1. Early exit for non-SpecWeave projects (<1ms)
|
|
9
|
+
# 2. Skip node spawn if deduplicator not available
|
|
10
|
+
# 3. Pure bash stdin reading
|
|
6
11
|
|
|
7
|
-
set +e
|
|
12
|
+
set +e
|
|
8
13
|
|
|
9
14
|
# ==============================================================================
|
|
10
|
-
#
|
|
15
|
+
# ULTRA-FAST EARLY EXIT
|
|
11
16
|
# ==============================================================================
|
|
12
17
|
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
18
|
+
# Quick check: If no .specweave in cwd or nearby, just approve
|
|
19
|
+
if [[ ! -d ".specweave" ]] && [[ ! -d "../.specweave" ]] && [[ ! -d "../../.specweave" ]]; then
|
|
20
|
+
cat /dev/stdin > /dev/null # Drain stdin
|
|
21
|
+
echo '{"decision":"approve"}'
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
33
24
|
|
|
34
25
|
# Read input JSON from stdin
|
|
35
26
|
INPUT=$(cat)
|
|
36
27
|
|
|
37
28
|
# ==============================================================================
|
|
38
|
-
# DEDUPLICATION CHECK:
|
|
29
|
+
# DEDUPLICATION CHECK: Pure bash with file-based cache
|
|
39
30
|
# ==============================================================================
|
|
40
31
|
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
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)
|
|
32
|
+
# Use file-based deduplication (no node!) with 1-second TTL
|
|
33
|
+
CACHE_DIR=".specweave/state/.dedup-cache"
|
|
34
|
+
mkdir -p "$CACHE_DIR" 2>/dev/null || true
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
36
|
+
# Extract command from input (use jq if available, fallback to grep)
|
|
37
|
+
if command -v jq >/dev/null 2>&1; then
|
|
38
|
+
COMMAND=$(echo "$INPUT" | jq -r '.prompt // ""' 2>/dev/null | head -c 100 | tr -d '\n' | tr '/' '_' | tr ' ' '_')
|
|
39
|
+
else
|
|
40
|
+
COMMAND=$(echo "$INPUT" | grep -oP '"prompt"\s*:\s*"\K[^"]{0,100}' 2>/dev/null | tr '/' '_' | tr ' ' '_')
|
|
41
|
+
fi
|
|
52
42
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
CACHE_SIZE=$(echo "$STATS" | grep -o '"currentCacheSize":[0-9]*' | cut -d':' -f2 || echo "1")
|
|
43
|
+
# Only check for SpecWeave commands
|
|
44
|
+
if [[ "$COMMAND" == *specweave* ]]; then
|
|
45
|
+
CACHE_FILE="$CACHE_DIR/${COMMAND:0:50}.lock"
|
|
57
46
|
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
# Check if cached and recent (within 1 second)
|
|
48
|
+
if [[ -f "$CACHE_FILE" ]]; then
|
|
49
|
+
CACHE_AGE=$(($(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0)))
|
|
50
|
+
if [[ "$CACHE_AGE" -lt 1 ]]; then
|
|
51
|
+
cat <<EOF
|
|
60
52
|
{
|
|
61
53
|
"decision": "block",
|
|
62
|
-
"reason": "
|
|
54
|
+
"reason": "Duplicate command detected (within 1 second). Wait a moment and try again."
|
|
63
55
|
}
|
|
64
56
|
EOF
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
70
59
|
fi
|
|
60
|
+
|
|
61
|
+
# Update cache timestamp
|
|
62
|
+
touch "$CACHE_FILE" 2>/dev/null || true
|
|
63
|
+
|
|
64
|
+
# Cleanup old cache files (>10 seconds old)
|
|
65
|
+
find "$CACHE_DIR" -type f -mmin +1 -delete 2>/dev/null &
|
|
71
66
|
fi
|
|
72
67
|
|
|
73
68
|
# ==============================================================================
|
|
74
|
-
# PASS THROUGH: No duplicate detected
|
|
69
|
+
# PASS THROUGH: No duplicate detected
|
|
75
70
|
# ==============================================================================
|
|
76
71
|
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
"decision": "approve"
|
|
80
|
-
}
|
|
81
|
-
EOF
|
|
82
|
-
|
|
72
|
+
echo '{"decision":"approve"}'
|
|
83
73
|
exit 0
|
|
@@ -45,6 +45,11 @@ find_project_root() {
|
|
|
45
45
|
PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
|
|
46
46
|
cd "$PROJECT_ROOT" 2>/dev/null || true
|
|
47
47
|
|
|
48
|
+
# ULTRA-FAST EARLY EXIT FOR NON-SPECWEAVE PROJECTS (T-006 - v0.26.15)
|
|
49
|
+
if [[ ! -d "$PROJECT_ROOT/.specweave" ]]; then
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
48
53
|
LOGS_DIR=".specweave/logs"
|
|
49
54
|
DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
|
|
50
55
|
|