specweave 0.23.1 → 0.23.4

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 (101) hide show
  1. package/.claude-plugin/marketplace.json +0 -88
  2. package/CLAUDE.md +368 -0
  3. package/bin/fix-marketplace-errors.sh +8 -8
  4. package/dist/plugins/specweave/lib/utils/fs-native.d.ts +133 -0
  5. package/dist/plugins/specweave/lib/utils/fs-native.d.ts.map +1 -0
  6. package/dist/plugins/specweave/lib/utils/fs-native.js +224 -0
  7. package/dist/plugins/specweave/lib/utils/fs-native.js.map +1 -0
  8. package/dist/plugins/specweave-github/lib/github-client-v2.js +1 -1
  9. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  10. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  11. package/dist/plugins/specweave-github/lib/github-feature-sync.js +52 -20
  12. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  13. package/dist/src/cli/helpers/init/initial-increment-generator.d.ts.map +1 -1
  14. package/dist/src/cli/helpers/init/initial-increment-generator.js +2 -1
  15. package/dist/src/cli/helpers/init/initial-increment-generator.js.map +1 -1
  16. package/dist/src/core/ac-test-validator-cli.d.ts +16 -0
  17. package/dist/src/core/ac-test-validator-cli.d.ts.map +1 -0
  18. package/dist/src/core/ac-test-validator-cli.js +118 -0
  19. package/dist/src/core/ac-test-validator-cli.js.map +1 -0
  20. package/dist/src/core/ac-test-validator.d.ts +111 -0
  21. package/dist/src/core/ac-test-validator.d.ts.map +1 -0
  22. package/dist/src/core/ac-test-validator.js +292 -0
  23. package/dist/src/core/ac-test-validator.js.map +1 -0
  24. package/dist/src/core/increment/desync-detector.d.ts +142 -0
  25. package/dist/src/core/increment/desync-detector.d.ts.map +1 -0
  26. package/dist/src/core/increment/desync-detector.js +270 -0
  27. package/dist/src/core/increment/desync-detector.js.map +1 -0
  28. package/dist/src/core/increment/metadata-manager.d.ts +8 -4
  29. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  30. package/dist/src/core/increment/metadata-manager.js +45 -21
  31. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  32. package/dist/src/core/qa/qa-runner.js +9 -2
  33. package/dist/src/core/qa/qa-runner.js.map +1 -1
  34. package/dist/src/sync/sync-coordinator.d.ts +1 -1
  35. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  36. package/dist/src/sync/sync-coordinator.js +40 -2
  37. package/dist/src/sync/sync-coordinator.js.map +1 -1
  38. package/dist/src/utils/fs-native.d.ts +133 -0
  39. package/dist/src/utils/fs-native.d.ts.map +1 -0
  40. package/dist/src/utils/fs-native.js +224 -0
  41. package/dist/src/utils/fs-native.js.map +1 -0
  42. package/package.json +1 -1
  43. package/plugins/specweave/.claude-plugin/plugin.json +22 -0
  44. package/plugins/specweave/agents/AGENTS-INDEX.md +216 -0
  45. package/plugins/specweave/agents/architect/AGENT.md +17 -0
  46. package/plugins/specweave/agents/code-standards-detective/AGENT.md +16 -0
  47. package/plugins/specweave/agents/docs-writer/AGENT.md +16 -0
  48. package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +704 -0
  49. package/plugins/specweave/agents/infrastructure/AGENT.md +16 -0
  50. package/plugins/specweave/agents/performance/AGENT.md +16 -0
  51. package/plugins/specweave/agents/pm/AGENT.md +17 -0
  52. package/plugins/specweave/agents/qa-lead/AGENT.md +15 -0
  53. package/plugins/specweave/agents/reflective-reviewer/AGENT.md +16 -0
  54. package/plugins/specweave/agents/security/AGENT.md +16 -0
  55. package/plugins/specweave/agents/tdd-orchestrator/AGENT.md +16 -0
  56. package/plugins/specweave/agents/tech-lead/AGENT.md +16 -0
  57. package/plugins/specweave/agents/test-aware-planner/AGENT.md +16 -0
  58. package/plugins/specweave/agents/translator/AGENT.md +13 -0
  59. package/plugins/specweave/commands/specweave-done.md +14 -0
  60. package/plugins/specweave/commands/specweave-qa.md +11 -1
  61. package/plugins/specweave/commands/specweave-sync-status.md +356 -0
  62. package/plugins/specweave/commands/specweave-validate.md +10 -1
  63. package/plugins/specweave/hooks/post-metadata-change.sh +160 -0
  64. package/plugins/specweave/hooks/pre-task-completion.sh +196 -0
  65. package/plugins/specweave/lib/hooks/git-diff-analyzer.js +3 -3
  66. package/plugins/specweave/lib/hooks/git-diff-analyzer.ts +3 -3
  67. package/plugins/specweave/lib/hooks/invoke-translator-skill.js +3 -2
  68. package/plugins/specweave/lib/hooks/invoke-translator-skill.ts +3 -2
  69. package/plugins/specweave/lib/hooks/prepare-reflection-context.js +3 -3
  70. package/plugins/specweave/lib/hooks/prepare-reflection-context.ts +3 -3
  71. package/plugins/specweave/lib/hooks/reflection-config-loader.js +4 -4
  72. package/plugins/specweave/lib/hooks/reflection-config-loader.ts +4 -4
  73. package/plugins/specweave/lib/hooks/reflection-storage.js +9 -9
  74. package/plugins/specweave/lib/hooks/reflection-storage.ts +9 -9
  75. package/plugins/specweave/lib/hooks/sync-cache.js +9 -8
  76. package/plugins/specweave/lib/hooks/sync-living-docs.js +57 -6
  77. package/plugins/specweave/lib/hooks/sync-us-tasks.js +6 -6
  78. package/plugins/specweave/lib/hooks/translate-file.js +3 -2
  79. package/plugins/specweave/lib/hooks/translate-file.ts +3 -2
  80. package/plugins/specweave/lib/hooks/translate-living-docs.js +4 -3
  81. package/plugins/specweave/lib/hooks/translate-living-docs.ts +4 -3
  82. package/plugins/specweave/lib/utils/fs-native.js +182 -0
  83. package/plugins/specweave/lib/utils/fs-native.ts +283 -0
  84. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +8 -4
  85. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +45 -21
  86. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  87. package/plugins/specweave/skills/SKILLS-INDEX.md +26 -2
  88. package/plugins/specweave/skills/increment-planner/SKILL.md +2 -2
  89. package/plugins/specweave-ado/commands/specweave-ado-close-workitem.md +1 -1
  90. package/plugins/specweave-ado/commands/specweave-ado-create-workitem.md +1 -1
  91. package/plugins/specweave-ado/commands/specweave-ado-status.md +1 -1
  92. package/plugins/specweave-ado/commands/specweave-ado-sync.md +1 -1
  93. package/plugins/specweave-diagrams/agents/diagrams-architect/AGENT.md +1 -1
  94. package/plugins/specweave-diagrams/skills/diagrams-generator/SKILL.md +4 -4
  95. package/plugins/specweave-github/lib/github-client-v2.js +2 -1
  96. package/plugins/specweave-github/lib/github-client-v2.ts +1 -1
  97. package/plugins/specweave-github/lib/github-feature-sync.js +30 -17
  98. package/plugins/specweave-github/lib/github-feature-sync.ts +54 -24
  99. package/plugins/specweave-mobile/README.md +1 -1
  100. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +72 -0
  101. package/plugins/specweave/skills/task-builder/README.md +0 -84
