specweave 0.24.11 → 0.24.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 (55) hide show
  1. package/CLAUDE.md +158 -10
  2. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +28 -0
  3. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  4. package/dist/plugins/specweave-github/lib/github-client-v2.js +47 -0
  5. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  6. package/dist/src/cli/commands/init.d.ts.map +1 -1
  7. package/dist/src/cli/commands/init.js +15 -7
  8. package/dist/src/cli/commands/init.js.map +1 -1
  9. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
  10. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +13 -1
  11. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
  12. package/dist/src/cli/helpers/issue-tracker/types.d.ts +1 -1
  13. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  14. package/dist/src/core/config/types.d.ts +46 -12
  15. package/dist/src/core/config/types.d.ts.map +1 -1
  16. package/dist/src/core/config/types.js +0 -5
  17. package/dist/src/core/config/types.js.map +1 -1
  18. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts.map +1 -1
  19. package/dist/src/core/repo-structure/repo-bulk-discovery.js +20 -5
  20. package/dist/src/core/repo-structure/repo-bulk-discovery.js.map +1 -1
  21. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  22. package/dist/src/core/repo-structure/repo-structure-manager.js +29 -14
  23. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  24. package/dist/src/sync/format-preservation-sync.d.ts.map +1 -1
  25. package/dist/src/sync/format-preservation-sync.js +23 -5
  26. package/dist/src/sync/format-preservation-sync.js.map +1 -1
  27. package/dist/src/sync/frontmatter-updater.d.ts +57 -0
  28. package/dist/src/sync/frontmatter-updater.d.ts.map +1 -0
  29. package/dist/src/sync/frontmatter-updater.js +147 -0
  30. package/dist/src/sync/frontmatter-updater.js.map +1 -0
  31. package/dist/src/sync/sync-coordinator.d.ts +22 -1
  32. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  33. package/dist/src/sync/sync-coordinator.js +268 -21
  34. package/dist/src/sync/sync-coordinator.js.map +1 -1
  35. package/dist/src/types/living-docs-us-file.d.ts +18 -0
  36. package/dist/src/types/living-docs-us-file.d.ts.map +1 -1
  37. package/dist/src/types/living-docs-us-file.js.map +1 -1
  38. package/package.json +1 -1
  39. package/plugins/specweave/.claude-plugin/plugin.json +17 -11
  40. package/plugins/specweave/agents/architect/AGENT.md +115 -45
  41. package/plugins/specweave/agents/test-aware-planner/AGENT.md +76 -6
  42. package/plugins/specweave/hooks/lib/update-status-line.sh +19 -0
  43. package/plugins/specweave/hooks/post-edit-write-consolidated.sh +335 -0
  44. package/plugins/specweave/hooks/post-metadata-change.sh +40 -6
  45. package/plugins/specweave/hooks/post-task-completion.sh +93 -9
  46. package/plugins/specweave/hooks/pre-edit-spec.sh +0 -11
  47. package/plugins/specweave/hooks/pre-edit-write-consolidated.sh +188 -0
  48. package/plugins/specweave/hooks/pre-task-completion.sh +11 -0
  49. package/plugins/specweave/hooks/pre-write-spec.sh +0 -11
  50. package/plugins/specweave/lib/hooks/consolidated-sync.js +61 -3
  51. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +294 -0
  52. package/plugins/specweave-github/lib/github-client-v2.js +46 -0
  53. package/plugins/specweave-github/lib/github-client-v2.ts +65 -0
  54. package/plugins/specweave-release/commands/specweave-release-npm.md +130 -2
  55. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +60 -0
