specweave 1.0.30 → 1.0.32

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 (79) hide show
  1. package/CLAUDE.md +140 -1235
  2. package/bin/specweave.js +23 -0
  3. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +3 -0
  4. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  5. package/dist/plugins/specweave-github/lib/github-client-v2.js +39 -0
  6. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  7. package/dist/src/cli/commands/set-sync-target.d.ts +41 -0
  8. package/dist/src/cli/commands/set-sync-target.d.ts.map +1 -0
  9. package/dist/src/cli/commands/set-sync-target.js +126 -0
  10. package/dist/src/cli/commands/set-sync-target.js.map +1 -0
  11. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
  12. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +5 -8
  13. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
  14. package/dist/src/core/hooks/HookScanner.d.ts +32 -0
  15. package/dist/src/core/hooks/HookScanner.d.ts.map +1 -1
  16. package/dist/src/core/hooks/HookScanner.js +125 -1
  17. package/dist/src/core/hooks/HookScanner.js.map +1 -1
  18. package/dist/src/core/hooks/types.d.ts +10 -1
  19. package/dist/src/core/hooks/types.d.ts.map +1 -1
  20. package/dist/src/core/increment/metadata-manager.d.ts +67 -1
  21. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  22. package/dist/src/core/increment/metadata-manager.js +93 -0
  23. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  24. package/dist/src/core/project/index.d.ts +21 -0
  25. package/dist/src/core/project/index.d.ts.map +1 -0
  26. package/dist/src/core/project/index.js +22 -0
  27. package/dist/src/core/project/index.js.map +1 -0
  28. package/dist/src/core/project/project-service.d.ts +122 -0
  29. package/dist/src/core/project/project-service.d.ts.map +1 -0
  30. package/dist/src/core/project/project-service.js +334 -0
  31. package/dist/src/core/project/project-service.js.map +1 -0
  32. package/dist/src/core/sync/external-tool-resolver.d.ts +171 -0
  33. package/dist/src/core/sync/external-tool-resolver.d.ts.map +1 -0
  34. package/dist/src/core/sync/external-tool-resolver.js +569 -0
  35. package/dist/src/core/sync/external-tool-resolver.js.map +1 -0
  36. package/dist/src/core/types/increment-metadata.d.ts +92 -0
  37. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  38. package/dist/src/hooks/processor.d.ts +7 -3
  39. package/dist/src/hooks/processor.d.ts.map +1 -1
  40. package/dist/src/hooks/processor.js +11 -5
  41. package/dist/src/hooks/processor.js.map +1 -1
  42. package/package.json +1 -1
  43. package/plugins/specweave/hooks/hooks.json +0 -69
  44. package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +96 -0
  45. package/plugins/specweave/hooks/v2/queue/processor.sh +13 -5
  46. package/plugins/specweave/lib/hooks/project-bridge.js +76 -0
  47. package/plugins/specweave/lib/hooks/update-tasks-md.js +0 -0
  48. package/plugins/specweave/lib/hooks/us-completion-orchestrator.js +0 -0
  49. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +67 -1
  50. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +93 -0
  51. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  52. package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +92 -0
  53. package/plugins/specweave-github/lib/github-client-v2.js +39 -0
  54. package/plugins/specweave-github/lib/github-client-v2.ts +44 -0
  55. package/plugins/specweave/hooks/docs-changed.sh +0 -87
  56. package/plugins/specweave/hooks/human-input-required.sh +0 -83
  57. package/plugins/specweave/hooks/post-edit-write-consolidated.sh +0 -428
  58. package/plugins/specweave/hooks/post-first-increment.sh +0 -61
  59. package/plugins/specweave/hooks/post-increment-change.sh +0 -103
  60. package/plugins/specweave/hooks/post-increment-completion.sh +0 -513
  61. package/plugins/specweave/hooks/post-increment-planning.sh +0 -1204
  62. package/plugins/specweave/hooks/post-increment-status-change.sh +0 -243
  63. package/plugins/specweave/hooks/post-metadata-change.sh +0 -246
  64. package/plugins/specweave/hooks/post-spec-update.sh +0 -158
  65. package/plugins/specweave/hooks/post-task-completion.sh +0 -557
  66. package/plugins/specweave/hooks/post-task-edit.sh +0 -47
  67. package/plugins/specweave/hooks/post-user-story-complete.sh +0 -230
  68. package/plugins/specweave/hooks/pre-command-deduplication.sh +0 -68
  69. package/plugins/specweave/hooks/pre-edit-write-consolidated.sh +0 -225
  70. package/plugins/specweave/hooks/pre-implementation.sh +0 -75
  71. package/plugins/specweave/hooks/pre-increment-start.sh +0 -173
  72. package/plugins/specweave/hooks/pre-task-completion-edit.sh +0 -355
  73. package/plugins/specweave/hooks/pre-task-completion.sh +0 -269
  74. package/plugins/specweave/hooks/pre-tool-use.sh +0 -137
  75. package/plugins/specweave/hooks/session-start-reconcile.sh +0 -139
  76. package/plugins/specweave/hooks/shared/bulk-operation-detector.sh +0 -167
  77. package/plugins/specweave/hooks/test-pretooluse-env.sh +0 -72
  78. package/plugins/specweave/hooks/validate-increment-completion.sh +0 -113
  79. package/plugins/specweave/lib/hooks/consolidated-sync.js +0 -288