@@ -0,0 +1,160 @@
1
+ #!/bin/bash
2
+ #
3
+ # Post-Metadata-Change Hook: Dispatcher for Increment Lifecycle Events
4
+ #
5
+ # Triggers: After Write/Edit modifies metadata.json
6
+ # Purpose: Detect WHAT changed in metadata and call appropriate lifecycle hook
7
+ #
8
+ # Architecture:
9
+ # - metadata.json is the source of truth for increment state
10
+ # - Different state changes require different actions:
11
+ # * status: "completed" → Call post-increment-completion.sh
12
+ # * status: "paused"|"resumed"|"abandoned" → Call post-increment-status-change.sh
13
+ # * other changes → Update status line only
14
+ #
15
+ # This fixes the critical bug where status line never updates on increment closure
16
+ # because post-increment-completion.sh was orphaned (never registered or called).
17
+ #
18
+ # Related Incident: 2025-11-20 - Increment 0047 completed but status line still shows active
19
+ # Root Cause: metadata.json writes don't trigger status line refresh
20
+ # Fix: This hook dispatches to post-increment-completion.sh which updates status line
21
+
22
+ set -e
23
+
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")
33
+ done
34
+ echo "$PWD"
35
+ }
36
+
37
+ PROJECT_ROOT=$(find_project_root)
38
+ LOGS_DIR="$PROJECT_ROOT/.specweave/logs"
39
+ DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
40
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
41
+
42
+ # Ensure logs directory exists
43
+ mkdir -p "$LOGS_DIR" 2>/dev/null || true
44
+
45
+ # Extract modified file from environment variables (Claude Code provides this)
46
+ MODIFIED_FILE=""
47
+
48
+ # Method 1: TOOL_USE_CONTENT environment variable (primary for Write)
49
+ if [[ -n "${TOOL_USE_CONTENT:-}" ]]; then
50
+ MODIFIED_FILE="$TOOL_USE_CONTENT"
51
+ fi
52
+
53
+ # Method 2: TOOL_RESULT environment variable (fallback)
54
+ if [[ -z "$MODIFIED_FILE" ]] && [[ -n "${TOOL_RESULT:-}" ]]; then
55
+ MODIFIED_FILE=$(echo "$TOOL_RESULT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "")
56
+ fi
57
+
58
+ # Method 3: Parse tool use arguments (last resort for Edit)
59
+ if [[ -z "$MODIFIED_FILE" ]] && [[ -n "${TOOL_USE_ARGS:-}" ]]; then
60
+ MODIFIED_FILE=$(echo "$TOOL_USE_ARGS" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "")
61
+ fi
62
+
63
+ echo "[$(date)] post-metadata-change: Detected file: ${MODIFIED_FILE:-<none>}" >> "$DEBUG_LOG" 2>/dev/null || true
64
+
65
+ # Check if this is a metadata.json change in an increment folder
66
+ if [[ -z "$MODIFIED_FILE" ]] || [[ "$MODIFIED_FILE" != *"/metadata.json" ]] || [[ "$MODIFIED_FILE" != *"/.specweave/increments/"* ]]; then
67
+ # Not a metadata.json change in increments folder - exit silently
68
+ exit 0
69
+ fi
70
+
71
+ # Exclude archived increments (shouldn't affect status line)
72
+ if [[ "$MODIFIED_FILE" == *"/_archive/"* ]]; then
73
+ echo "[$(date)] post-metadata-change: Archived increment - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
74
+ exit 0
75
+ fi
76
+
77
+ echo "[$(date)] post-metadata-change: metadata.json changed - analyzing..." >> "$DEBUG_LOG" 2>/dev/null || true
78
+
79
+ # Extract increment ID from path
80
+ # Path format: /path/to/project/.specweave/increments/0047-name/metadata.json
81
+ INCREMENT_ID=$(echo "$MODIFIED_FILE" | grep -o '\.specweave/increments/[^/]*' | sed 's/\.specweave\/increments\///')
82
+
83
+ if [[ -z "$INCREMENT_ID" ]]; then
84
+ echo "[$(date)] post-metadata-change: Could not extract increment ID from path: $MODIFIED_FILE" >> "$DEBUG_LOG" 2>/dev/null || true
85
+ exit 0
86
+ fi
87
+
88
+ echo "[$(date)] post-metadata-change: Increment ID: $INCREMENT_ID" >> "$DEBUG_LOG" 2>/dev/null || true
89
+
90
+ # Read the metadata.json to detect what changed
91
+ METADATA_PATH="$PROJECT_ROOT/.specweave/increments/$INCREMENT_ID/metadata.json"
92
+
93
+ if [[ ! -f "$METADATA_PATH" ]]; then
94
+ echo "[$(date)] post-metadata-change: metadata.json not found at $METADATA_PATH" >> "$DEBUG_LOG" 2>/dev/null || true
95
+ exit 0
96
+ fi
97
+
98
+ # Check if jq is available for parsing JSON
99
+ if ! command -v jq &> /dev/null; then
100
+ echo "[$(date)] post-metadata-change: jq not found - updating status line as fallback" >> "$DEBUG_LOG" 2>/dev/null || true
101
+ bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
102
+ exit 0
103
+ fi
104
+
105
+ # Extract current status
106
+ CURRENT_STATUS=$(jq -r '.status // "unknown"' "$METADATA_PATH" 2>/dev/null)
107
+
108
+ echo "[$(date)] post-metadata-change: Current status: $CURRENT_STATUS" >> "$DEBUG_LOG" 2>/dev/null || true
109
+
110
+ # Dispatch to appropriate lifecycle hook based on status
111
+ case "$CURRENT_STATUS" in
112
+ completed)
113
+ # Increment completed - call post-increment-completion.sh
114
+ # This hook handles:
115
+ # - Closing GitHub issues
116
+ # - Syncing living docs
117
+ # - Updating status line
118
+ echo "[$(date)] post-metadata-change: Increment completed - calling post-increment-completion.sh" >> "$DEBUG_LOG" 2>/dev/null || true
119
+
120
+ if [[ -x "$HOOK_DIR/post-increment-completion.sh" ]]; then
121
+ bash "$HOOK_DIR/post-increment-completion.sh" "$INCREMENT_ID" 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
122
+ echo "[$(date)] post-metadata-change: post-increment-completion.sh failed (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
123
+ }
124
+ else
125
+ echo "[$(date)] post-metadata-change: post-increment-completion.sh not found or not executable" >> "$DEBUG_LOG" 2>/dev/null || true
126
+ # Fallback: Update status line directly
127
+ bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
128
+ fi
129
+ ;;
130
+
131
+ paused|resumed|abandoned)
132
+ # Status change - call post-increment-status-change.sh
133
+ # Note: This typically gets called manually by /specweave:pause commands
134
+ # But we handle it here for completeness
135
+ echo "[$(date)] post-metadata-change: Status changed to $CURRENT_STATUS - calling post-increment-status-change.sh" >> "$DEBUG_LOG" 2>/dev/null || true
136
+
137
+ if [[ -x "$HOOK_DIR/post-increment-status-change.sh" ]]; then
138
+ # Extract reason if available
139
+ REASON=$(jq -r '.statusReason // "Not specified"' "$METADATA_PATH" 2>/dev/null)
140
+ bash "$HOOK_DIR/post-increment-status-change.sh" "$INCREMENT_ID" "$CURRENT_STATUS" "$REASON" 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
141
+ echo "[$(date)] post-metadata-change: post-increment-status-change.sh failed (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
142
+ }
143
+ else
144
+ echo "[$(date)] post-metadata-change: post-increment-status-change.sh not found" >> "$DEBUG_LOG" 2>/dev/null || true
145
+ # Fallback: Update status line directly
146
+ bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
147
+ fi
148
+ ;;
149
+
150
+ *)
151
+ # Other metadata changes (e.g., task completion count, AC count)
152
+ # Just update status line to reflect new progress
153
+ echo "[$(date)] post-metadata-change: Status is $CURRENT_STATUS - updating status line only" >> "$DEBUG_LOG" 2>/dev/null || true
154
+ bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
155
+ ;;
156
+ esac
157
+
158
+ echo "[$(date)] post-metadata-change: Complete" >> "$DEBUG_LOG" 2>/dev/null || true
159
+
160
+ exit 0
@@ -0,0 +1,196 @@
1
+ #!/bin/bash
2
+
3
+ # SpecWeave Pre-Task-Completion Hook
4
+ # CRITICAL QUALITY GATE: Validates AC tests before allowing task completion
5
+ #
6
+ # Runs automatically BEFORE any task is marked complete via TodoWrite
7
+ #
8
+ # WORKFLOW:
9
+ # =========
10
+ # 1. TodoWrite called with status="completed"
11
+ # 2. This hook fires (pre-completion validation)
12
+ # 3. Extract task ID from TodoWrite input
13
+ # 4. Find task in tasks.md
14
+ # 5. Run AC test validator
15
+ # 6. If tests PASS → Allow completion (continue: true)
16
+ # 7. If tests FAIL → Block completion (continue: false, show error)
17
+ #
18
+ # ENFORCEMENT:
19
+ # ============
20
+ # This is the ONLY way to mark tasks complete in SpecWeave.
21
+ # Manual edits to tasks.md are detected and flagged by pre-commit hooks.
22
+
23
+ set -e
24
+
25
+ # Find project root
26
+ find_project_root() {
27
+ local dir="$1"
28
+ while [ "$dir" != "/" ]; do
29
+ if [ -d "$dir/.specweave" ]; then
30
+ echo "$dir"
31
+ return 0
32
+ fi
33
+ dir="$(dirname "$dir")"
34
+ done
35
+ pwd
36
+ }
37
+
38
+ PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
39
+ cd "$PROJECT_ROOT" 2>/dev/null || true
40
+
41
+ # ============================================================================
42
+ # CONFIGURATION
43
+ # ============================================================================
44
+
45
+ LOGS_DIR=".specweave/logs"
46
+ DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
47
+
48
+ mkdir -p "$LOGS_DIR" 2>/dev/null || true
49
+
50
+ echo "[$(date)] 🔒 Pre-task-completion hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
51
+
52
+ # ============================================================================
53
+ # CAPTURE INPUT
54
+ # ============================================================================
55
+
56
+ STDIN_DATA=$(mktemp)
57
+ cat > "$STDIN_DATA"
58
+
59
+ echo "[$(date)] Input JSON:" >> "$DEBUG_LOG" 2>/dev/null || true
60
+ cat "$STDIN_DATA" >> "$DEBUG_LOG" 2>/dev/null || true
61
+
62
+ # ============================================================================
63
+ # CHECK FOR TASK COMPLETION
64
+ # ============================================================================
65
+
66
+ # Only validate if a task is being marked complete
67
+ COMPLETING_TASK=false
68
+
69
+ if command -v jq >/dev/null 2>&1; then
70
+ # Check if any task is transitioning to "completed" status
71
+ COMPLETED_COUNT=$(jq -r '.tool_input.todos // [] | map(select(.status == "completed")) | length' "$STDIN_DATA" 2>/dev/null || echo "0")
72
+
73
+ if [ "$COMPLETED_COUNT" != "0" ]; then
74
+ COMPLETING_TASK=true
75
+ echo "[$(date)] ✓ Detected task completion (${COMPLETED_COUNT} tasks)" >> "$DEBUG_LOG" 2>/dev/null || true
76
+ fi
77
+ fi
78
+
79
+ # If no tasks being completed, allow without validation
80
+ if [ "$COMPLETING_TASK" = "false" ]; then
81
+ echo "[$(date)] ⏭️ No tasks being completed, skipping validation" >> "$DEBUG_LOG" 2>/dev/null || true
82
+ rm -f "$STDIN_DATA"
83
+ cat <<EOF
84
+ {
85
+ "continue": true
86
+ }
87
+ EOF
88
+ exit 0
89
+ fi
90
+
91
+ # ============================================================================
92
+ # DETECT CURRENT INCREMENT
93
+ # ============================================================================
94
+
95
+ CURRENT_INCREMENT=$(ls -t .specweave/increments/ 2>/dev/null | grep -v "_backlog" | grep -v "_archive" | head -1)
96
+
97
+ if [ -z "$CURRENT_INCREMENT" ]; then
98
+ echo "[$(date)] ⚠️ No active increment found, skipping validation" >> "$DEBUG_LOG" 2>/dev/null || true
99
+ rm -f "$STDIN_DATA"
100
+ cat <<EOF
101
+ {
102
+ "continue": true,
103
+ "systemMessage": "⚠️ Warning: No active increment found. Task completion validation skipped."
104
+ }
105
+ EOF
106
+ exit 0
107
+ fi
108
+
109
+ TASKS_MD=".specweave/increments/$CURRENT_INCREMENT/tasks.md"
110
+
111
+ if [ ! -f "$TASKS_MD" ]; then
112
+ echo "[$(date)] ⚠️ tasks.md not found for $CURRENT_INCREMENT" >> "$DEBUG_LOG" 2>/dev/null || true
113
+ rm -f "$STDIN_DATA"
114
+ cat <<EOF
115
+ {
116
+ "continue": true,
117
+ "systemMessage": "⚠️ Warning: tasks.md not found. Task completion validation skipped."
118
+ }
119
+ EOF
120
+ exit 0
121
+ fi
122
+
123
+ # ============================================================================
124
+ # RUN AC TEST VALIDATION
125
+ # ============================================================================
126
+
127
+ echo "[$(date)] 🧪 Running AC test validation for $CURRENT_INCREMENT" >> "$DEBUG_LOG" 2>/dev/null || true
128
+
129
+ # Determine which validation script to use
130
+ VALIDATOR_SCRIPT=""
131
+ if [ -f "$PROJECT_ROOT/dist/src/core/ac-test-validator-cli.js" ]; then
132
+ VALIDATOR_SCRIPT="$PROJECT_ROOT/dist/src/core/ac-test-validator-cli.js"
133
+ elif [ -f "$PROJECT_ROOT/node_modules/specweave/dist/src/core/ac-test-validator-cli.js" ]; then
134
+ VALIDATOR_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/src/core/ac-test-validator-cli.js"
135
+ elif [ -n "${CLAUDE_PLUGIN_ROOT}" ] && [ -f "${CLAUDE_PLUGIN_ROOT}/dist/src/core/ac-test-validator-cli.js" ]; then
136
+ VALIDATOR_SCRIPT="${CLAUDE_PLUGIN_ROOT}/dist/src/core/ac-test-validator-cli.js"
137
+ fi
138
+
139
+ if [ -z "$VALIDATOR_SCRIPT" ] || ! command -v node &> /dev/null; then
140
+ echo "[$(date)] ⚠️ AC test validator not found or Node.js missing" >> "$DEBUG_LOG" 2>/dev/null || true
141
+ rm -f "$STDIN_DATA"
142
+ cat <<EOF
143
+ {
144
+ "continue": true,
145
+ "systemMessage": "⚠️ Warning: AC test validator not available. Task completion validation skipped. Install Node.js and rebuild SpecWeave to enable validation."
146
+ }
147
+ EOF
148
+ exit 0
149
+ fi
150
+
151
+ # Run validator (captures exit code)
152
+ VALIDATION_OUTPUT=$(mktemp)
153
+ VALIDATION_EXIT_CODE=0
154
+
155
+ (cd "$PROJECT_ROOT" && node "$VALIDATOR_SCRIPT" "$CURRENT_INCREMENT") > "$VALIDATION_OUTPUT" 2>&1 || VALIDATION_EXIT_CODE=$?
156
+
157
+ echo "[$(date)] Validator exit code: $VALIDATION_EXIT_CODE" >> "$DEBUG_LOG" 2>/dev/null || true
158
+ cat "$VALIDATION_OUTPUT" >> "$DEBUG_LOG" 2>/dev/null || true
159
+
160
+ rm -f "$STDIN_DATA"
161
+
162
+ # ============================================================================
163
+ # DECISION LOGIC
164
+ # ============================================================================
165
+
166
+ if [ "$VALIDATION_EXIT_CODE" = "0" ]; then
167
+ # Validation passed - allow completion
168
+ echo "[$(date)] ✅ AC test validation passed" >> "$DEBUG_LOG" 2>/dev/null || true
169
+
170
+ VALIDATION_SUMMARY=$(cat "$VALIDATION_OUTPUT" | tail -5 | tr '\n' ' ')
171
+
172
+ rm -f "$VALIDATION_OUTPUT"
173
+
174
+ cat <<EOF
175
+ {
176
+ "continue": true,
177
+ "systemMessage": "✅ AC Test Validation Passed: All acceptance criteria have passing tests. Task completion allowed. ${VALIDATION_SUMMARY}"
178
+ }
179
+ EOF
180
+ else
181
+ # Validation failed - block completion
182
+ echo "[$(date)] ❌ AC test validation failed" >> "$DEBUG_LOG" 2>/dev/null || true
183
+
184
+ VALIDATION_ERROR=$(cat "$VALIDATION_OUTPUT" | grep -A 10 "VALIDATION FAILED" | tr '\n' ' ' | cut -c 1-300)
185
+
186
+ rm -f "$VALIDATION_OUTPUT"
187
+
188
+ cat <<EOF
189
+ {
190
+ "continue": false,
191
+ "systemMessage": "❌ AC TEST VALIDATION FAILED: Cannot mark task as complete until all acceptance criteria have passing tests. ${VALIDATION_ERROR}
192
+
193
+ Fix the failing tests and try again. Run tests manually: npm test"
194
+ }
195
+ EOF
196
+ fi
@@ -1,5 +1,5 @@
1
1
  import { execSync } from "child_process";
