specweave 0.26.11 → 0.26.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) 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/plugins/specweave-jira/lib/setup-wizard.js +1 -1
  6. package/dist/plugins/specweave-jira/lib/setup-wizard.js.map +1 -1
  7. package/dist/src/cli/commands/import-docs.js +2 -2
  8. package/dist/src/cli/commands/import-docs.js.map +1 -1
  9. package/dist/src/cli/commands/init.js +8 -8
  10. package/dist/src/cli/commands/init.js.map +1 -1
  11. package/dist/src/cli/commands/install.js +2 -2
  12. package/dist/src/cli/commands/install.js.map +1 -1
  13. package/dist/src/cli/helpers/ado-area-path-mapper.js +3 -3
  14. package/dist/src/cli/helpers/ado-area-path-mapper.js.map +1 -1
  15. package/dist/src/cli/helpers/github/profile-manager.js +1 -1
  16. package/dist/src/cli/helpers/github/profile-manager.js.map +1 -1
  17. package/dist/src/cli/helpers/github-repo-selector.js +3 -3
  18. package/dist/src/cli/helpers/github-repo-selector.js.map +1 -1
  19. package/dist/src/cli/helpers/import-strategy-prompter.js +1 -1
  20. package/dist/src/cli/helpers/import-strategy-prompter.js.map +1 -1
  21. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +1 -1
  22. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
  23. package/dist/src/cli/helpers/issue-tracker/github.js +3 -3
  24. package/dist/src/cli/helpers/issue-tracker/github.js.map +1 -1
  25. package/dist/src/cli/helpers/issue-tracker/index.js +1 -1
  26. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  27. package/dist/src/cli/helpers/issue-tracker/jira.js +1 -1
  28. package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
  29. package/dist/src/cli/helpers/smart-filter.js +1 -1
  30. package/dist/src/cli/helpers/smart-filter.js.map +1 -1
  31. package/dist/src/core/increment/increment-archiver.d.ts +3 -0
  32. package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
  33. package/dist/src/core/increment/increment-archiver.js +35 -4
  34. package/dist/src/core/increment/increment-archiver.js.map +1 -1
  35. package/dist/src/core/living-docs/feature-archiver.d.ts +5 -0
  36. package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
  37. package/dist/src/core/living-docs/feature-archiver.js +66 -18
  38. package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
  39. package/dist/src/core/repo-structure/repo-bulk-discovery.js +2 -2
  40. package/dist/src/core/repo-structure/repo-bulk-discovery.js.map +1 -1
  41. package/dist/src/core/repo-structure/repo-structure-manager.js +10 -10
  42. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  43. package/dist/src/core/sync/bidirectional-engine.js +1 -1
  44. package/dist/src/core/sync/bidirectional-engine.js.map +1 -1
  45. package/dist/src/init/InitFlow.js +1 -1
  46. package/dist/src/init/InitFlow.js.map +1 -1
  47. package/dist/src/integrations/ado/area-path-mapper.js +1 -1
  48. package/dist/src/integrations/ado/area-path-mapper.js.map +1 -1
  49. package/dist/src/utils/external-resource-validator.js +4 -4
  50. package/dist/src/utils/external-resource-validator.js.map +1 -1
  51. package/package.json +1 -1
  52. package/plugins/PLUGINS-INDEX.md +120 -0
  53. package/plugins/specweave/commands/specweave-archive.md +10 -1
  54. package/plugins/specweave/commands/specweave-increment.md +1 -1
  55. package/plugins/specweave/commands/specweave-update-scope.md +2 -2
  56. package/plugins/specweave/hooks/hooks.json +10 -0
  57. package/plugins/specweave/hooks/lib/update-active-increment.sh +96 -0
  58. package/plugins/specweave/hooks/lib/update-status-line.sh +153 -189
  59. package/plugins/specweave/hooks/post-edit-write-consolidated.sh +6 -0
  60. package/plugins/specweave/hooks/post-metadata-change.sh +9 -0
  61. package/plugins/specweave/hooks/post-task-completion.sh +8 -0
  62. package/plugins/specweave/hooks/post-task-edit.sh +37 -0
  63. package/plugins/specweave/hooks/post-user-story-complete.sh +86 -35
  64. package/plugins/specweave/hooks/pre-command-deduplication.sh +43 -53
  65. package/plugins/specweave/hooks/pre-tool-use.sh +5 -0
  66. package/plugins/specweave/hooks/user-prompt-submit.sh +143 -289
  67. package/plugins/specweave/lib/hooks/us-completion-orchestrator.js +62 -1
  68. package/plugins/specweave/lib/hooks/us-completion-orchestrator.ts +106 -3
  69. package/plugins/specweave/skills/SKILLS-INDEX.md +69 -225
  70. package/plugins/specweave-ado/commands/specweave-ado-import-projects.md +1 -1
  71. package/plugins/specweave-ado/lib/project-selector.js +1 -1
  72. package/plugins/specweave-ado/lib/project-selector.ts +1 -1
  73. package/plugins/specweave-ado/skills/ado-resource-validator/SKILL.md +1 -1
  74. package/plugins/specweave-github/lib/completion-calculator.js +34 -16
  75. package/plugins/specweave-github/lib/completion-calculator.ts +54 -32
  76. package/plugins/specweave-github/lib/repo-selector.js +1 -1
  77. package/plugins/specweave-github/lib/repo-selector.ts +1 -1
  78. package/plugins/specweave-jira/lib/project-selector.js +1 -1
  79. package/plugins/specweave-jira/lib/project-selector.ts +1 -1
  80. package/plugins/specweave-jira/lib/setup-wizard.js +1 -1
  81. package/plugins/specweave-jira/lib/setup-wizard.ts +1 -1
  82. package/src/templates/AGENTS.md.template +301 -2452
  83. 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 (Enhanced with AC Metrics)
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
- # Logic:
9
- # 1. Scan all spec.md for status=active/planning (SOURCE OF TRUTH!)
10
- # 2. Take first (oldest) as current increment
11
- # 3. Count all active/planning as openCount
12
- # 4. Parse current increment's tasks.md for task progress
13
- # 5. Parse current increment's spec.md for AC progress
14
- # 6. Write to cache
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: 50-100ms (runs async, user doesn't wait)
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
- # Find project root
25
- find_project_root() {
26
- local dir="$PWD"
27
- while [[ "$dir" != "/" ]]; do
28
- if [[ -d "$dir/.specweave" ]]; then
29
- echo "$dir"
30
- return 0
31
- fi
32
- dir=$(dirname "$dir")
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
- echo "$PWD"
35
- }
36
-
37
- PROJECT_ROOT=$(find_project_root)
30
+ [[ ! -d "$PROJECT_ROOT/.specweave" ]] && PROJECT_ROOT="$PWD"
31
+ fi
38
32
 
39
33
  # ============================================================================
40
- # RECURSION PREVENTION (CRITICAL - v0.26.0 - FILE-BASED GUARD)
34
+ # ULTRA-FAST EXITS
41
35
  # ============================================================================
42
- # FIX: Don't update status line from within hook chain
43
- # WHY: Status line writes status-line.json which triggers post-edit-write hook
44
- # which tries to update status line AGAIN → infinite recursion!
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
- RECURSION_GUARD_FILE="$PROJECT_ROOT/.specweave/state/.hook-recursion-guard"
41
+ # No .specweave? Exit immediately
42
+ [[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
50
43
 
51
- if [[ -f "$RECURSION_GUARD_FILE" ]]; then
52
- # We're inside a hook chain - skip status line update to prevent recursion
53
- # This is NORMAL behavior (not an error!)
54
- exit 0
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
- CACHE_FILE="$PROJECT_ROOT/.specweave/state/status-line.json"
58
- INCREMENTS_DIR="$PROJECT_ROOT/.specweave/increments"
59
- TMP_FILE="$PROJECT_ROOT/.specweave/state/.status-line-tmp.txt"
60
-
61
- # Ensure state directory exists
62
- mkdir -p "$PROJECT_ROOT/.specweave/state"
63
-
64
- # Step 1: Find all open increments (active/planning)
65
- # Read from spec.md (source of truth), not metadata.json
66
- # ONLY accepts official IncrementStatus enum values
67
- # Write to temp file: "timestamp increment_id"
68
- > "$TMP_FILE"
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
- # Step 2: Count open increments
91
- OPEN_COUNT=$(wc -l < "$TMP_FILE" | tr -d ' ')
92
-
93
- if [[ $OPEN_COUNT -eq 0 ]]; then
94
- # No open increments
95
- jq -n '{
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
- # Step 3: Sort by timestamp (oldest first) and take first
105
- CURRENT_INCREMENT=$(sort "$TMP_FILE" | head -1 | awk '{print $2}')
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
- # Clean up temp file
108
- rm -f "$TMP_FILE"
96
+ while IFS= read -r spec_file; do
97
+ [[ -z "$spec_file" ]] && continue
109
98
 
110
- # Step 4: Parse current increment's tasks.md for progress
111
- TASKS_FILE="$INCREMENTS_DIR/$CURRENT_INCREMENT/tasks.md"
112
- TOTAL_TASKS=0
113
- COMPLETED_TASKS=0
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
- if [[ -f "$TASKS_FILE" ]]; then
117
- # Use TaskCounter CLI for accurate counting (fixes overcounting bug)
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
- if [[ -f "$COUNT_TASKS_CLI" ]]; then
121
- # Call CLI and parse JSON output
122
- TASK_COUNTS=$(node "$COUNT_TASKS_CLI" "$TASKS_FILE" 2>/dev/null || echo '{"total":0,"completed":0,"percentage":0}')
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
- fi
114
+ done < <(find "$INCREMENTS_DIR" -maxdepth 2 -name "spec.md" -not -path "*/_archive/*" 2>/dev/null)
145
115
 
146
- # Step 4b: Parse spec.md for AC (Acceptance Criteria) progress
147
- SPEC_FILE="$INCREMENTS_DIR/$CURRENT_INCREMENT/spec.md"
148
- TOTAL_ACS=0
149
- COMPLETED_ACS=0
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
- # Step 5: Extract increment ID and name
170
- # Format: XXXX-name where XXXX is 4-digit prefix (brackets added by manager)
171
- INCREMENT_ID=$(echo "$CURRENT_INCREMENT" | grep -oE '^[0-9]{4}')
172
- INCREMENT_NAME_ONLY=$(echo "$CURRENT_INCREMENT" | sed 's/^[0-9]\{4\}-//')
173
- INCREMENT_NAME="$INCREMENT_ID-$INCREMENT_NAME_ONLY"
174
-
175
- # Step 6: Write cache with atomic validation (v0.24.4 - prevents corruption)
176
- TMP_CACHE_FILE="$PROJECT_ROOT/.specweave/state/.status-line-tmp.json"
177
-
178
- # Validate all numeric inputs before jq (prevents jq failures)
179
- [[ "$TOTAL_TASKS" =~ ^[0-9]+$ ]] || TOTAL_TASKS=0
180
- [[ "$COMPLETED_TASKS" =~ ^[0-9]+$ ]] || COMPLETED_TASKS=0
181
- [[ "$PERCENTAGE" =~ ^[0-9]+$ ]] || PERCENTAGE=0
182
- [[ "$TOTAL_ACS" =~ ^[0-9]+$ ]] || TOTAL_ACS=0
183
- [[ "$COMPLETED_ACS" =~ ^[0-9]+$ ]] || COMPLETED_ACS=0
184
- [[ "$OPEN_ACS" =~ ^[0-9]+$ ]] || OPEN_ACS=0
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
@@ -45,17 +45,50 @@ echo "🎉 Post-User-Story-Complete Hook"
45
45
  echo " Spec: $SPEC_ID"
46
46
  echo " User Story: $USER_STORY_ID"
47
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"
48
+ # Find user story file in living docs
49
+ # Input: SPEC_ID = increment ID (e.g., "0059-context-optimization-crash-prevention")
50
+ # USER_STORY_ID = US ID (e.g., "US-001")
51
+ # Search in: .specweave/docs/internal/specs/**/us-*.md
52
+
53
+ # STEP 1: Get feature ID from increment spec.md
54
+ INCREMENT_SPEC=".specweave/increments/$SPEC_ID/spec.md"
55
+ FEATURE_ID=""
56
+
57
+ if [[ -f "$INCREMENT_SPEC" ]]; then
58
+ FEATURE_ID=$(head -20 "$INCREMENT_SPEC" | grep "^feature_id:" | head -1 | sed 's/feature_id: *//; s/ *$//' || echo "")
59
+ fi
60
+
61
+ echo " 🔍 Looking for $USER_STORY_ID in feature $FEATURE_ID"
62
+
63
+ # STEP 2: Find user story file by ID AND feature
64
+ # Prioritize files in the correct feature folder
65
+ US_FILE=""
66
+ while IFS= read -r file; do
67
+ # Check if file has matching user story ID in frontmatter
68
+ if head -20 "$file" 2>/dev/null | grep -q "^id: $USER_STORY_ID"; then
69
+ # If feature ID is known, verify the file is in the correct feature folder
70
+ if [[ -n "$FEATURE_ID" ]]; then
71
+ FILE_FEATURE=$(head -20 "$file" 2>/dev/null | grep "^feature:" | head -1 | sed 's/feature: *//; s/ *$//' || echo "")
72
+ if [[ "$FILE_FEATURE" == "$FEATURE_ID" ]]; then
73
+ US_FILE="$file"
74
+ break
75
+ fi
76
+ else
77
+ # No feature filter - take first match
78
+ US_FILE="$file"
79
+ break
80
+ fi
81
+ fi
82
+ done < <(find .specweave/docs/internal/specs -name "us-*.md" -type f 2>/dev/null)
83
+
84
+ if [[ -z "$US_FILE" ]]; then
85
+ echo "❌ Error: User story file not found for $USER_STORY_ID (feature: $FEATURE_ID) in increment $SPEC_ID"
56
86
  exit 1
57
87
  fi
58
88
 
89
+ echo " 📄 Found user story file: $US_FILE"
90
+ SPEC_FILE="$US_FILE"
91
+
59
92
  # Load config to check if auto-sync is enabled
60
93
  CONFIG_FILE=".specweave/config.json"
61
94
  if [[ ! -f "$CONFIG_FILE" ]]; then
@@ -71,41 +104,48 @@ if [[ "$AUTO_SYNC" != "true" ]]; then
71
104
  exit 0
72
105
  fi
73
106
 
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 "")
107
+ # Parse user story frontmatter to detect external links
108
+ # User story files have format:
109
+ # external:
110
+ # github:
111
+ # issue: 745
112
+ # url: https://github.com/...
113
+
114
+ # Extract GitHub issue number from frontmatter
115
+ GITHUB_ISSUE=$(head -20 "$SPEC_FILE" | grep -A 5 "github:" | grep "issue:" | head -1 | sed 's/.*issue: *//; s/ *$//' || echo "")
77
116
 
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 "")
117
+ # Extract Jira issue from frontmatter
118
+ JIRA_ISSUE=$(head -20 "$SPEC_FILE" | grep -A 5 "jira:" | grep "issue:" | head -1 | sed 's/.*issue: *//; s/ *$//' || echo "")
80
119
 
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 "")
120
+ # Extract ADO work item from frontmatter
121
+ ADO_ITEM=$(head -20 "$SPEC_FILE" | grep -A 5 "ado:" | grep "item:" | head -1 | sed 's/.*item: *//; s/ *$//' || echo "")
83
122
 
