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.
Files changed (52) hide show
  1. package/dist/plugins/specweave-github/lib/completion-calculator.d.ts +4 -1
  2. package/dist/plugins/specweave-github/lib/completion-calculator.d.ts.map +1 -1
  3. package/dist/plugins/specweave-github/lib/completion-calculator.js +49 -29
  4. package/dist/plugins/specweave-github/lib/completion-calculator.js.map +1 -1
  5. package/dist/src/core/increment/increment-archiver.d.ts +3 -0
  6. package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
  7. package/dist/src/core/increment/increment-archiver.js +35 -4
  8. package/dist/src/core/increment/increment-archiver.js.map +1 -1
  9. package/dist/src/core/living-docs/feature-archiver.d.ts +5 -0
  10. package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
  11. package/dist/src/core/living-docs/feature-archiver.js +66 -18
  12. package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
  13. package/package.json +1 -1
  14. package/plugins/specweave/commands/specweave-archive.md +10 -1
  15. package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
  16. package/plugins/specweave/hooks/hooks.json +10 -0
  17. package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
  18. package/plugins/specweave/hooks/lib/update-active-increment.sh +96 -0
  19. package/plugins/specweave/hooks/lib/update-status-line.sh +153 -189
  20. package/plugins/specweave/hooks/post-edit-write-consolidated.sh +6 -0
  21. package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
  22. package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
  23. package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
  24. package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
  25. package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
  26. package/plugins/specweave/hooks/post-metadata-change.sh +9 -0
  27. package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
  28. package/plugins/specweave/hooks/post-task-completion.sh +8 -0
  29. package/plugins/specweave/hooks/post-task-edit.sh +37 -0
  30. package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
  31. package/plugins/specweave/hooks/pre-command-deduplication.sh +43 -53
  32. package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
  33. package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
  34. package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
  35. package/plugins/specweave/hooks/pre-tool-use.sh +5 -0
  36. package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
  37. package/plugins/specweave/hooks/user-prompt-submit.sh +143 -289
  38. package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
  39. package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
  40. package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
  41. package/plugins/specweave-ado/lib/ado-multi-project-sync.js +1 -0
  42. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
  43. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1098 -0
  44. package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
  45. package/plugins/specweave-github/lib/completion-calculator.js +34 -16
  46. package/plugins/specweave-github/lib/completion-calculator.ts +54 -32
  47. package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
  48. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +3 -3
  49. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1008 -0
  50. package/plugins/specweave-release/hooks/post-task-completion.sh.backup +110 -0
  51. package/src/templates/AGENTS.md.template +301 -2452
  52. 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 # EMERGENCY FIX: Changed from set -euo pipefail to prevent Claude Code crashes
16
+ set +e
8
17
 
9
- # Read input JSON from stdin
18
+ # ==============================================================================
19
+ # ULTRA-FAST EARLY EXIT (before ANY processing)
20
+ # ==============================================================================
10
21
  INPUT=$(cat)
11
22
 
12
- # Extract prompt from JSON
13
- PROMPT=$(echo "$INPUT" | node -e "
14
- const input = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
15
- console.log(input.prompt || '');
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
- # Check increment discipline using check-discipline CLI command
24
- # This enforces WIP limits (max 1 active, hard cap 2)
25
- SPECWEAVE_DIR=".specweave"
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
- exit 0
60
- fi
91
+ exit 0
92
+ fi
61
93
 
62
- # Soft warning: 1 active (recommended limit)
63
- if [[ "$ACTIVE_COUNT" -ge 1 ]]; then
64
- # Get list of active increments for warning
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
- exit 0
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: Ensure living docs are fresh before operations
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 (if provided)
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 no ID in prompt, try to find active increment
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=".specweave/increments/$INCREMENT_ID/spec.md"
164
- LIVING_DOCS_SPEC=".specweave/docs/internal/specs/spec-$INCREMENT_ID.md"
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
- # Get modification times
169
- if [[ "$(uname)" == "Darwin" ]]; then
170
- # macOS
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: Detect spec.md changes and warn about sync needed
134
+ # SPEC SYNC CHECK (LIGHTWEIGHT - only when really needed)
202
135
  # ==============================================================================
203
-
204
- # Check if spec.md was modified after plan.md (requires sync)
205
- if [[ -d ".specweave/increments" ]]; then
206
- # Find active increment
207
- ACTIVE_INCREMENT_FOR_SYNC=$(find .specweave/increments -mindepth 1 -maxdepth 1 -type d | while read increment_dir; do
208
- metadata="$increment_dir/metadata.json"
209
- if [[ -f "$metadata" ]]; then
210
- status=$(node -e "
211
- try {
212
- const data = JSON.parse(require('fs').readFileSync('$metadata', 'utf-8'));
213
- if (data.status === 'active') {
214
- console.log('$(basename "$increment_dir")');
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": "$SYNC_MESSAGE"
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
- exit 0
273
- fi
153
+ exit 0
274
154
  fi
275
155
  fi
276
156
  fi
277
157
 
278
158
  # ==============================================================================
279
- # CONTEXT INJECTION: Add current increment status
159
+ # CONTEXT INJECTION (uses cached ACTIVE_INCREMENT - no more find loops!)
280
160
  # ==============================================================================
281
161
 
282
162
  CONTEXT=""
283
163
 
284
- # Find active increment
285
- if [[ -d ".specweave/increments" ]]; then
286
- ACTIVE_INCREMENT=$(find .specweave/increments -mindepth 1 -maxdepth 1 -type d | while read increment_dir; do
287
- metadata="$increment_dir/metadata.json"
288
- if [[ -f "$metadata" ]]; then
289
- status=$(node -e "
290
- try {
291
- const data = JSON.parse(require('fs').readFileSync('$metadata', 'utf-8'));
292
- if (data.status === 'active') {
293
- console.log('$(basename "$increment_dir")');
294
- }
295
- } catch (e) {}
296
- ")
297
-
298
- if [[ -n "$status" ]]; then
299
- echo "$status"
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
- done)
304
-
305
- if [[ -n "$ACTIVE_INCREMENT" ]]; then
306
- # Read from status-line.json cache (single source of truth)
307
- CACHE_FILE=".specweave/state/status-line.json"
308
-
309
- if [[ -f "$CACHE_FILE" ]] && command -v jq >/dev/null 2>&1; then
310
- # Parse cache file for accurate counts
311
- TOTAL_TASKS=$(jq -r '.current.total // 0' "$CACHE_FILE" 2>/dev/null || echo "0")
312
- COMPLETED_TASKS=$(jq -r '.current.completed // 0' "$CACHE_FILE" 2>/dev/null || echo "0")
313
- TOTAL_ACS=$(jq -r '.current.acsTotal // 0' "$CACHE_FILE" 2>/dev/null || echo "0")
314
- COMPLETED_ACS=$(jq -r '.current.acsCompleted // 0' "$CACHE_FILE" 2>/dev/null || echo "0")
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: Ensure cache is fresh before showing context
221
+ # STATUS LINE REFRESH (v0.26.13 - CONDITIONAL + ASYNC)
359
222
  # ==============================================================================
360
- # Performance: ~50-100ms (acceptable for UX)
361
- # Frequency: Every user prompt (high coverage)
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
- HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
376
- bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
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