@@ -1,167 +0,0 @@
1
- #!/bin/bash
2
- # Bulk Operation Detector (v0.26.0)
3
- #
4
- # Detects bulk edit operations (5+ edits in 10 seconds) and triggers batch mode.
5
- # Part of ADR-0130: Hook Bulk Operation Detection and Batching
6
- #
7
- # Usage:
8
- # source bulk-operation-detector.sh
9
- # BULK_STATUS=$(detect_bulk_operation "$INCREMENT_ID")
10
- #
11
- # if [ "$BULK_STATUS" = "BULK_MODE_DETECTED" ]; then
12
- # # Skip individual hook, batch job will run later
13
- # exit 0
14
- # fi
15
-
16
- # Configuration
17
- BULK_THRESHOLD=${SPECWEAVE_BULK_THRESHOLD:-5} # 5+ operations = bulk mode
18
- BULK_WINDOW=${SPECWEAVE_BULK_WINDOW:-10} # seconds
19
- IDLE_DELAY=${SPECWEAVE_IDLE_DELAY:-5} # seconds before batch execution
20
-
21
- # State files
22
- STATE_DIR="$PROJECT_ROOT/.specweave/state"
23
- OPERATION_COUNTER="$STATE_DIR/.hook-operation-counter"
24
- OPERATION_TIMESTAMP="$STATE_DIR/.hook-operation-timestamp"
25
-
26
- # Ensure state directory exists
27
- mkdir -p "$STATE_DIR"
28
-
29
- ##
30
- # Detect bulk operation
31
- #
32
- # Uses sliding window counter to detect operation bursts:
33
- # - Counts operations within BULK_WINDOW (10 seconds)
34
- # - If count >= BULK_THRESHOLD (5 operations) → bulk mode
35
- # - Resets counter after idle period
36
- #
37
- # Args:
38
- # $1 - INCREMENT_ID (optional, for logging)
39
- #
40
- # Returns:
41
- # BULK_MODE_DETECTED - Bulk operation detected, skip individual hook
42
- # SINGLE_OPERATION - Single operation, run hook normally
43
- ##
44
- detect_bulk_operation() {
45
- local INCREMENT_ID="${1:-unknown}"
46
- local CURRENT_TIME=$(date +%s)
47
-
48
- # Read existing counter and timestamp
49
- local RECENT_OPS=0
50
- local LAST_OP_TIME=0
51
-
52
- if [ -f "$OPERATION_COUNTER" ]; then
53
- RECENT_OPS=$(cat "$OPERATION_COUNTER" 2>/dev/null || echo 0)
54
- fi
55
-
56
- if [ -f "$OPERATION_TIMESTAMP" ]; then
57
- LAST_OP_TIME=$(cat "$OPERATION_TIMESTAMP" 2>/dev/null || echo 0)
58
- fi
59
-
60
- # Reset counter if outside window (idle period detected)
61
- local TIME_SINCE_LAST_OP=$((CURRENT_TIME - LAST_OP_TIME))
62
- if (( TIME_SINCE_LAST_OP > BULK_WINDOW )); then
63
- RECENT_OPS=0
64
- fi
65
-
66
- # Increment operation counter
67
- RECENT_OPS=$((RECENT_OPS + 1))
68
-
69
- # Write atomically (use temp file + rename for atomicity)
70
- echo "$RECENT_OPS" > "${OPERATION_COUNTER}.tmp"
71
- mv "${OPERATION_COUNTER}.tmp" "$OPERATION_COUNTER"
72
-
73
- echo "$CURRENT_TIME" > "${OPERATION_TIMESTAMP}.tmp"
74
- mv "${OPERATION_TIMESTAMP}.tmp" "$OPERATION_TIMESTAMP"
75
-
76
- # Bulk operation detected?
77
- if (( RECENT_OPS >= BULK_THRESHOLD )); then
78
- # Log bulk mode activation
79
- local DEBUG_LOG="$PROJECT_ROOT/.specweave/logs/hook-debug.log"
80
- echo "[$(date)] [BULK] Detected bulk operation for $INCREMENT_ID (op #$RECENT_OPS)" >> "$DEBUG_LOG" 2>/dev/null || true
81
-
82
- # Schedule batch job (if not already scheduled)
83
- schedule_batch_job "$INCREMENT_ID"
84
-
85
- echo "BULK_MODE_DETECTED"
86
- return 0
87
- fi
88
-
89
- # Single operation
90
- echo "SINGLE_OPERATION"
91
- return 0
92
- }
93
-
94
- ##
95
- # Schedule batch job
96
- #
97
- # Schedules a batch job to run after idle period (IDLE_DELAY seconds).
98
- # If a batch job is already scheduled, it gets canceled and rescheduled.
99
- #
100
- # Args:
101
- # $1 - INCREMENT_ID
102
- ##
103
- schedule_batch_job() {
104
- local INCREMENT_ID="$1"
105
- local BATCH_LOCK="$STATE_DIR/.batch-job-${INCREMENT_ID}.lock"
106
- local DEBUG_LOG="$PROJECT_ROOT/.specweave/logs/hook-debug.log"
107
-
108
- # Cancel existing batch job (if any)
109
- if [ -f "$BATCH_LOCK" ]; then
110
- local OLD_PID=$(cat "$BATCH_LOCK" 2>/dev/null || echo "")
111
- if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then
112
- kill "$OLD_PID" 2>/dev/null || true
113
- echo "[$(date)] [BATCH] Canceled previous batch job (PID: $OLD_PID)" >> "$DEBUG_LOG" 2>/dev/null || true
114
- fi
115
- rm -f "$BATCH_LOCK"
116
- fi
117
-
118
- # Schedule new batch job (background process)
119
- (
120
- # Wait for idle period
121
- sleep "$IDLE_DELAY"
122
-
123
- # If operation counter still exists → run batched hook
124
- if [ -f "$OPERATION_COUNTER" ]; then
125
- local OPERATION_COUNT=$(cat "$OPERATION_COUNTER" 2>/dev/null || echo 0)
126
-
127
- echo "[$(date)] [BATCH] Idle period detected, running batch job for $INCREMENT_ID ($OPERATION_COUNT ops)" >> "$DEBUG_LOG" 2>/dev/null || true
128
-
129
- # Clear operation counter (batch job starting)
130
- rm -f "$OPERATION_COUNTER" "$OPERATION_TIMESTAMP"
131
-
132
- # Run consolidated hook in batch mode
133
- bash "$PROJECT_ROOT/plugins/specweave/hooks/post-task-completion.sh" \
134
- --batch-mode \
135
- --increment "$INCREMENT_ID" \
136
- >> "$PROJECT_ROOT/.specweave/logs/batch-jobs.log" 2>&1
137
-
138
- echo "[$(date)] [BATCH] āœ… Batch job completed for $INCREMENT_ID" >> "$DEBUG_LOG" 2>/dev/null || true
139
- else
140
- echo "[$(date)] [BATCH] Operation counter cleared before batch job ran (duplicate batch?)" >> "$DEBUG_LOG" 2>/dev/null || true
141
- fi
142
-
143
- # Cleanup batch lock
144
- rm -f "$BATCH_LOCK"
145
- ) &
146
-
147
- # Save PID for cancellation
148
- local BATCH_PID=$!
149
- echo "$BATCH_PID" > "$BATCH_LOCK"
150
-
151
- echo "[$(date)] [BATCH] Scheduled batch job for $INCREMENT_ID (PID: $BATCH_PID, delay: ${IDLE_DELAY}s)" >> "$DEBUG_LOG" 2>/dev/null || true
152
- }
153
-
154
- ##
155
- # Reset bulk operation counter
156
- #
157
- # Manually resets the operation counter (useful for testing or troubleshooting).
158
- ##
159
- reset_bulk_counter() {
160
- rm -f "$OPERATION_COUNTER" "$OPERATION_TIMESTAMP"
161
- echo "[$(date)] [BULK] Operation counter reset" >> "$PROJECT_ROOT/.specweave/logs/hook-debug.log" 2>/dev/null || true
162
- }
163
-
164
- # Export functions for use in hooks
165
- export -f detect_bulk_operation
166
- export -f schedule_batch_job
167
- export -f reset_bulk_counter
@@ -1,72 +0,0 @@
1
- #!/bin/bash
2
- #
3
- # PreToolUse Environment Variable Validation Test
4
- #
5
- # Purpose: Validate that PreToolUse hooks receive tool arguments via env vars
6
- # This tests the core assumption of Tier 2 implementation
7
- #
8
- # Usage:
9
- # 1. Register this hook in plugin.json under PreToolUse:Edit
10
- # 2. Make 10 test edits to any file
11
- # 3. Review /tmp/pretooluse-test.log
12
- # 4. Check if TOOL_USE_ARGS is populated
13
- #
14
- # Success Criteria:
15
- # - TOOL_USE_ARGS contains file_path
16
- # - At least 80% of invocations have non-empty TOOL_USE_ARGS
17
- #
18
- # If this test fails, Tier 2 PreToolUse coordination cannot work
19
- # and we must rely on Tier 1 (mtime fallback) or proceed to Tier 3
20
- #
21
- # Version: v0.24.2 (Tier 2 Validation)
22
- # Date: 2025-11-22
23
-
24
- set +e # Don't fail on errors (this is a diagnostic tool)
25
-
26
- TEST_LOG="/tmp/pretooluse-test.log"
27
- TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
28
-
29
- # Initialize log on first run
30
- if [[ ! -f "$TEST_LOG" ]]; then
31
- echo "==================================" > "$TEST_LOG"
32
- echo "PreToolUse Environment Variable Test" >> "$TEST_LOG"
33
- echo "Started: $TIMESTAMP" >> "$TEST_LOG"
34
- echo "==================================" >> "$TEST_LOG"
35
- echo "" >> "$TEST_LOG"
36
- fi
37
-
38
- # Record this invocation
39
- echo "[$TIMESTAMP] PreToolUse:Edit invocation #$(wc -l < "$TEST_LOG")" >> "$TEST_LOG"
40
-
41
- # Check ALL tool-related environment variables
42
- echo " TOOL_USE_ARGS: ${TOOL_USE_ARGS:-<EMPTY>}" >> "$TEST_LOG"
43
- echo " TOOL_USE_CONTENT: ${TOOL_USE_CONTENT:-<EMPTY>}" >> "$TEST_LOG"
44
- echo " TOOL_RESULT: ${TOOL_RESULT:-<EMPTY>}" >> "$TEST_LOG"
45
- echo " PWD: $PWD" >> "$TEST_LOG"
46
-
47
- # Try to extract file_path if TOOL_USE_ARGS exists
48
- if [[ -n "${TOOL_USE_ARGS:-}" ]]; then
49
- if command -v jq &> /dev/null; then
50
- FILE_PATH=$(echo "$TOOL_USE_ARGS" | jq -r '.file_path // "<NOT_FOUND>"' 2>/dev/null)
51
- echo " Extracted file_path: $FILE_PATH" >> "$TEST_LOG"
52
- else
53
- FILE_PATH=$(echo "$TOOL_USE_ARGS" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "<PARSE_FAILED>")
54
- echo " Extracted file_path (no jq): $FILE_PATH" >> "$TEST_LOG"
55
- fi
56
- fi
57
-
58
- # Dump all TOOL* env vars
59
- echo " All TOOL* environment variables:" >> "$TEST_LOG"
60
- env | grep -E "^TOOL" | while read line; do
61
- echo " $line" >> "$TEST_LOG"
62
- done
63
-
64
- echo "" >> "$TEST_LOG"
65
-
66
- # Log rotation: Keep last 100 invocations
67
- if [[ $(wc -l < "$TEST_LOG") -gt 500 ]]; then
68
- tail -400 "$TEST_LOG" > "$TEST_LOG.tmp"
69
- mv "$TEST_LOG.tmp" "$TEST_LOG"
70
- fi
71
-
72
- exit 0 # Always succeed (diagnostic tool)
@@ -1,113 +0,0 @@
1
- #!/bin/bash
2
- #
3
- # Pre-commit hook: Validate Increment Completion
4
- #
5
- # Prevents commits where increment status="completed" but:
6
- # - Acceptance criteria are still open (- [ ] **AC-...)
7
- # - Tasks are still pending (**Status**: [ ] pending)
8
- #
9
- # This prevents false completion and data integrity violations.
10
- #
11
- # Exit code 0: All completed increments are valid (allow commit)
12
- # Exit code 1: Invalid completion detected (block commit)
13
-
14
- # ANSI colors
15
- RED='\033[0;31m'
16
- YELLOW='\033[1;33m'
17
- GREEN='\033[0;32m'
18
- NC='\033[0m' # No Color
19
-
20
- # Check if in SpecWeave project
21
- if [ ! -d ".specweave/increments" ]; then
22
- # Not a SpecWeave project, skip validation
23
- exit 0
24
- fi
25
-
26
- # Track if any validation failures found
27
- has_failures=false
28
-
29
- # Check all increments
30
- for increment_dir in .specweave/increments/*/; do
31
- # Skip if not a directory
32
- [ ! -d "$increment_dir" ] && continue
33
-
34
- increment_id=$(basename "$increment_dir")
35
- spec_file="$increment_dir/spec.md"
36
- tasks_file="$increment_dir/tasks.md"
37
- metadata_file="$increment_dir/metadata.json"
38
-
39
- # Skip if required files don't exist
40
- [ ! -f "$spec_file" ] && continue
41
- [ ! -f "$tasks_file" ] && continue
42
-
43
- # Get status from spec.md frontmatter
44
- spec_status=$(grep -m1 "^status:" "$spec_file" 2>/dev/null | cut -d: -f2 | tr -d ' ')
45
-
46
- # Get status from metadata.json (if exists)
47
- metadata_status=""
48
- if [ -f "$metadata_file" ]; then
49
- metadata_status=$(grep -m1 '"status"' "$metadata_file" 2>/dev/null | cut -d'"' -f4)
50
- fi
51
-
52
- # Only validate if status is "completed"
53
- if [ "$spec_status" = "completed" ] || [ "$metadata_status" = "completed" ]; then
54
- # Count open acceptance criteria (- [ ] **AC-)
55
- open_acs=$(grep -c "^- \[ \] \*\*AC-" "$spec_file" 2>/dev/null || echo 0)
56
-
57
- # Count pending tasks (**Status**: [ ] pending)
58
- pending_tasks=$(grep -ic "\*\*Status\*\*:\s*\[\s*\]\s*pending" "$tasks_file" 2>/dev/null || echo 0)
59
-
60
- # Validate
61
- if [ "$open_acs" -gt 0 ] || [ "$pending_tasks" -gt 0 ]; then
62
- has_failures=true
63
-
64
- echo -e "${RED}āŒ COMMIT BLOCKED: Invalid completion detected${NC}"
65
- echo -e "${YELLOW}Increment: $increment_id${NC}"
66
- echo -e "Status: $spec_status (spec.md) / $metadata_status (metadata.json)"
67
- echo ""
68
-
69
- if [ "$open_acs" -gt 0 ]; then
70
- echo -e "${RED} • $open_acs acceptance criteria still open${NC}"
71
- fi
72
-
73
- if [ "$pending_tasks" -gt 0 ]; then
74
- echo -e "${RED} • $pending_tasks tasks still pending${NC}"
75
- fi
76
-
77
- echo ""
78
- echo -e "${YELLOW}Fix options:${NC}"
79
- echo " 1. Complete the open work before committing"
80
- echo " 2. Change status to 'active' or 'paused' in both spec.md and metadata.json"
81
- echo ""
82
- echo -e "${YELLOW}To see details:${NC}"
83
- echo " cat $spec_file | grep '- \[ \] \*\*AC-'"
84
- echo " cat $tasks_file | grep -i 'Status.*pending'"
85
- echo ""
86
- fi
87
- fi
88
- done
89
-
90
- # Exit with failure if any validation failures
91
- if [ "$has_failures" = true ]; then
92
- echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
93
- echo -e "${RED}COMMIT BLOCKED: Cannot commit increments with invalid completion${NC}"
94
- echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
95
- echo ""
96
- echo -e "${YELLOW}Why this matters:${NC}"
97
- echo " • Prevents false completion (status='completed' with open work)"
98
- echo " • Ensures data integrity (metadata matches reality)"
99
- echo " • Stops misleading status line and GitHub sync"
100
- echo ""
101
- echo -e "${YELLOW}To fix:${NC}"
102
- echo " 1. Complete all open ACs and pending tasks, OR"
103
- echo " 2. Change status to 'active'/'paused' in spec.md and metadata.json"
104
- echo ""
105
- echo -e "${YELLOW}To bypass (NOT RECOMMENDED):${NC}"
106
- echo " git commit --no-verify"
107
- echo ""
108
- exit 1
109
- fi
110
-
111
- # All validations passed
112
- echo -e "${GREEN}āœ… Completion validation passed${NC}"
113
- exit 0
@@ -1,288 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Consolidated Sync Hook
4
- *
5
- * PERFORMANCE OPTIMIZATION (v0.24.4):
6
- * =====================================
7
- * This script consolidates ALL post-task-completion operations into a SINGLE
8
- * Node.js process instead of spawning 5-6 separate processes.
9
- *
10
- * BEFORE (v0.24.3):
11
- * - Spawn 1: update-tasks-md.js (10KB)
12
- * - Spawn 2: sync-living-docs.js (18KB)
13
- * - Spawn 3: update-ac-status.js (2KB)
14
- * - Spawn 4: translate-living-docs.js (4.4KB)
15
- * - Spawn 5: run-self-reflection.js (4.1KB - conditional)
16
- * Total: 5-6 Node.js processes per TodoWrite
17
- *
18
- * AFTER (v0.24.4):
19
- * - Spawn 1: consolidated-sync.js (runs all operations sequentially)
20
- * Total: 1 Node.js process per TodoWrite
21
- *
22
- * IMPACT:
23
- * - 83% reduction in process spawning overhead
24
- * - Faster execution (shared Node.js context)
25
- * - Better error handling (single error boundary)
26
- * - Reduced system resource usage
27
- *
28
- * ROOT CAUSE ADDRESSED:
29
- * Multiple rapid TodoWrite calls were spawning 12+ concurrent Node.js processes,
30
- * causing process exhaustion and Claude Code crashes. This consolidation prevents
31
- * that by limiting to 1 process per hook fire.
32
- *
33
- * See: .specweave/increments/0051-automatic-github-sync/reports/HOOK-CRASH-ANALYSIS.md
34
- * ADR: (pending - will be created after testing)
35
- */
36
-
37
- // Import REAL implementations from existing modules
38
- import { updateTasksMd } from './update-tasks-md.js';
39
- import { syncLivingDocs } from './sync-living-docs.js';
40
- import { translateLivingDocs } from './translate-living-docs.js';
41
-
42
- // Import for AC status (uses ACStatusManager directly)
43
- import { ACStatusManager } from '../vendor/core/increment/ac-status-manager.js';
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
-
50
- // Import US completion orchestrator (NEW in v0.25.0+)
51
- import { syncCompletedUserStories } from './us-completion-orchestrator.js';
52
-
53
- // Import auto-transition (NEW in v0.35.0+ - CRITICAL for preventing auto-completion bug)
54
- import { checkAndTransitionToReadyForReview } from '../vendor/core/increment/status-auto-transition.js';
55
-
56
- // ============================================================================
57
- // WRAPPER: UPDATE AC STATUS
58
- // ============================================================================
59
- // Note: update-ac-status.js doesn't export a function, it only has CLI logic
60
- // So we replicate the core logic here using ACStatusManager
61
- async function updateACStatus(incrementId) {
62
- try {
63
- console.log(`\nšŸ”„ [3/6] Syncing AC status for increment ${incrementId}...`);
64
-
65
- const projectRoot = process.cwd();
66
-
67
- if (process.env.SKIP_AC_SYNC === 'true') {
68
- console.log('ā„¹ļø AC sync skipped (SKIP_AC_SYNC=true)');
69
- return { success: true, message: 'Sync skipped' };
70
- }
71
-
72
- const manager = new ACStatusManager(projectRoot);
73
- const result = await manager.syncACStatus(incrementId);
74
-
75
- if (result.warnings && result.warnings.length > 0) {
76
- console.log('\nāš ļø Warnings:');
77
- result.warnings.forEach((warning) => console.log(` ${warning}`));
78
- }
79
-
80
- if (result.conflicts && result.conflicts.length > 0) {
81
- console.log('\nāš ļø Conflicts detected:');
82
- result.conflicts.forEach((conflict) => console.log(` ${conflict}`));
83
- }
84
-
85
- if (result.updated && result.updated.length > 0) {
86
- console.log('\nāœ… Updated AC checkboxes:');
87
- result.updated.forEach((acId) => console.log(` ${acId} → [x]`));
88
- } else if (result.synced) {
89
- console.log('āœ… All ACs already in sync (no changes needed)');
90
- } else {
91
- console.log('ā„¹ļø No AC updates needed');
92
- }
93
-
94
- return { success: true, result };
95
- } catch (error) {
96
- console.error('āŒ Error updating AC status:', error.message);
97
- return { success: false, error: error.message };
98
- }
99
- }
100
-
101
- // ============================================================================
102
- // WRAPPER: GITHUB SYNC
103
- // ============================================================================
104
- async function syncGitHub(incrementId) {
105
- try {
106
- console.log(`\nšŸ”— [5/6] Syncing to GitHub...`);
107
-
108
- const projectRoot = process.cwd();
109
-
110
- const coordinator = new SyncCoordinator({
111
- projectRoot,
112
- incrementId,
113
- logger: consoleLogger
114
- });
115
-
116
- const result = await coordinator.syncIncrementCompletion();
117
-
118
- if (result.success) {
119
- console.log(`āœ… GitHub sync completed (${result.userStoriesSynced} user stories synced)`);
120
- } else {
121
- console.warn(`āš ļø GitHub sync had errors (see logs)`);
122
- }
123
-
124
- return { success: result.success, result };
125
- } catch (error) {
126
- console.error('āŒ Error syncing to GitHub:', error.message);
127
- return { success: false, error: error.message };
128
- }
129
- }
130
-
131
- // ============================================================================
132
- // MAIN EXECUTION
133
- // ============================================================================
134
- async function runConsolidatedSync(incrementId) {
135
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
136
- console.log(`šŸš€ CONSOLIDATED SYNC: ${incrementId}`);
137
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
138
-
139
- const startTime = Date.now();
140
- const results = {};
141
-
142
- try {
143
- // OPERATION 0 (v0.35.0+ - CRITICAL): Check for ACTIVE → READY_FOR_REVIEW transition
144
- // This MUST run FIRST to prevent the auto-completion bug!
145
- // When all tasks are done, increment transitions to READY_FOR_REVIEW (not COMPLETED).
146
- // Only /sw:done can then transition to COMPLETED with user approval.
147
- console.log('\nšŸ“‹ [0/6] Checking for status auto-transition...');
148
- try {
149
- const transitionResult = checkAndTransitionToReadyForReview(incrementId);
150
- if (transitionResult.transitioned) {
151
- console.log(`\nšŸŽ‰ ${transitionResult.message}`);
152
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
153
- console.log('šŸ“‹ INCREMENT READY FOR REVIEW');
154
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
155
- console.log(` Status: ACTIVE → READY_FOR_REVIEW`);
156
- console.log(` Tasks: ${transitionResult.taskStatus?.completedTasks}/${transitionResult.taskStatus?.totalTasks} completed`);
157
- console.log('');
158
- console.log(' To close this increment, run:');
159
- console.log(` /sw:done ${incrementId}`);
160
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
161
- } else {
162
- console.log(` ${transitionResult.message}`);
163
- }
164
- results.autoTransition = { success: true, ...transitionResult };
165
- } catch (error) {
166
- console.error('āŒ Error checking auto-transition:', error.message);
167
- results.autoTransition = { success: false, error: error.message };
168
- }
169
-
170
- // OPERATION 1: Update tasks.md (uses imported function)
171
- console.log('\nšŸ”„ [1/6] Updating tasks.md...');
172
- try {
173
- await updateTasksMd(incrementId);
174
- results.updateTasks = { success: true };
175
- } catch (error) {
176
- console.error('āŒ Error updating tasks.md:', error.message);
177
- results.updateTasks = { success: false, error: error.message };
178
- }
179
-
180
- // OPERATION 2: Sync living docs (uses imported function)
181
- console.log('\nšŸ“š [2/6] Syncing living docs...');
182
- try {
183
- await syncLivingDocs(incrementId);
184
- results.syncDocs = { success: true };
185
- } catch (error) {
186
- console.error('āŒ Error syncing living docs:', error.message);
187
- results.syncDocs = { success: false, error: error.message };
188
- }
189
-
190
- // OPERATION 3: Update AC status (uses local wrapper)
191
- try {
192
- results.updateAC = await updateACStatus(incrementId);
193
- } catch (error) {
194
- console.error('āŒ Error updating AC status:', error.message);
195
- results.updateAC = { success: false, error: error.message };
196
- }
197
-
198
- // OPERATION 4: Translate living docs (uses imported function)
199
- console.log('\n🌐 [4/6] Checking translation needs...');
200
- try {
201
- await translateLivingDocs(incrementId);
202
- results.translate = { success: true };
203
- } catch (error) {
204
- console.error('āŒ Error translating docs:', error.message);
205
- results.translate = { success: false, error: error.message };
206
- }
207
-
208
- // OPERATION 5: Detect and sync newly completed user stories (NEW in v0.25.0+)
209
- // CRITICAL: This operation runs AFTER AC sync to ensure ACs are up-to-date
210
- // When a user story becomes fully complete (all ACs satisfied), this:
211
- // - Detects the completion
212
- // - Updates living docs
213
- // - Triggers external tool sync (GitHub/JIRA/ADO)
214
- try {
215
- results.usCompletion = await syncCompletedUserStories(incrementId);
216
- } catch (error) {
217
- console.error('āŒ Error detecting completed user stories:', error.message);
218
- results.usCompletion = { success: false, error: error.message };
219
- }
220
-
221
- // OPERATION 6: Sync to GitHub (NEW in v0.24.0+)
222
- // FIX (v0.26.0): Skip GitHub sync in post-task-completion hook!
223
- // WHY: GitHub sync should ONLY run on increment COMPLETION, not task completion
224
- // This prevents 27 duplicate comments on every TodoWrite (see root cause analysis)
225
- //
226
- // GitHub sync is now ONLY triggered by:
227
- // - post-increment-completion.sh (when status → "completed")
228
- // - Manual sync via /specweave-github:sync command
229
- // - US completion orchestrator (OPERATION 5) when user stories complete
230
- //
231
- // See: .specweave/increments/0051-*/reports/GITHUB-COMMENT-RECURSION-ROOT-CAUSE-2025-11-24.md
232
- if (process.env.SKIP_GITHUB_SYNC === 'true') {
233
- console.log('\nā­ļø [6/6] GitHub sync SKIPPED (called from post-task-completion hook)');
234
- console.log(' GitHub sync will run automatically on increment completion.');
235
- results.syncGitHub = { success: true, skipped: true };
236
- } else {
237
- try {
238
- results.syncGitHub = await syncGitHub(incrementId);
239
- } catch (error) {
240
- console.error('āŒ Error syncing to GitHub:', error.message);
241
- results.syncGitHub = { success: false, error: error.message };
242
- }
243
- }
244
-
245
- const duration = Date.now() - startTime;
246
-
247
- console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
248
- console.log(`āœ… CONSOLIDATED SYNC COMPLETED in ${duration}ms`);
249
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
250
-
251
- // Summary of results
252
- const successCount = Object.values(results).filter(r => r.success).length;
253
- const totalCount = Object.keys(results).length;
254
- console.log(`šŸ“Š Results: ${successCount}/${totalCount} operations successful`);
255
-
256
- // Check for any failures (non-blocking - we tolerate partial failures)
257
- const failures = Object.entries(results).filter(([_, result]) => !result.success);
258
- if (failures.length > 0) {
259
- console.warn('\nāš ļø Some operations had issues (non-blocking):');
260
- failures.forEach(([op, result]) => {
261
- console.warn(` ${op}: ${result.error || 'Unknown error'}`);
262
- });
263
- }
264
-
265
- // Always exit 0 to prevent hook failures from blocking Claude Code
266
- process.exit(0);
267
- } catch (error) {
268
- console.error('\nāŒ FATAL ERROR in consolidated sync:', error);
269
- // Even on fatal error, exit 0 to prevent blocking Claude Code
270
- process.exit(0);
271
- }
272
- }
273
-
274
- // CLI Interface
275
- const isMainModule = import.meta.url === `file://${process.argv[1]}`;
276
- if (isMainModule) {
277
- const incrementId = process.argv[2];
278
-
279
- if (!incrementId) {
280
- console.error('Usage: node consolidated-sync.js <increment-id>');
281
- console.error('Example: node consolidated-sync.js 0051-automatic-github-sync');
282
- process.exit(1);
283
- }
284
-
285
- runConsolidatedSync(incrementId);
286
- }
287
-
288
- export { runConsolidatedSync };