84
123
  # Determine which provider to sync
85
124
  PROVIDER=""
86
- if [[ -n "$GITHUB_LINK" ]]; then
125
+ EXTERNAL_ID=""
126
+ if [[ -n "$GITHUB_ISSUE" ]]; then
87
127
  PROVIDER="github"
88
- EXTERNAL_ID="$GITHUB_LINK"
89
- elif [[ -n "$JIRA_LINK" ]]; then
128
+ EXTERNAL_ID="$GITHUB_ISSUE"
129
+ elif [[ -n "$JIRA_ISSUE" ]]; then
90
130
  PROVIDER="jira"
91
- EXTERNAL_ID="$JIRA_LINK"
92
- elif [[ -n "$ADO_LINK" ]]; then
131
+ EXTERNAL_ID="$JIRA_ISSUE"
132
+ elif [[ -n "$ADO_ITEM" ]]; then
93
133
  PROVIDER="ado"
94
- EXTERNAL_ID="$ADO_LINK"
134
+ EXTERNAL_ID="$ADO_ITEM"
95
135
  fi
96
136
 
97
137
  # No external link found - skip sync
98
138
  if [[ -z "$PROVIDER" ]]; then
99
- echo " â„šī¸ Spec not linked to external tool, skipping sync"
139
+ echo " â„šī¸ User story not linked to external tool, skipping sync"
100
140
  exit 0