@@ -0,0 +1,188 @@
1
+ #!/bin/bash
2
+ #
3
+ # Pre-Edit/Write Consolidated Hook: Capture File Path BEFORE Edit/Write Executes
4
+ #
5
+ # Purpose: Unified hook for both Edit and Write tools
6
+ # Strategy: Detect file path, signal post-hook if it's a spec/tasks file
7
+ #
8
+ # CONSOLIDATION (v0.25.0):
9
+ # - Replaces pre-edit-spec.sh and pre-write-spec.sh (identical code)
10
+ # - Reduces hook overhead by 50% (2 pre-hooks → 1)
11
+ # - Single point of maintenance
12
+ #
13
+ # TIER 2 COORDINATION:
14
+ # 1. Extract file_path from TOOL_USE_ARGS (reliable in PreToolUse)
15
+ # 2. If it's spec.md/tasks.md in increments folder, signal PostToolUse hook
16
+ # 3. Write file path to .pending-status-update for PostToolUse to consume
17
+ #
18
+ # Architecture:
19
+ # PreToolUse:Edit/Write → pre-edit-write-consolidated.sh (this file)
20
+ # ↓ writes to
21
+ # .specweave/state/.pending-status-update
22
+ # ↓ read by
23
+ # PostToolUse:Edit/Write → post-edit-write-consolidated.sh
24
+ #
25
+ # Version: v0.25.0 (HOOK CONSOLIDATION)
26
+ # Date: 2025-11-23
27
+ #
28
+ # EMERGENCY FIXES (v0.24.3):
29
+ # - Kill switch: Set SPECWEAVE_DISABLE_HOOKS=1 to disable ALL hooks
30
+ # - Circuit breaker: Auto-disable after 3 consecutive failures
31
+ # - File locking: Prevent concurrent executions
32
+ # - Complete error isolation: Never let errors reach Claude Code
33
+
34
+ # EMERGENCY FIX: Remove set -e - it causes Claude Code crashes!
35
+ set +e
36
+
37
+ # Find project root (must be BEFORE recursion guard to get PROJECT_ROOT)
38
+ find_project_root() {
39
+ local dir="$PWD"
40
+ while [[ "$dir" != "/" ]]; do
41
+ if [[ -d "$dir/.specweave" ]]; then
42
+ echo "$dir"
43
+ return 0
44
+ fi
45
+ dir=$(dirname "$dir")
46
+ done
47
+ echo "$PWD"
48
+ }
49
+
50
+ PROJECT_ROOT=$(find_project_root)
51
+ STATE_DIR="$PROJECT_ROOT/.specweave/state"
52
+ LOGS_DIR="$PROJECT_ROOT/.specweave/logs"
53
+ DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
54
+ PENDING_FILE="$STATE_DIR/.pending-status-update"
55
+ METRICS_FILE="$STATE_DIR/hook-metrics.jsonl"
56
+
57
+ # ============================================================================
58
+ # RECURSION PREVENTION (CRITICAL - v0.26.0 - FILE-BASED GUARD)
59
+ # ============================================================================
60
+ # NEW SOLUTION (v0.26.0): File-based recursion guard
61
+ # - Guard file exists = already inside hook chain
62
+ # - Works across ALL processes (not just current shell)
63
+ #
64
+ # See: ADR-0073 (Hook Recursion Prevention Strategy)
65
+
66
+ RECURSION_GUARD_FILE="$PROJECT_ROOT/.specweave/state/.hook-recursion-guard"
67
+
68
+ if [[ -f "$RECURSION_GUARD_FILE" ]]; then
69
+ # Silent exit - we're already inside a hook chain
70
+ exit 0
71
+ fi
72
+
73
+ # EMERGENCY KILL SWITCH: Disable all hooks if env variable set
74
+ if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
75
+ exit 0
76
+ fi
77
+
78
+ # Ensure directories exist
79
+ mkdir -p "$STATE_DIR" "$LOGS_DIR" 2>/dev/null || true
80
+
81
+ # ============================================================================
82
+ # TIER 2: Extract File Path from Tool Arguments
83
+ # ============================================================================
84
+ # PreToolUse should have access to tool arguments BEFORE execution
85
+ # Try multiple methods to extract file_path
86
+
87
+ FILE_PATH=""
88
+
89
+ # Method 1: TOOL_USE_ARGS environment variable (primary for PreToolUse)
90
+ if [[ -n "${TOOL_USE_ARGS:-}" ]]; then
91
+ # Try to parse JSON with jq if available
92
+ if command -v jq &> /dev/null; then
93
+ FILE_PATH=$(echo "$TOOL_USE_ARGS" | jq -r '.file_path // empty' 2>/dev/null || echo "")
94
+ fi
95
+
96
+ # Fallback: Regex extraction if jq not available or failed
97
+ if [[ -z "$FILE_PATH" ]]; then
98
+ FILE_PATH=$(echo "$TOOL_USE_ARGS" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "")
99
+ fi
100
+ fi
101
+
102
+ # Method 2: TOOL_USE_CONTENT (fallback)
103
+ if [[ -z "$FILE_PATH" ]] && [[ -n "${TOOL_USE_CONTENT:-}" ]]; then
104
+ FILE_PATH="$TOOL_USE_CONTENT"
105
+ fi
106
+
107
+ # Method 3: Parse from stdin (last resort - experimental)
108
+ if [[ -z "$FILE_PATH" ]] && [[ ! -t 0 ]]; then
109
+ # Read stdin and try to extract file_path
110
+ STDIN_DATA=$(cat 2>/dev/null || echo "")
111
+ if [[ -n "$STDIN_DATA" ]] && command -v jq &> /dev/null; then
112
+ FILE_PATH=$(echo "$STDIN_DATA" | jq -r '.file_path // empty' 2>/dev/null || echo "")
113
+ fi
114
+ fi
115
+
116
+ # ============================================================================
117
+ # TIER 2: Signal Detection and Validation
118
+ # ============================================================================
119
+
120
+ # Log what we detected (for debugging PreToolUse effectiveness)
121
+ if [[ -n "$FILE_PATH" ]]; then
122
+ echo "[$(date)] pre-edit-write: Detected file_path: $FILE_PATH" >> "$DEBUG_LOG" 2>/dev/null || true
123
+ else
124
+ echo "[$(date)] pre-edit-write: No file_path detected (will fall back to Tier 1)" >> "$DEBUG_LOG" 2>/dev/null || true
125
+ exit 0 # Let PostToolUse handle it with mtime fallback
126
+ fi
127
+
128
+ # ============================================================================
129
+ # EARLY EXIT: Only process .specweave/ files (v0.24.4 Performance Fix)
130
+ # ============================================================================
131
+ # 90% of Edit/Write operations are on non-SpecWeave files (src/, tests/, node_modules/)
132
+ # Exit immediately for non-.specweave/ files to reduce hook overhead by 90%
133
+
134
+ # Handle both absolute and relative paths: /.specweave/ or .specweave/
135
+ if [[ "$FILE_PATH" != *"/.specweave/"* ]] && [[ "$FILE_PATH" != ".specweave/"* ]]; then
136
+ echo "[$(date)] pre-edit-write: Not a .specweave/ file, skipping (performance optimization)" >> "$DEBUG_LOG" 2>/dev/null || true
137
+ exit 0
138
+ fi
139
+
140
+ # Check if this is a spec.md or tasks.md file in increments folder
141
+ IS_SPEC_FILE=false
142
+ if [[ "$FILE_PATH" == *"/spec.md" ]] || [[ "$FILE_PATH" == *"/tasks.md" ]]; then
143
+ # Handle both absolute (/.specweave/increments/) and relative (.specweave/increments/) paths
144
+ if [[ "$FILE_PATH" == *"/.specweave/increments/"* ]] || [[ "$FILE_PATH" == ".specweave/increments/"* ]]; then
145
+ # Exclude archived increments
146
+ if [[ "$FILE_PATH" != *"/_archive/"* ]]; then
147
+ IS_SPEC_FILE=true
148
+ fi
149
+ fi
150
+ fi
151
+
152
+ # If not a spec/tasks file, exit silently (no signal to PostToolUse)
153
+ if [[ "$IS_SPEC_FILE" != "true" ]]; then
154
+ echo "[$(date)] pre-edit-write: Not a spec/tasks file - no signal" >> "$DEBUG_LOG" 2>/dev/null || true
155
+ exit 0
156
+ fi
157
+
158
+ # ============================================================================
159
+ # TIER 2: Write Signal for PostToolUse Hook
160
+ # ============================================================================
161
+
162
+ # Write file path to pending file for PostToolUse to consume
163
+ echo "$FILE_PATH" > "$PENDING_FILE" 2>/dev/null || true
164
+
165
+ echo "[$(date)] pre-edit-write: Signaled PostToolUse for: $FILE_PATH" >> "$DEBUG_LOG" 2>/dev/null || true
166
+
167
+ # ============================================================================
168
+ # TIER 2: Metrics Collection
169
+ # ============================================================================
170
+
171
+ # Record metrics (JSONL format - one JSON object per line)
172
+ TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
173
+ METRIC_ENTRY="{\"timestamp\":\"$TIMESTAMP\",\"hook\":\"pre-edit-write\",\"event\":\"file_detected\",\"file_path\":\"$FILE_PATH\",\"method\":\"TOOL_USE_ARGS\"}"
174
+
175
+ # Append to metrics file (JSONL)
176
+ echo "$METRIC_ENTRY" >> "$METRICS_FILE" 2>/dev/null || true
177
+
178
+ # Log rotation for metrics (keep last 1000 entries)
179
+ if [[ -f "$METRICS_FILE" ]]; then
180
+ LINE_COUNT=$(wc -l < "$METRICS_FILE" 2>/dev/null || echo 0)
181
+ if (( LINE_COUNT > 1000 )); then
182
+ tail -1000 "$METRICS_FILE" > "$METRICS_FILE.tmp" 2>/dev/null || true
183
+ mv "$METRICS_FILE.tmp" "$METRICS_FILE" 2>/dev/null || true
184
+ fi
185
+ fi
186
+
187
+ # ALWAYS exit 0 - NEVER let hook errors crash Claude Code
188
+ exit 0
@@ -22,6 +22,17 @@
22
22
 