2
- import fs from "fs-extra";
2
+ import { existsSync, readFileSync } from "fs";
3
3
  import path from "path";
4
4
  function executeGitCommand(command, cwd) {
5
5
  try {
@@ -65,10 +65,10 @@ function getFileContent(file, cwd) {
65
65
  try {
66
66
  const workingDir = cwd || process.cwd();
67
67
  const absolutePath = path.isAbsolute(file) ? file : path.join(workingDir, file);
68
- if (!fs.existsSync(absolutePath)) {
68
+ if (!existsSync(absolutePath)) {
69
69
  return "";
70
70
  }
71
- return fs.readFileSync(absolutePath, "utf-8");
71
+ return readFileSync(absolutePath, "utf-8");
72
72
  } catch {
73
73
  return "";
74
74
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { execSync } from 'child_process';
11
- import fs from 'fs-extra';
11
+ import { existsSync, readFileSync } from 'fs';
12
12
  import path from 'path';
13
13
  import { GitDiffInfo } from './types/reflection-types';
14
14
 
@@ -136,11 +136,11 @@ export function getFileContent(file: string, cwd?: string): string {
136
136
  const workingDir = cwd || process.cwd();
137
137
  const absolutePath = path.isAbsolute(file) ? file : path.join(workingDir, file);
138
138
 
139
- if (!fs.existsSync(absolutePath)) {
139
+ if (!existsSync(absolutePath)) {
140
140
  return '';
141
141
  }
142
142
 
143
- return fs.readFileSync(absolutePath, 'utf-8');
143
+ return readFileSync(absolutePath, 'utf-8');
144
144
  } catch {
145
145
  return '';
146
146
  }
@@ -1,4 +1,5 @@
1
- import fs from "fs-extra";
1
+ import { existsSync } from "fs";
2
+ import { promises as fs } from "fs";
2
3
  import {
3
4
  detectLanguage,
4
5
  prepareTranslation,
@@ -94,7 +95,7 @@ ${contentToTranslate}`,
94
95
  }
95
96
  }
96
97
  async function translateFile(filePath, targetLang = "en") {
97
- if (!await fs.pathExists(filePath)) {
98
+ if (!existsSync(filePath)) {
98
99
  throw new Error(`File not found: ${filePath}`);
99
100
  }
100
101
  const content = await fs.readFile(filePath, "utf-8");
@@ -8,7 +8,8 @@
8
8
  * @see plugins/specweave/commands/translate.md
9
9
  */
10
10
 
11
- import fs from 'fs-extra';
11
+ import { existsSync } from 'fs';
12
+ import { promises as fs } from 'fs';
12
13
  import path from 'path';
13
14
  import {
14
15
  detectLanguage,
@@ -186,7 +187,7 @@ export async function translateFile(
186
187
  targetLang: SupportedLanguage = 'en'
187
188
  ): Promise<TranslationResult & { filePath: string }> {
188
189
  // Read file
189
- if (!await fs.pathExists(filePath)) {
190
+ if (!existsSync(filePath)) {
190
191
  throw new Error(`File not found: ${filePath}`);
191
192
  }
192
193
 
@@ -1,4 +1,4 @@
1
- import fs from "fs-extra";
1
+ import { mkdirpSync, writeJsonSync } from "../utils/fs-native.js";
2
2
  import path from "path";
3
3
  import { createReflectionContext } from "./run-self-reflection";
4
4
  import { getModifiedFilesSummary } from "./git-diff-analyzer";
@@ -10,7 +10,7 @@ function prepareReflectionContext(incrementId, taskId, projectRoot) {
10
10
  }
11
11
  const rootDir = projectRoot || process.cwd();
12
12
  const tempDir = path.join(rootDir, ".specweave", "increments", incrementId, "logs", "reflections", ".temp");
13
- fs.mkdirpSync(tempDir);
13
+ mkdirpSync(tempDir);
14
14
  const contextFile = path.join(tempDir, "reflection-context.json");
15
15
  const fileStats = getModifiedFilesSummary(context.modifiedFiles);
16
16
  const contextData = {
@@ -30,7 +30,7 @@ function prepareReflectionContext(incrementId, taskId, projectRoot) {
30
30
  config: context.config,
31
31
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
32
32
  };
33
- fs.writeJsonSync(contextFile, contextData, { spaces: 2 });
33
+ writeJsonSync(contextFile, contextData, { spaces: 2 });
34
34
  return contextFile;
35
35
  } catch (error) {
36
36
  console.error(`Failed to prepare reflection context: ${error.message}`);
@@ -7,7 +7,7 @@
7
7
  * @module prepare-reflection-context
8
8
  */
9
9
 
10
- import fs from 'fs-extra';
10
+ import { mkdirpSync, writeJsonSync } from '../utils/fs-native.js';
11
11
  import path from 'path';
12
12
  import { createReflectionContext } from './run-self-reflection';
13
13
  import { getModifiedFilesSummary } from './git-diff-analyzer';
@@ -37,7 +37,7 @@ export function prepareReflectionContext(
37
37
  // Create temp directory for reflection context
38
38
  const rootDir = projectRoot || process.cwd();
39
39
  const tempDir = path.join(rootDir, '.specweave', 'increments', incrementId, 'logs', 'reflections', '.temp');
40
- fs.mkdirpSync(tempDir);
40
+ mkdirpSync(tempDir);
41
41
 
42
42
  // Save context to temp file
43
43
  const contextFile = path.join(tempDir, 'reflection-context.json');
@@ -62,7 +62,7 @@ export function prepareReflectionContext(
62
62
  timestamp: new Date().toISOString()
63
63
  };
64
64
 
65
- fs.writeJsonSync(contextFile, contextData, { spaces: 2 });
65
+ writeJsonSync(contextFile, contextData, { spaces: 2 });
66
66
 
67
67
  return contextFile;
68
68
  } catch (error: any) {
@@ -1,4 +1,4 @@
1
- import fs from "fs-extra";
1
+ import { existsSync, readFileSync } from "fs";
2
2
  import path from "path";
3
3
  import { DEFAULT_REFLECTION_CONFIG } from "./types/reflection-types";
4
4
  function findSpecweaveRoot(startDir = process.cwd()) {
@@ -6,7 +6,7 @@ function findSpecweaveRoot(startDir = process.cwd()) {
6
6
  const root = path.parse(currentDir).root;
7
7
  while (currentDir !== root) {
8
8
  const specweavePath = path.join(currentDir, ".specweave");
9
- if (fs.existsSync(specweavePath)) {
9
+ if (existsSync(specweavePath)) {
10
10
  return currentDir;
11
11
  }
12
12
  currentDir = path.dirname(currentDir);
@@ -19,11 +19,11 @@ function loadReflectionConfig(projectRoot) {
19
19
  return { ...DEFAULT_REFLECTION_CONFIG };
20
20
  }
21
21
  const configPath = path.join(rootDir, ".specweave", "config.json");
22
- if (!fs.existsSync(configPath)) {
22
+ if (!existsSync(configPath)) {
23
23
  return { ...DEFAULT_REFLECTION_CONFIG };
24
24
  }
25
25
  try {
26
- const configContent = fs.readFileSync(configPath, "utf-8");
26
+ const configContent = readFileSync(configPath, "utf-8");
27
27
  const config = JSON.parse(configContent);
28
28
  const userReflectionConfig = config.reflection || {};
29
29
  const mergedConfig = {
@@ -7,7 +7,7 @@
7
7
  * @module reflection-config-loader
8
8
  */
9
9
 
10
- import fs from 'fs-extra';
10
+ import { existsSync, readFileSync } from 'fs';
11
11
  import path from 'path';
12
12
  import { DEFAULT_REFLECTION_CONFIG, ReflectionConfig } from './types/reflection-types';
13
13
 
@@ -22,7 +22,7 @@ export function findSpecweaveRoot(startDir: string = process.cwd()): string | nu
22
22
 
23
23
  while (currentDir !== root) {
24
24
  const specweavePath = path.join(currentDir, '.specweave');
25
- if (fs.existsSync(specweavePath)) {
25
+ if (existsSync(specweavePath)) {
26
26
  return currentDir;
27
27
  }
28
28
  currentDir = path.dirname(currentDir);
@@ -51,13 +51,13 @@ export function loadReflectionConfig(projectRoot?: string): ReflectionConfig {
51
51
  const configPath = path.join(rootDir, '.specweave', 'config.json');
52
52
 
53
53
  // Config file doesn't exist, return defaults
54
- if (!fs.existsSync(configPath)) {
54
+ if (!existsSync(configPath)) {
55
55
  return { ...DEFAULT_REFLECTION_CONFIG };
56
56
  }
57
57
 
58
58
  try {
59
59
  // Read and parse config file
60
- const configContent = fs.readFileSync(configPath, 'utf-8');
60
+ const configContent = readFileSync(configPath, 'utf-8');
61
61
  const config = JSON.parse(configContent);
62
62
 
63
63
  // Extract reflection section (may be undefined)
@@ -1,4 +1,4 @@
1
- import fs from "fs-extra";
1
+ import { mkdirpSync, writeFileSync, existsSync, readdirSync, statSync, readFileSync, removeSync } from "../utils/fs-native.js";
2
2
  import path from "path";
3
3
  import { IssueSeverity } from "./types/reflection-types";
4
4
  function generateReflectionMarkdown(result) {
@@ -183,12 +183,12 @@ function getReflectionFilename(taskId, timestamp) {
183
183
  }
184
184
  function saveReflection(result, incrementId, taskId, projectRoot) {
185
185
  const logDir = getReflectionLogDir(incrementId, projectRoot);
186
- fs.mkdirpSync(logDir);
186
+ mkdirpSync(logDir);
187
187
  const filename = getReflectionFilename(taskId);
188
188
  const filepath = path.join(logDir, filename);
189
189
  const markdown = generateReflectionMarkdown(result);
190
190
  try {
191
- fs.writeFileSync(filepath, markdown, "utf-8");
191
+ writeFileSync(filepath, markdown, "utf-8");
192
192
  return filepath;
193
193
  } catch (error) {
194
194
  throw new Error(`Failed to save reflection: ${error.message}`);
@@ -196,26 +196,26 @@ function saveReflection(result, incrementId, taskId, projectRoot) {
196
196
  }
197
197
  function listReflections(incrementId, projectRoot) {
198
198
  const logDir = getReflectionLogDir(incrementId, projectRoot);
199
- if (!fs.existsSync(logDir)) {
199
+ if (!existsSync(logDir)) {
200
200
  return [];
201
201
  }
202
- const files = fs.readdirSync(logDir).filter((file) => file.endsWith(".md")).map((file) => path.join(logDir, file)).sort((a, b) => {
203
- const statA = fs.statSync(a);
204
- const statB = fs.statSync(b);
202
+ const files = readdirSync(logDir).filter((file) => file.endsWith(".md")).map((file) => path.join(logDir, file)).sort((a, b) => {
203
+ const statA = statSync(a);
204
+ const statB = statSync(b);
205
205
  return statB.mtime.getTime() - statA.mtime.getTime();
206
206
  });
207
207
  return files;
208
208
  }
209
209
  function readReflection(filepath) {
210
210
  try {
211
- return fs.readFileSync(filepath, "utf-8");
211
+ return readFileSync(filepath, "utf-8");
212
212
  } catch (error) {
213
213
  throw new Error(`Failed to read reflection: ${error.message}`);
214
214
  }
215
215
  }
216
216
  function deleteReflection(filepath) {
217
217
  try {
218
- fs.removeSync(filepath);
218
+ removeSync(filepath);
219
219
  } catch (error) {
220
220
  throw new Error(`Failed to delete reflection: ${error.message}`);
221
221
  }
@@ -7,7 +7,7 @@
7
7
  * @module reflection-storage
8
8
  */
9
9
 
10
- import fs from 'fs-extra';
10
+ import { mkdirpSync, writeFileSync, existsSync, readdirSync, statSync, readFileSync, removeSync } from '../utils/fs-native.js';
11
11
  import path from 'path';
12
12
  import { ReflectionResult, IssueSeverity } from './types/reflection-types';
13
13
 
@@ -294,7 +294,7 @@ export function saveReflection(
294
294
  ): string {
295
295
  // Ensure reflection logs directory exists
296
296
  const logDir = getReflectionLogDir(incrementId, projectRoot);
297
- fs.mkdirpSync(logDir);
297
+ mkdirpSync(logDir);
298
298
 
299
299
  // Generate filename
300
300
  const filename = getReflectionFilename(taskId);
@@ -305,7 +305,7 @@ export function saveReflection(
305
305
 
306
306
  // Write to file
307
307
  try {
308
- fs.writeFileSync(filepath, markdown, 'utf-8');
308
+ writeFileSync(filepath, markdown, 'utf-8');
309
309
  return filepath;
310
310
  } catch (error: any) {
311
311
  throw new Error(`Failed to save reflection: ${error.message}`);
@@ -324,17 +324,17 @@ export function listReflections(
324
324
  ): string[] {
325
325
  const logDir = getReflectionLogDir(incrementId, projectRoot);
326
326
 
327
- if (!fs.existsSync(logDir)) {
327
+ if (!existsSync(logDir)) {
328
328
  return [];
329
329
  }
330
330
 
331
- const files = fs.readdirSync(logDir)
331
+ const files = readdirSync(logDir)
332
332
  .filter(file => file.endsWith('.md'))
333
333
  .map(file => path.join(logDir, file))
334
334
  .sort((a, b) => {
335
335
  // Sort by modification time (newest first)
336
- const statA = fs.statSync(a);
337
- const statB = fs.statSync(b);
336
+ const statA = statSync(a);
337
+ const statB = statSync(b);
338
338
  return statB.mtime.getTime() - statA.mtime.getTime();
339
339
  });
340
340
 
@@ -349,7 +349,7 @@ export function listReflections(
349
349
  */
350
350
  export function readReflection(filepath: string): string {
351
351
  try {
352
- return fs.readFileSync(filepath, 'utf-8');
352
+ return readFileSync(filepath, 'utf-8');
353
353
  } catch (error: any) {
354
354
  throw new Error(`Failed to read reflection: ${error.message}`);
355
355
  }
@@ -362,7 +362,7 @@ export function readReflection(filepath: string): string {
362
362
  */
363
363
  export function deleteReflection(filepath: string): void {
364
364
  try {
365
- fs.removeSync(filepath);
365
+ removeSync(filepath);
366
366
  } catch (error: any) {
367
367
  throw new Error(`Failed to delete reflection: ${error.message}`);
368
368
  }