101
141
  fi
102
142
 
103
- echo " 🔗 Detected external link: $PROVIDER"
143
+ echo " 🔗 Detected external link: $PROVIDER (ID: $EXTERNAL_ID)"
104
144
 
105
145
  # Update external tool based on provider
106
146
  case "$PROVIDER" in
107
147
  github)
108
- echo " 🔄 Updating GitHub Issue for $USER_STORY_ID..."
148
+ echo " 🔄 Updating GitHub Issue #$EXTERNAL_ID for $USER_STORY_ID..."
109
149
 
110
150
  # Check if GitHub CLI is available
111
151
  if ! command -v gh &> /dev/null; then
@@ -113,26 +153,37 @@ case "$PROVIDER" in
113
153
  exit 0
114
154
  fi
115
155
 
116
- # Find GitHub Issue for this user story
117
- # Search for issue with title pattern "[USER_STORY_ID]"
156
+ # Use issue number from frontmatter (EXTERNAL_ID = issue number)
157
+ ISSUE_NUMBER="$EXTERNAL_ID"
118
158
  REPO=$(git remote get-url origin | sed -E 's/.*github\.com[:/]([^/]+\/[^/]+)(\.git)?$/\1/')
119
159
 
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 "")
160
+ if [[ -z "$ISSUE_NUMBER" || "$ISSUE_NUMBER" == "null" ]]; then
161
+ echo " âš ī¸ No GitHub Issue number found in user story metadata"
162
+ exit 0
163
+ fi
164
+
165
+ echo " 📝 Using GitHub Issue #$ISSUE_NUMBER from metadata"
122
166
 