23
23
  set +e # EMERGENCY FIX: Prevents Claude Code crashes
24
24
 
25
+ # ============================================================================
26
+ # RECURSION PREVENTION (CRITICAL - v0.25.1)
27
+ # ============================================================================
28
+ # Skip if we're already inside a hook to prevent infinite recursion
29
+ if [[ "${SPECWEAVE_IN_HOOK:-0}" == "1" ]]; then
30
+ exit 0
31
+ fi
32
+
33
+ # Mark that we're now inside a hook
34
+ export SPECWEAVE_IN_HOOK=1
35
+
25
36
  # EMERGENCY KILL SWITCH
26
37
  if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
27
38
  exit 0
@@ -101,17 +101,6 @@ else
101
101
  exit 0 # Let PostToolUse handle it with mtime fallback
102
102
  fi
103
103
 
104
- # ============================================================================
105
- # EARLY EXIT: Only process .specweave/ files (v0.24.4 Performance Fix)
106
- # ============================================================================
107
- # 90% of Write operations are on non-SpecWeave files (src/, tests/, node_modules/)
108
- # Exit immediately for non-.specweave/ files to reduce hook overhead by 90%
109
-
110
- if [[ "$FILE_PATH" != *"/.specweave/"* ]]; then
111
- echo "[$(date)] pre-write-spec: Not a .specweave/ file, skipping (performance optimization)" >> "$DEBUG_LOG" 2>/dev/null || true
112
- exit 0
113
- fi
114
-
115
104
  # Check if this is a spec.md or tasks.md file in increments folder
