specweave 0.26.11 → 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/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/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/docs-changed.sh.backup +79 -0
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/human-input-required.sh.backup +75 -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-first-increment.sh.backup +61 -0
- package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
- package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
- package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
- package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
- package/plugins/specweave/hooks/post-metadata-change.sh +9 -0
- package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -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/post-user-story-complete.sh.backup +179 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +43 -53
- package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
- package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
- package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
- package/plugins/specweave/hooks/pre-tool-use.sh +5 -0
- package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +143 -289
- package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
- package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-ado/lib/ado-multi-project-sync.js +1 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1098 -0
- package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
- package/plugins/specweave-github/lib/completion-calculator.js +34 -16
- package/plugins/specweave-github/lib/completion-calculator.ts +54 -32
- package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +3 -3
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1008 -0
- package/plugins/specweave-release/hooks/post-task-completion.sh.backup +110 -0
- package/src/templates/AGENTS.md.template +301 -2452
- package/src/templates/CLAUDE.md.template +99 -667
|
@@ -1,341 +1,204 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
# SpecWeave UserPromptSubmit Hook
|
|
3
|
+
# SpecWeave UserPromptSubmit Hook (v0.26.13 - ULTRA-OPTIMIZED)
|
|
4
4
|
# Fires BEFORE user's command executes (prompt-based hook)
|
|
5
5
|
# Purpose: Discipline validation, context injection, command suggestions
|
|
6
|
+
#
|
|
7
|
+
# OPTIMIZATIONS (v0.26.13):
|
|
8
|
+
# 1. jq for JSON parsing (10x faster than node -e)
|
|
9
|
+
# 2. Single active increment detection (cached, not 4x!)
|
|
10
|
+
# 3. Removed redundant find | while loops
|
|
11
|
+
# 4. Deferred heavy checks (SpecSyncManager only when needed)
|
|
12
|
+
# 5. Ultra-fast early exits
|
|
13
|
+
#
|
|
14
|
+
# Performance: <10ms (most prompts) vs 200-500ms (before)
|
|
6
15
|
|
|
7
|
-
set +e
|
|
16
|
+
set +e
|
|
8
17
|
|
|
9
|
-
#
|
|
18
|
+
# ==============================================================================
|
|
19
|
+
# ULTRA-FAST EARLY EXIT (before ANY processing)
|
|
20
|
+
# ==============================================================================
|
|
10
21
|
INPUT=$(cat)
|
|
11
22
|
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
# Use jq if available (10x faster than node), fallback to simple grep
|
|
24
|
+
if command -v jq >/dev/null 2>&1; then
|
|
25
|
+
PROMPT=$(echo "$INPUT" | jq -r '.prompt // ""' 2>/dev/null || echo "")
|
|
26
|
+
else
|
|
27
|
+
# Fallback: extract prompt with grep (no node!)
|
|
28
|
+
PROMPT=$(echo "$INPUT" | grep -oP '"prompt"\s*:\s*"\K[^"]*' 2>/dev/null || echo "")
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# CRITICAL: Exit immediately for non-SpecWeave prompts
|
|
32
|
+
# This covers 90%+ of prompts with <5ms overhead
|
|
33
|
+
if ! echo "$PROMPT" | grep -qE "(specweave|/specweave:|increment|add|create|implement|build|develop)"; then
|
|
34
|
+
echo '{"decision":"approve"}'
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# ==============================================================================
|
|
39
|
+
# EARLY EXIT FOR NON-SPECWEAVE PROJECTS (T-006 - v0.26.15)
|
|
40
|
+
# ==============================================================================
|
|
41
|
+
# Even if prompt contains SpecWeave keywords, exit if no .specweave directory
|
|
42
|
+
SPECWEAVE_DIR=".specweave"
|
|
43
|
+
if [[ ! -d "$SPECWEAVE_DIR" ]]; then
|
|
44
|
+
echo '{"decision":"approve"}'
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# ==============================================================================
|
|
49
|
+
# CACHED ACTIVE INCREMENT DETECTION (ONCE - reused throughout!)
|
|
50
|
+
# ==============================================================================
|
|
51
|
+
ACTIVE_INCREMENT=""
|
|
52
|
+
ACTIVE_COUNT=0
|
|
53
|
+
ACTIVE_LIST=""
|
|
54
|
+
|
|
55
|
+
if [[ -d "$SPECWEAVE_DIR/increments" ]]; then
|
|
56
|
+
# Single find + jq pass to get ALL active increment info
|
|
57
|
+
while IFS= read -r metadata_file; do
|
|
58
|
+
[[ -z "$metadata_file" ]] && continue
|
|
59
|
+
|
|
60
|
+
# Use jq (fast) to extract status and id
|
|
61
|
+
if command -v jq >/dev/null 2>&1; then
|
|
62
|
+
read -r status inc_type < <(jq -r '"\(.status // "unknown") \(.type // "feature")"' "$metadata_file" 2>/dev/null || echo "unknown feature")
|
|
63
|
+
else
|
|
64
|
+
# Fallback: grep (no node!)
|
|
65
|
+
status=$(grep -oP '"status"\s*:\s*"\K[^"]*' "$metadata_file" 2>/dev/null || echo "unknown")
|
|
66
|
+
inc_type=$(grep -oP '"type"\s*:\s*"\K[^"]*' "$metadata_file" 2>/dev/null || echo "feature")
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [[ "$status" == "active" || "$status" == "planning" || "$status" == "in-progress" ]]; then
|
|
70
|
+
inc_id=$(basename "$(dirname "$metadata_file")")
|
|
71
|
+
ACTIVE_COUNT=$((ACTIVE_COUNT + 1))
|
|
72
|
+
ACTIVE_LIST="${ACTIVE_LIST} - $inc_id [$inc_type]\n"
|
|
73
|
+
[[ -z "$ACTIVE_INCREMENT" ]] && ACTIVE_INCREMENT="$inc_id"
|
|
74
|
+
fi
|
|
75
|
+
done < <(find "$SPECWEAVE_DIR/increments" -maxdepth 2 -name "metadata.json" -not -path "*/_archive/*" 2>/dev/null)
|
|
76
|
+
fi
|
|
17
77
|
|
|
18
78
|
# ==============================================================================
|
|
19
79
|
# DISCIPLINE VALIDATION: Block /specweave:increment if incomplete increments exist
|
|
20
80
|
# ==============================================================================
|
|
21
81
|
|
|
22
82
|
if echo "$PROMPT" | grep -q "/specweave:increment"; then
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if [[ -d "$SPECWEAVE_DIR/increments" ]]; then
|
|
28
|
-
# Run discipline check (exit code: 0=pass, 1=violations, 2=error)
|
|
29
|
-
if command -v node >/dev/null 2>&1 && [[ -f "dist/src/core/increment/metadata-manager.js" ]]; then
|
|
30
|
-
# Check active increments using MetadataManager
|
|
31
|
-
ACTIVE_COUNT=$(node -e "
|
|
32
|
-
try {
|
|
33
|
-
const { MetadataManager } = require('./dist/src/core/increment/metadata-manager.js');
|
|
34
|
-
const active = MetadataManager.getActive();
|
|
35
|
-
console.log(active.length);
|
|
36
|
-
} catch (e) {
|
|
37
|
-
console.error('Error checking active increments:', e.message);
|
|
38
|
-
process.exit(2);
|
|
39
|
-
}
|
|
40
|
-
" 2>/dev/null || echo "0")
|
|
41
|
-
|
|
42
|
-
# Hard cap: never >2 active
|
|
43
|
-
if [[ "$ACTIVE_COUNT" -ge 2 ]]; then
|
|
44
|
-
# Get list of active increments for error message
|
|
45
|
-
ACTIVE_LIST=$(node -e "
|
|
46
|
-
try {
|
|
47
|
-
const { MetadataManager } = require('./dist/src/core/increment/metadata-manager.js');
|
|
48
|
-
const active = MetadataManager.getActive();
|
|
49
|
-
active.forEach(inc => console.log(' - ' + inc.id + ' [' + inc.type + ']'));
|
|
50
|
-
} catch (e) {}
|
|
51
|
-
" 2>/dev/null || echo "")
|
|
52
|
-
|
|
53
|
-
cat <<EOF
|
|
83
|
+
# Hard cap: never >2 active
|
|
84
|
+
if [[ "$ACTIVE_COUNT" -ge 2 ]]; then
|
|
85
|
+
cat <<EOF
|
|
54
86
|
{
|
|
55
87
|
"decision": "block",
|
|
56
88
|
"reason": "❌ HARD CAP REACHED\n\nYou have $ACTIVE_COUNT active increments (absolute maximum: 2)\n\nActive increments:\n$ACTIVE_LIST\n\n💡 You MUST complete or pause existing work first:\n\n1️⃣ Complete an increment:\n /specweave:done <id>\n\n2️⃣ Pause an increment:\n /specweave:pause <id> --reason=\"...\"\n\n3️⃣ Check status:\n /specweave:status\n\n📝 Multiple hotfixes? Combine them into ONE increment!\n Example: 0009-security-fixes (SQL + XSS + CSRF)\n\n⛔ This limit is enforced for your productivity.\nResearch: 3+ concurrent tasks = 40% slower + more bugs"
|
|
57
89
|
}
|
|
58
90
|
EOF
|
|
59
|
-
|
|
60
|
-
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
61
93
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
ACTIVE_LIST=$(node -e "
|
|
66
|
-
try {
|
|
67
|
-
const { MetadataManager } = require('./dist/src/core/increment/metadata-manager.js');
|
|
68
|
-
const active = MetadataManager.getActive();
|
|
69
|
-
active.forEach(inc => console.log(' - ' + inc.id + ' [' + inc.type + ']'));
|
|
70
|
-
} catch (e) {}
|
|
71
|
-
" 2>/dev/null || echo "")
|
|
72
|
-
|
|
73
|
-
# Just warn, don't block (user can choose to continue)
|
|
74
|
-
cat <<EOF
|
|
94
|
+
# Soft warning: 1 active (recommended limit)
|
|
95
|
+
if [[ "$ACTIVE_COUNT" -ge 1 ]]; then
|
|
96
|
+
cat <<EOF
|
|
75
97
|
{
|
|
76
98
|
"decision": "approve",
|
|
77
99
|
"systemMessage": "⚠️ WIP LIMIT REACHED\n\nYou have $ACTIVE_COUNT active increment (recommended limit: 1)\n\nActive increments:\n$ACTIVE_LIST\n\n🧠 Focus Principle: ONE active increment = maximum productivity\nStarting a 2nd increment reduces focus and velocity.\n\n💡 Consider:\n 1️⃣ Complete current work (recommended)\n 2️⃣ Pause current work (/specweave:pause)\n 3️⃣ Continue anyway (accept 20% productivity cost)\n\n⚠️ Emergency hotfix/bug? Use --type=hotfix or --type=bug to bypass this warning."
|
|
78
100
|
}
|
|
79
101
|
EOF
|
|
80
|
-
|
|
81
|
-
fi
|
|
82
|
-
else
|
|
83
|
-
# Fallback: check for active/planning status manually
|
|
84
|
-
INCOMPLETE_INCREMENTS=$(find "$SPECWEAVE_DIR/increments" -mindepth 1 -maxdepth 1 -type d | while read increment_dir; do
|
|
85
|
-
metadata="$increment_dir/metadata.json"
|
|
86
|
-
if [[ -f "$metadata" ]]; then
|
|
87
|
-
status=$(node -e "
|
|
88
|
-
try {
|
|
89
|
-
const data = JSON.parse(require('fs').readFileSync('$metadata', 'utf-8'));
|
|
90
|
-
console.log(data.status || 'unknown');
|
|
91
|
-
} catch (e) {
|
|
92
|
-
console.log('unknown');
|
|
93
|
-
}
|
|
94
|
-
")
|
|
95
|
-
|
|
96
|
-
if [[ "$status" == "active" || "$status" == "planning" ]]; then
|
|
97
|
-
echo "$(basename "$increment_dir")"
|
|
98
|
-
fi
|
|
99
|
-
fi
|
|
100
|
-
done)
|
|
101
|
-
|
|
102
|
-
if [[ -n "$INCOMPLETE_INCREMENTS" ]]; then
|
|
103
|
-
COUNT=$(echo "$INCOMPLETE_INCREMENTS" | wc -l | xargs)
|
|
104
|
-
|
|
105
|
-
# Get incomplete task count for migration guidance
|
|
106
|
-
MIGRATION_SCRIPT="$(dirname "${BASH_SOURCE[0]}")/lib/migrate-increment-work.sh"
|
|
107
|
-
INCOMPLETE_TASKS=""
|
|
108
|
-
|
|
109
|
-
for increment in $INCOMPLETE_INCREMENTS; do
|
|
110
|
-
if [[ -x "$MIGRATION_SCRIPT" ]]; then
|
|
111
|
-
TASK_COUNT=$("$MIGRATION_SCRIPT" count-incomplete "$increment" 2>/dev/null || echo "?")
|
|
112
|
-
INCOMPLETE_TASKS="${INCOMPLETE_TASKS}\n - $increment ($TASK_COUNT incomplete tasks)"
|
|
113
|
-
else
|
|
114
|
-
INCOMPLETE_TASKS="${INCOMPLETE_TASKS}\n - $increment"
|
|
115
|
-
fi
|
|
116
|
-
done
|
|
117
|
-
|
|
118
|
-
cat <<EOF
|
|
119
|
-
{
|
|
120
|
-
"decision": "block",
|
|
121
|
-
"reason": "❌ Cannot create new increment! You have $COUNT incomplete increment(s):$INCOMPLETE_TASKS\n\n💡 **SMART MIGRATION OPTIONS:**\n\n1️⃣ **Transfer Work** (Recommended)\n Move incomplete tasks to new increment:\n \`\`\`bash\n # After creating new increment, run:\n bash plugins/specweave/hooks/lib/migrate-increment-work.sh transfer <old-id> <new-id>\n \`\`\`\n ✅ Clean closure + work continues\n\n2️⃣ **Adjust WIP Limit** (Emergency Only)\n Temporarily allow 3 active increments:\n \`\`\`bash\n bash plugins/specweave/hooks/lib/migrate-increment-work.sh adjust-wip 3\n \`\`\`\n ⚠️ 20% productivity cost, revert ASAP\n\n3️⃣ **Force-Close** (Quick Fix)\n Mark increment as complete (work lost):\n \`\`\`bash\n bash plugins/specweave/hooks/lib/migrate-increment-work.sh force-close <increment-id>\n \`\`\`\n ⚠️ Incomplete work NOT transferred!\n\n📝 **Traditional Options:**\n - /specweave:done <id> # Complete properly\n - /specweave:pause <id> # Pause for later\n - /specweave:abandon <id> # Abandon if obsolete\n\nℹ️ The discipline exists for a reason:\n ✓ Prevents scope creep\n ✓ Ensures completions are tracked\n ✓ Maintains living docs accuracy\n ✓ Keeps work focused"
|
|
122
|
-
}
|
|
123
|
-
EOF
|
|
124
|
-
exit 0
|
|
125
|
-
fi
|
|
126
|
-
fi
|
|
102
|
+
exit 0
|
|
127
103
|
fi
|
|
128
104
|
fi
|
|
129
105
|
|
|
130
106
|
# ==============================================================================
|
|
131
|
-
# PRE-FLIGHT SYNC CHECK
|
|
107
|
+
# PRE-FLIGHT SYNC CHECK (LIGHTWEIGHT - uses cached ACTIVE_INCREMENT)
|
|
132
108
|
# ==============================================================================
|
|
133
109
|
|
|
134
110
|
# Detect increment operations that need fresh data
|
|
135
111
|
if echo "$PROMPT" | grep -qE "/(specweave:)?(done|validate|progress|do)"; then
|
|
136
|
-
# Extract increment ID from prompt
|
|
112
|
+
# Extract increment ID from prompt OR use cached active
|
|
137
113
|
INCREMENT_ID=$(echo "$PROMPT" | grep -oE "[0-9]{4}[a-z0-9-]*" | head -1)
|
|
114
|
+
[[ -z "$INCREMENT_ID" ]] && INCREMENT_ID="$ACTIVE_INCREMENT"
|
|
138
115
|
|
|
139
|
-
# If
|
|
140
|
-
if [[ -z "$INCREMENT_ID" ]] && [[ -d ".specweave/increments" ]]; then
|
|
141
|
-
INCREMENT_ID=$(find .specweave/increments -mindepth 1 -maxdepth 1 -type d | while read increment_dir; do
|
|
142
|
-
metadata="$increment_dir/metadata.json"
|
|
143
|
-
if [[ -f "$metadata" ]]; then
|
|
144
|
-
status=$(node -e "
|
|
145
|
-
try {
|
|
146
|
-
const data = JSON.parse(require('fs').readFileSync('$metadata', 'utf-8'));
|
|
147
|
-
if (data.status === 'active') {
|
|
148
|
-
console.log('$(basename "$increment_dir")');
|
|
149
|
-
}
|
|
150
|
-
} catch (e) {}
|
|
151
|
-
" 2>/dev/null)
|
|
152
|
-
|
|
153
|
-
if [[ -n "$status" ]]; then
|
|
154
|
-
echo "$status"
|
|
155
|
-
break
|
|
156
|
-
fi
|
|
157
|
-
fi
|
|
158
|
-
done)
|
|
159
|
-
fi
|
|
160
|
-
|
|
161
|
-
# If we have an increment ID, check freshness
|
|
116
|
+
# If we have an increment ID, check freshness (pure bash - no node!)
|
|
162
117
|
if [[ -n "$INCREMENT_ID" ]]; then
|
|
163
|
-
INCREMENT_SPEC="
|
|
164
|
-
LIVING_DOCS_SPEC="
|
|
118
|
+
INCREMENT_SPEC="$SPECWEAVE_DIR/increments/$INCREMENT_ID/spec.md"
|
|
119
|
+
LIVING_DOCS_SPEC="$SPECWEAVE_DIR/docs/internal/specs/spec-$INCREMENT_ID.md"
|
|
165
120
|
|
|
166
|
-
# Check if increment spec exists
|
|
167
121
|
if [[ -f "$INCREMENT_SPEC" ]]; then
|
|
168
|
-
#
|
|
169
|
-
if [[ "$(
|
|
170
|
-
#
|
|
171
|
-
INCREMENT_MTIME=$(stat -f %m "$INCREMENT_SPEC" 2>/dev/null || echo 0)
|
|
172
|
-
LIVING_DOCS_MTIME=$(stat -f %m "$LIVING_DOCS_SPEC" 2>/dev/null || echo 0)
|
|
173
|
-
else
|
|
174
|
-
# Linux
|
|
175
|
-
INCREMENT_MTIME=$(stat -c %Y "$INCREMENT_SPEC" 2>/dev/null || echo 0)
|
|
176
|
-
LIVING_DOCS_MTIME=$(stat -c %Y "$LIVING_DOCS_SPEC" 2>/dev/null || echo 0)
|
|
177
|
-
fi
|
|
178
|
-
|
|
179
|
-
# Check if increment is newer than living docs (or living docs doesn't exist)
|
|
180
|
-
if [[ "$INCREMENT_MTIME" -gt "$LIVING_DOCS_MTIME" ]]; then
|
|
181
|
-
# Sync needed - run sync-living-docs
|
|
122
|
+
# Use find -newer for mtime comparison (single syscall!)
|
|
123
|
+
if [[ ! -f "$LIVING_DOCS_SPEC" ]] || [[ -n $(find "$INCREMENT_SPEC" -newer "$LIVING_DOCS_SPEC" 2>/dev/null) ]]; then
|
|
124
|
+
# Sync needed - run async (non-blocking!)
|
|
182
125
|
PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
183
126
|
SYNC_SCRIPT="$PLUGIN_ROOT/lib/hooks/sync-living-docs.js"
|
|
184
|
-
|
|
185
|
-
if [[ -f "$SYNC_SCRIPT" ]]; then
|
|
186
|
-
# Run sync (capture output but don't block on errors)
|
|
187
|
-
if node "$SYNC_SCRIPT" "$INCREMENT_ID" >/dev/null 2>&1; then
|
|
188
|
-
# Success - sync completed
|
|
189
|
-
:
|
|
190
|
-
else
|
|
191
|
-
# Sync failed - log but continue
|
|
192
|
-
echo "[WARNING] Pre-flight sync failed for $INCREMENT_ID" >&2
|
|
193
|
-
fi
|
|
194
|
-
fi
|
|
127
|
+
[[ -f "$SYNC_SCRIPT" ]] && node "$SYNC_SCRIPT" "$INCREMENT_ID" >/dev/null 2>&1 &
|
|
195
128
|
fi
|
|
196
129
|
fi
|
|
197
130
|
fi
|
|
198
131
|
fi
|
|
199
132
|
|
|
200
133
|
# ==============================================================================
|
|
201
|
-
# SPEC SYNC CHECK
|
|
134
|
+
# SPEC SYNC CHECK (LIGHTWEIGHT - only when really needed)
|
|
202
135
|
# ==============================================================================
|
|
203
|
-
|
|
204
|
-
#
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
} catch (e) {}
|
|
217
|
-
" 2>/dev/null)
|
|
218
|
-
|
|
219
|
-
if [[ -n "$status" ]]; then
|
|
220
|
-
echo "$status"
|
|
221
|
-
break
|
|
222
|
-
fi
|
|
223
|
-
fi
|
|
224
|
-
done)
|
|
225
|
-
|
|
226
|
-
if [[ -n "$ACTIVE_INCREMENT_FOR_SYNC" ]]; then
|
|
227
|
-
# Check if SpecSyncManager detects changes
|
|
228
|
-
if command -v node >/dev/null 2>&1 && [[ -f "dist/src/core/increment/spec-sync-manager.js" ]]; then
|
|
229
|
-
SYNC_CHECK=$(node -e "
|
|
230
|
-
try {
|
|
231
|
-
const { SpecSyncManager } = require('./dist/src/core/increment/spec-sync-manager.js');
|
|
232
|
-
const manager = new SpecSyncManager(process.cwd());
|
|
233
|
-
const detection = manager.detectSpecChange('$ACTIVE_INCREMENT_FOR_SYNC');
|
|
234
|
-
|
|
235
|
-
if (detection.specChanged) {
|
|
236
|
-
const message = manager.formatSyncMessage(detection);
|
|
237
|
-
console.log(JSON.stringify({ needsSync: true, message }));
|
|
238
|
-
} else {
|
|
239
|
-
console.log(JSON.stringify({ needsSync: false }));
|
|
240
|
-
}
|
|
241
|
-
} catch (e) {
|
|
242
|
-
console.log(JSON.stringify({ needsSync: false, error: e.message }));
|
|
243
|
-
}
|
|
244
|
-
" 2>/dev/null || echo '{"needsSync":false}')
|
|
245
|
-
|
|
246
|
-
NEEDS_SYNC=$(echo "$SYNC_CHECK" | node -e "
|
|
247
|
-
try {
|
|
248
|
-
const data = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
|
|
249
|
-
console.log(data.needsSync || false);
|
|
250
|
-
} catch (e) {
|
|
251
|
-
console.log(false);
|
|
252
|
-
}
|
|
253
|
-
")
|
|
254
|
-
|
|
255
|
-
if [[ "$NEEDS_SYNC" == "true" ]]; then
|
|
256
|
-
SYNC_MESSAGE=$(echo "$SYNC_CHECK" | node -e "
|
|
257
|
-
try {
|
|
258
|
-
const data = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
|
|
259
|
-
console.log(data.message || '');
|
|
260
|
-
} catch (e) {
|
|
261
|
-
console.log('');
|
|
262
|
-
}
|
|
263
|
-
")
|
|
264
|
-
|
|
265
|
-
# Show sync warning (don't block, just warn)
|
|
266
|
-
cat <<EOF
|
|
136
|
+
# Skip SpecSyncManager for most prompts - it's HEAVY!
|
|
137
|
+
# Only check on explicit sync-related commands
|
|
138
|
+
|
|
139
|
+
if [[ -n "$ACTIVE_INCREMENT" ]] && echo "$PROMPT" | grep -qE "/(specweave:)?(sync|done)"; then
|
|
140
|
+
# Simple mtime check: spec.md vs plan.md (pure bash!)
|
|
141
|
+
SPEC_FILE="$SPECWEAVE_DIR/increments/$ACTIVE_INCREMENT/spec.md"
|
|
142
|
+
PLAN_FILE="$SPECWEAVE_DIR/increments/$ACTIVE_INCREMENT/plan.md"
|
|
143
|
+
|
|
144
|
+
if [[ -f "$SPEC_FILE" ]] && [[ -f "$PLAN_FILE" ]]; then
|
|
145
|
+
# Check if spec is newer than plan (indicates spec changes need sync)
|
|
146
|
+
if [[ -n $(find "$SPEC_FILE" -newer "$PLAN_FILE" 2>/dev/null) ]]; then
|
|
147
|
+
cat <<EOF
|
|
267
148
|
{
|
|
268
149
|
"decision": "approve",
|
|
269
|
-
"systemMessage": "$
|
|
150
|
+
"systemMessage": "⚠️ Spec changes detected in $ACTIVE_INCREMENT\n\nspec.md has been modified after plan.md.\nConsider running /specweave:sync-docs to update living documentation."
|
|
270
151
|
}
|
|
271
152
|
EOF
|
|
272
|
-
|
|
273
|
-
fi
|
|
153
|
+
exit 0
|
|
274
154
|
fi
|
|
275
155
|
fi
|
|
276
156
|
fi
|
|
277
157
|
|
|
278
158
|
# ==============================================================================
|
|
279
|
-
# CONTEXT INJECTION
|
|
159
|
+
# CONTEXT INJECTION (uses cached ACTIVE_INCREMENT - no more find loops!)
|
|
280
160
|
# ==============================================================================
|
|
281
161
|
|
|
282
162
|
CONTEXT=""
|
|
283
163
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
")
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
break
|
|
301
|
-
fi
|
|
164
|
+
if [[ -n "$ACTIVE_INCREMENT" ]]; then
|
|
165
|
+
# Read from status-line.json cache (single source of truth)
|
|
166
|
+
CACHE_FILE="$SPECWEAVE_DIR/state/status-line.json"
|
|
167
|
+
|
|
168
|
+
if [[ -f "$CACHE_FILE" ]]; then
|
|
169
|
+
# Single jq call for all values (or pure bash fallback)
|
|
170
|
+
if command -v jq >/dev/null 2>&1; then
|
|
171
|
+
read -r TOTAL_TASKS COMPLETED_TASKS TOTAL_ACS COMPLETED_ACS < <(
|
|
172
|
+
jq -r '[.current.total // 0, .current.completed // 0, .current.acsTotal // 0, .current.acsCompleted // 0] | @tsv' "$CACHE_FILE" 2>/dev/null || echo "0 0 0 0"
|
|
173
|
+
)
|
|
174
|
+
else
|
|
175
|
+
# Pure grep fallback (no node!)
|
|
176
|
+
TOTAL_TASKS=$(grep -oP '"total"\s*:\s*\K[0-9]+' "$CACHE_FILE" 2>/dev/null | head -1 || echo "0")
|
|
177
|
+
COMPLETED_TASKS=$(grep -oP '"completed"\s*:\s*\K[0-9]+' "$CACHE_FILE" 2>/dev/null | head -1 || echo "0")
|
|
178
|
+
TOTAL_ACS=$(grep -oP '"acsTotal"\s*:\s*\K[0-9]+' "$CACHE_FILE" 2>/dev/null || echo "0")
|
|
179
|
+
COMPLETED_ACS=$(grep -oP '"acsCompleted"\s*:\s*\K[0-9]+' "$CACHE_FILE" 2>/dev/null || echo "0")
|
|
302
180
|
fi
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
TOTAL_ACS
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
# Ensure valid numbers
|
|
317
|
-
TOTAL_TASKS=${TOTAL_TASKS:-0}
|
|
318
|
-
COMPLETED_TASKS=${COMPLETED_TASKS:-0}
|
|
319
|
-
TOTAL_ACS=${TOTAL_ACS:-0}
|
|
320
|
-
COMPLETED_ACS=${COMPLETED_ACS:-0}
|
|
321
|
-
|
|
322
|
-
if [[ "$TOTAL_TASKS" -gt 0 ]] 2>/dev/null; then
|
|
323
|
-
PERCENTAGE=$(( COMPLETED_TASKS * 100 / TOTAL_TASKS ))
|
|
324
|
-
|
|
325
|
-
# Include AC count if available
|
|
326
|
-
if [[ "$TOTAL_ACS" -gt 0 ]] 2>/dev/null; then
|
|
327
|
-
AC_PERCENTAGE=$(( COMPLETED_ACS * 100 / TOTAL_ACS ))
|
|
328
|
-
CONTEXT="✓ Active: $ACTIVE_INCREMENT ($COMPLETED_TASKS/$TOTAL_TASKS tasks, $PERCENTAGE% | $COMPLETED_ACS/$TOTAL_ACS ACs, $AC_PERCENTAGE%)"
|
|
329
|
-
else
|
|
330
|
-
CONTEXT="✓ Active: $ACTIVE_INCREMENT ($COMPLETED_TASKS/$TOTAL_TASKS tasks, $PERCENTAGE%)"
|
|
331
|
-
fi
|
|
181
|
+
|
|
182
|
+
# Ensure valid numbers
|
|
183
|
+
TOTAL_TASKS=${TOTAL_TASKS:-0}
|
|
184
|
+
COMPLETED_TASKS=${COMPLETED_TASKS:-0}
|
|
185
|
+
TOTAL_ACS=${TOTAL_ACS:-0}
|
|
186
|
+
COMPLETED_ACS=${COMPLETED_ACS:-0}
|
|
187
|
+
|
|
188
|
+
if [[ "$TOTAL_TASKS" -gt 0 ]] 2>/dev/null; then
|
|
189
|
+
PERCENTAGE=$(( COMPLETED_TASKS * 100 / TOTAL_TASKS ))
|
|
190
|
+
|
|
191
|
+
if [[ "$TOTAL_ACS" -gt 0 ]] 2>/dev/null; then
|
|
192
|
+
AC_PERCENTAGE=$(( COMPLETED_ACS * 100 / TOTAL_ACS ))
|
|
193
|
+
CONTEXT="✓ Active: $ACTIVE_INCREMENT ($COMPLETED_TASKS/$TOTAL_TASKS tasks, $PERCENTAGE% | $COMPLETED_ACS/$TOTAL_ACS ACs, $AC_PERCENTAGE%)"
|
|
332
194
|
else
|
|
333
|
-
CONTEXT="✓ Active: $ACTIVE_INCREMENT"
|
|
195
|
+
CONTEXT="✓ Active: $ACTIVE_INCREMENT ($COMPLETED_TASKS/$TOTAL_TASKS tasks, $PERCENTAGE%)"
|
|
334
196
|
fi
|
|
335
197
|
else
|
|
336
|
-
# Fallback: No cache or no jq, show basic status
|
|
337
198
|
CONTEXT="✓ Active: $ACTIVE_INCREMENT"
|
|
338
199
|
fi
|
|
200
|
+
else
|
|
201
|
+
CONTEXT="✓ Active: $ACTIVE_INCREMENT"
|
|
339
202
|
fi
|
|
340
203
|
fi
|
|
341
204
|
|
|
@@ -355,25 +218,16 @@ if echo "$PROMPT" | grep -qiE "(add|create|implement|build|develop)" && ! echo "
|
|
|
355
218
|
fi
|
|
356
219
|
|
|
357
220
|
# ==============================================================================
|
|
358
|
-
# STATUS LINE REFRESH
|
|
221
|
+
# STATUS LINE REFRESH (v0.26.13 - CONDITIONAL + ASYNC)
|
|
359
222
|
# ==============================================================================
|
|
360
|
-
#
|
|
361
|
-
#
|
|
362
|
-
# Benefit: Catches ALL edge cases (manual edits, resume, direct changes)
|
|
363
|
-
#
|
|
364
|
-
# Why here? This hook runs on EVERY user prompt, ensuring status line
|
|
365
|
-
# is ALWAYS up-to-date before showing context to the user.
|
|
366
|
-
#
|
|
367
|
-
# Prevents desync scenarios:
|
|
368
|
-
# - Manual spec.md edits (status: planning → active)
|
|
369
|
-
# - /specweave:resume (status: paused → active)
|
|
370
|
-
# - Direct metadata changes (without hook triggers)
|
|
371
|
-
# - File system operations bypassing hooks
|
|
372
|
-
#
|
|
373
|
-
# Background execution: Runs async, doesn't block user prompt
|
|
223
|
+
# Only refresh when we have an active increment (skip for most prompts)
|
|
224
|
+
# Runs in background to avoid blocking user prompt
|
|
374
225
|
|
|
375
|
-
|
|
376
|
-
|
|
226
|
+
if [[ -n "$ACTIVE_INCREMENT" ]] && [[ -d "$SPECWEAVE_DIR" ]]; then
|
|
227
|
+
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
228
|
+
# Run async (non-blocking!) - update-status-line.sh has its own TTL/mtime guards
|
|
229
|
+
bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null &
|
|
230
|
+
fi
|
|
377
231
|
|
|
378
232
|
# ==============================================================================
|
|
379
233
|
# OUTPUT: Approve with context or no context
|