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.
- package/CLAUDE.md +140 -1235
- package/bin/specweave.js +23 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +3 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +39 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/src/cli/commands/set-sync-target.d.ts +41 -0
- package/dist/src/cli/commands/set-sync-target.d.ts.map +1 -0
- package/dist/src/cli/commands/set-sync-target.js +126 -0
- package/dist/src/cli/commands/set-sync-target.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +5 -8
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
- package/dist/src/core/hooks/HookScanner.d.ts +32 -0
- package/dist/src/core/hooks/HookScanner.d.ts.map +1 -1
- package/dist/src/core/hooks/HookScanner.js +125 -1
- package/dist/src/core/hooks/HookScanner.js.map +1 -1
- package/dist/src/core/hooks/types.d.ts +10 -1
- package/dist/src/core/hooks/types.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.d.ts +67 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +93 -0
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/project/index.d.ts +21 -0
- package/dist/src/core/project/index.d.ts.map +1 -0
- package/dist/src/core/project/index.js +22 -0
- package/dist/src/core/project/index.js.map +1 -0
- package/dist/src/core/project/project-service.d.ts +122 -0
- package/dist/src/core/project/project-service.d.ts.map +1 -0
- package/dist/src/core/project/project-service.js +334 -0
- package/dist/src/core/project/project-service.js.map +1 -0
- package/dist/src/core/sync/external-tool-resolver.d.ts +171 -0
- package/dist/src/core/sync/external-tool-resolver.d.ts.map +1 -0
- package/dist/src/core/sync/external-tool-resolver.js +569 -0
- package/dist/src/core/sync/external-tool-resolver.js.map +1 -0
- package/dist/src/core/types/increment-metadata.d.ts +92 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/hooks/processor.d.ts +7 -3
- package/dist/src/hooks/processor.d.ts.map +1 -1
- package/dist/src/hooks/processor.js +11 -5
- package/dist/src/hooks/processor.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +0 -69
- package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +96 -0
- package/plugins/specweave/hooks/v2/queue/processor.sh +13 -5
- package/plugins/specweave/lib/hooks/project-bridge.js +76 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.js +0 -0
- package/plugins/specweave/lib/hooks/us-completion-orchestrator.js +0 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +67 -1
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +93 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +92 -0
- package/plugins/specweave-github/lib/github-client-v2.js +39 -0
- package/plugins/specweave-github/lib/github-client-v2.ts +44 -0
- package/plugins/specweave/hooks/docs-changed.sh +0 -87
- package/plugins/specweave/hooks/human-input-required.sh +0 -83
- package/plugins/specweave/hooks/post-edit-write-consolidated.sh +0 -428
- package/plugins/specweave/hooks/post-first-increment.sh +0 -61
- package/plugins/specweave/hooks/post-increment-change.sh +0 -103
- package/plugins/specweave/hooks/post-increment-completion.sh +0 -513
- package/plugins/specweave/hooks/post-increment-planning.sh +0 -1204
- package/plugins/specweave/hooks/post-increment-status-change.sh +0 -243
- package/plugins/specweave/hooks/post-metadata-change.sh +0 -246
- package/plugins/specweave/hooks/post-spec-update.sh +0 -158
- package/plugins/specweave/hooks/post-task-completion.sh +0 -557
- package/plugins/specweave/hooks/post-task-edit.sh +0 -47
- package/plugins/specweave/hooks/post-user-story-complete.sh +0 -230
- package/plugins/specweave/hooks/pre-command-deduplication.sh +0 -68
- package/plugins/specweave/hooks/pre-edit-write-consolidated.sh +0 -225
- package/plugins/specweave/hooks/pre-implementation.sh +0 -75
- package/plugins/specweave/hooks/pre-increment-start.sh +0 -173
- package/plugins/specweave/hooks/pre-task-completion-edit.sh +0 -355
- package/plugins/specweave/hooks/pre-task-completion.sh +0 -269
- package/plugins/specweave/hooks/pre-tool-use.sh +0 -137
- package/plugins/specweave/hooks/session-start-reconcile.sh +0 -139
- package/plugins/specweave/hooks/shared/bulk-operation-detector.sh +0 -167
- package/plugins/specweave/hooks/test-pretooluse-env.sh +0 -72
- package/plugins/specweave/hooks/validate-increment-completion.sh +0 -113
- 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 };
|