116
105
  IS_SPEC_FILE=false
117
106
  if [[ "$FILE_PATH" == *"/spec.md" ]] || [[ "$FILE_PATH" == *"/tasks.md" ]]; then
@@ -42,6 +42,11 @@ import { translateLivingDocs } from './translate-living-docs.js';
42
42
  // Import for AC status (uses ACStatusManager directly)
43
43
  import { ACStatusManager } from '../vendor/core/increment/ac-status-manager.js';
44
44
 
45
+ // Import for GitHub sync (uses SyncCoordinator directly)
46
+ // NOTE: Import from project root dist (not relative path)
47
+ import { SyncCoordinator } from '../../../../dist/src/sync/sync-coordinator.js';
48
+ import { consoleLogger } from '../vendor/utils/logger.js';
49
+
45
50
  // ============================================================================
46
51
  // WRAPPER: UPDATE AC STATUS
47
52
  // ============================================================================
@@ -87,6 +92,36 @@ async function updateACStatus(incrementId) {
87
92
  }
88
93
  }
89
94
 
95
+ // ============================================================================
96
+ // WRAPPER: GITHUB SYNC
97
+ // ============================================================================
98
+ async function syncGitHub(incrementId) {
99
+ try {
100
+ console.log(`\n🔗 [5/5] Syncing to GitHub...`);
101
+
102
+ const projectRoot = process.cwd();
103
+
104
+ const coordinator = new SyncCoordinator({
105
+ projectRoot,
106
+ incrementId,
107
+ logger: consoleLogger
108
+ });
109
+
110
+ const result = await coordinator.syncIncrementCompletion();
111
+
112
+ if (result.success) {
113
+ console.log(`✅ GitHub sync completed (${result.userStoriesSynced} user stories synced)`);
114
+ } else {
115
+ console.warn(`⚠️ GitHub sync had errors (see logs)`);
116
+ }
117
+
118
+ return { success: result.success, result };
119
+ } catch (error) {
120
+ console.error('❌ Error syncing to GitHub:', error.message);
121
+ return { success: false, error: error.message };
122
+ }
123
+ }
124
+
90
125
  // ============================================================================