123
- if [[ -z "$ISSUE_NUMBER" ]]; then
124
- echo " âš ī¸ GitHub Issue not found for $USER_STORY_ID"
167
+ # Check if issue is already closed
168
+ ISSUE_STATE=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json state --jq '.state' 2>/dev/null || echo "")
169
+ if [[ "$ISSUE_STATE" == "CLOSED" ]]; then
170
+ echo " ✅ GitHub Issue #$ISSUE_NUMBER already closed"
125
171
  exit 0
126
172
  fi
127
173
 
128
- echo " 📝 Found GitHub Issue #$ISSUE_NUMBER"
174
+ # Close issue with completion comment
175
+ gh issue close "$ISSUE_NUMBER" --repo "$REPO" --comment "✅ **User Story Verified Complete**
176
+
177
+ 📊 **Completion Status**:
178
+ - ✅ All Acceptance Criteria satisfied
179
+ - ✅ All implementation tasks complete
129
180
 
130
- # Close issue
131
- gh issue close "$ISSUE_NUMBER" --repo "$REPO" --comment "✅ User story completed
181
+ **User Story**: $USER_STORY_ID
182
+ **Increment**: $SPEC_ID
132
183
 
133
- 🤖 Auto-closed by SpecWeave hook
184
+ 🤖 Auto-closed by SpecWeave US Completion Hook
134
185
  Completed at: $(date -u +%Y-%m-%dT%H:%M:%SZ)" 2>/dev/null || {
135
- echo " âš ī¸ Failed to close issue"
186
+ echo " âš ī¸ Failed to close issue (may already be closed)"
136
187
  exit 0
137
188
  }
138
189