91
126
  // MAIN EXECUTION
92
127
  // ============================================================================
@@ -100,7 +135,7 @@ async function runConsolidatedSync(incrementId) {
100
135
 
101
136
  try {
102
137
  // OPERATION 1: Update tasks.md (uses imported function)
103
- console.log('\n🔄 [1/4] Updating tasks.md...');
138
+ console.log('\n🔄 [1/5] Updating tasks.md...');
104
139
  try {
105
140
  await updateTasksMd(incrementId);
106
141
  results.updateTasks = { success: true };
@@ -110,7 +145,7 @@ async function runConsolidatedSync(incrementId) {
110
145
  }
111
146
 
112
147
  // OPERATION 2: Sync living docs (uses imported function)
113
- console.log('\n📚 [2/4] Syncing living docs...');
148
+ console.log('\n📚 [2/5] Syncing living docs...');
114
149
  try {
115
150
  await syncLivingDocs(incrementId);
116
151
  results.syncDocs = { success: true };
@@ -128,7 +163,7 @@ async function runConsolidatedSync(incrementId) {
128
163
  }
129
164
 
130
165
  // OPERATION 4: Translate living docs (uses imported function)
131
- console.log('\n🌐 [4/4] Checking translation needs...');
166
+ console.log('\n🌐 [4/5] Checking translation needs...');
132
167
  try {
133
168
  await translateLivingDocs(incrementId);
134
169
  results.translate = { success: true };
@@ -137,6 +172,29 @@ async function runConsolidatedSync(incrementId) {
137
172
  results.translate = { success: false, error: error.message };
138
173
  }
139
174
 
175
+ // OPERATION 5: Sync to GitHub (NEW in v0.24.0+)
176
+ // FIX (v0.26.0): Skip GitHub sync in post-task-completion hook!
177
+ // WHY: GitHub sync should ONLY run on increment COMPLETION, not task completion
178
+ // This prevents 27 duplicate comments on every TodoWrite (see root cause analysis)
179
+ //
180
+ // GitHub sync is now ONLY triggered by:
181
+ // - post-increment-completion.sh (when status → "completed")
182
+ // - Manual sync via /specweave-github:sync command
183
+ //
184
+ // See: .specweave/increments/0051-*/reports/GITHUB-COMMENT-RECURSION-ROOT-CAUSE-2025-11-24.md
185
+ if (process.env.SKIP_GITHUB_SYNC === 'true') {
186
+ console.log('\n⏭️ [5/5] GitHub sync SKIPPED (called from post-task-completion hook)');
187
+ console.log(' GitHub sync will run automatically on increment completion.');
188
+ results.syncGitHub = { success: true, skipped: true };
189
+ } else {
190
+ try {
191
+ results.syncGitHub = await syncGitHub(incrementId);
192
+ } catch (error) {
193
+ console.error('❌ Error syncing to GitHub:', error.message);
194
+ results.syncGitHub = { success: false, error: error.message };
195
+ }
196
+ }
197
+
140
198
  const duration = Date.now() - startTime;
141
199
 
142
200
  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');