specweave 1.0.550 → 1.0.552

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 (166) hide show
  1. package/CLAUDE.md +1 -1
  2. package/bin/specweave.js +23 -1
  3. package/dist/src/cli/commands/hook.d.ts +15 -0
  4. package/dist/src/cli/commands/hook.d.ts.map +1 -0
  5. package/dist/src/cli/commands/hook.js +61 -0
  6. package/dist/src/cli/commands/hook.js.map +1 -0
  7. package/dist/src/cli/commands/init.d.ts.map +1 -1
  8. package/dist/src/cli/commands/init.js +5 -0
  9. package/dist/src/cli/commands/init.js.map +1 -1
  10. package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
  11. package/dist/src/cli/commands/refresh-plugins.js +11 -1
  12. package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
  13. package/dist/src/cli/commands/sync-setup.d.ts.map +1 -1
  14. package/dist/src/cli/commands/sync-setup.js +7 -3
  15. package/dist/src/cli/commands/sync-setup.js.map +1 -1
  16. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts +9 -0
  17. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts.map +1 -1
  18. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js +9 -3
  19. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js.map +1 -1
  20. package/dist/src/config/types.d.ts +2 -2
  21. package/dist/src/core/config/types.d.ts +18 -2
  22. package/dist/src/core/config/types.d.ts.map +1 -1
  23. package/dist/src/core/config/types.js.map +1 -1
  24. package/dist/src/core/hooks/handlers/hook-router.d.ts +19 -0
  25. package/dist/src/core/hooks/handlers/hook-router.d.ts.map +1 -0
  26. package/dist/src/core/hooks/handlers/hook-router.js +75 -0
  27. package/dist/src/core/hooks/handlers/hook-router.js.map +1 -0
  28. package/dist/src/core/hooks/handlers/index.d.ts +10 -0
  29. package/dist/src/core/hooks/handlers/index.d.ts.map +1 -0
  30. package/dist/src/core/hooks/handlers/index.js +9 -0
  31. package/dist/src/core/hooks/handlers/index.js.map +1 -0
  32. package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts +11 -0
  33. package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts.map +1 -0
  34. package/dist/src/core/hooks/handlers/post-tool-use-analytics.js +73 -0
  35. package/dist/src/core/hooks/handlers/post-tool-use-analytics.js.map +1 -0
  36. package/dist/src/core/hooks/handlers/post-tool-use.d.ts +11 -0
  37. package/dist/src/core/hooks/handlers/post-tool-use.d.ts.map +1 -0
  38. package/dist/src/core/hooks/handlers/post-tool-use.js +76 -0
  39. package/dist/src/core/hooks/handlers/post-tool-use.js.map +1 -0
  40. package/dist/src/core/hooks/handlers/pre-compact.d.ts +11 -0
  41. package/dist/src/core/hooks/handlers/pre-compact.d.ts.map +1 -0
  42. package/dist/src/core/hooks/handlers/pre-compact.js +77 -0
  43. package/dist/src/core/hooks/handlers/pre-compact.js.map +1 -0
  44. package/dist/src/core/hooks/handlers/pre-tool-use.d.ts +11 -0
  45. package/dist/src/core/hooks/handlers/pre-tool-use.d.ts.map +1 -0
  46. package/dist/src/core/hooks/handlers/pre-tool-use.js +318 -0
  47. package/dist/src/core/hooks/handlers/pre-tool-use.js.map +1 -0
  48. package/dist/src/core/hooks/handlers/session-start.d.ts +9 -0
  49. package/dist/src/core/hooks/handlers/session-start.d.ts.map +1 -0
  50. package/dist/src/core/hooks/handlers/session-start.js +111 -0
  51. package/dist/src/core/hooks/handlers/session-start.js.map +1 -0
  52. package/dist/src/core/hooks/handlers/stop-auto.d.ts +16 -0
  53. package/dist/src/core/hooks/handlers/stop-auto.d.ts.map +1 -0
  54. package/dist/src/core/hooks/handlers/stop-auto.js +122 -0
  55. package/dist/src/core/hooks/handlers/stop-auto.js.map +1 -0
  56. package/dist/src/core/hooks/handlers/stop-reflect.d.ts +14 -0
  57. package/dist/src/core/hooks/handlers/stop-reflect.d.ts.map +1 -0
  58. package/dist/src/core/hooks/handlers/stop-reflect.js +43 -0
  59. package/dist/src/core/hooks/handlers/stop-reflect.js.map +1 -0
  60. package/dist/src/core/hooks/handlers/stop-sync.d.ts +15 -0
  61. package/dist/src/core/hooks/handlers/stop-sync.d.ts.map +1 -0
  62. package/dist/src/core/hooks/handlers/stop-sync.js +68 -0
  63. package/dist/src/core/hooks/handlers/stop-sync.js.map +1 -0
  64. package/dist/src/core/hooks/handlers/types.d.ts +63 -0
  65. package/dist/src/core/hooks/handlers/types.d.ts.map +1 -0
  66. package/dist/src/core/hooks/handlers/types.js +27 -0
  67. package/dist/src/core/hooks/handlers/types.js.map +1 -0
  68. package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts +14 -0
  69. package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts.map +1 -0
  70. package/dist/src/core/hooks/handlers/user-prompt-submit.js +173 -0
  71. package/dist/src/core/hooks/handlers/user-prompt-submit.js.map +1 -0
  72. package/dist/src/core/hooks/handlers/utils.d.ts +25 -0
  73. package/dist/src/core/hooks/handlers/utils.d.ts.map +1 -0
  74. package/dist/src/core/hooks/handlers/utils.js +64 -0
  75. package/dist/src/core/hooks/handlers/utils.js.map +1 -0
  76. package/dist/src/core/increment/completion-validator.d.ts.map +1 -1
  77. package/dist/src/core/increment/completion-validator.js +32 -0
  78. package/dist/src/core/increment/completion-validator.js.map +1 -1
  79. package/dist/src/init/research/types.d.ts +1 -1
  80. package/dist/src/sync/sync-target-resolver.js.map +1 -1
  81. package/dist/src/utils/lock-manager.d.ts.map +1 -1
  82. package/dist/src/utils/lock-manager.js +5 -0
  83. package/dist/src/utils/lock-manager.js.map +1 -1
  84. package/dist/src/utils/plugin-copier.d.ts +10 -0
  85. package/dist/src/utils/plugin-copier.d.ts.map +1 -1
  86. package/dist/src/utils/plugin-copier.js +63 -35
  87. package/dist/src/utils/plugin-copier.js.map +1 -1
  88. package/package.json +1 -1
  89. package/plugins/specweave/agents/sw-closer.md +3 -2
  90. package/plugins/specweave/hooks/hooks.json +10 -10
  91. package/plugins/specweave/skills/code-reviewer/SKILL.md +180 -16
  92. package/plugins/specweave/skills/code-reviewer/agents/reviewer-comments.md +83 -0
  93. package/plugins/specweave/skills/code-reviewer/agents/reviewer-silent-failures.md +19 -0
  94. package/plugins/specweave/skills/code-reviewer/agents/reviewer-spec-compliance.md +19 -0
  95. package/plugins/specweave/skills/code-reviewer/agents/reviewer-tests.md +101 -0
  96. package/plugins/specweave/skills/code-reviewer/agents/reviewer-types.md +20 -0
  97. package/plugins/specweave/skills/done/SKILL.md +56 -21
  98. package/plugins/specweave/skills/grill/SKILL.md +1 -1
  99. package/plugins/specweave/skills/team-lead/agents/reviewer-logic.md +19 -0
  100. package/plugins/specweave/skills/team-lead/agents/reviewer-performance.md +20 -0
  101. package/plugins/specweave/skills/team-lead/agents/reviewer-security.md +20 -0
  102. package/src/templates/CLAUDE.md.template +7 -4
  103. package/plugins/specweave/hooks/README.md +0 -493
  104. package/plugins/specweave/hooks/_archive/stop-auto-v4-legacy.sh +0 -1319
  105. package/plugins/specweave/hooks/lib/common-setup.sh +0 -144
  106. package/plugins/specweave/hooks/lib/hook-errors.sh +0 -414
  107. package/plugins/specweave/hooks/lib/migrate-increment-work.sh +0 -245
  108. package/plugins/specweave/hooks/lib/resolve-package.sh +0 -146
  109. package/plugins/specweave/hooks/lib/scheduler-startup.sh +0 -135
  110. package/plugins/specweave/hooks/lib/score-increment.sh +0 -87
  111. package/plugins/specweave/hooks/lib/sync-spec-content.sh +0 -193
  112. package/plugins/specweave/hooks/lib/update-active-increment.sh +0 -95
  113. package/plugins/specweave/hooks/lib/update-status-line.sh +0 -233
  114. package/plugins/specweave/hooks/lib/validate-spec-status.sh +0 -171
  115. package/plugins/specweave/hooks/llm-judge-validator.sh +0 -219
  116. package/plugins/specweave/hooks/log-decision.sh +0 -168
  117. package/plugins/specweave/hooks/pre-compact.sh +0 -64
  118. package/plugins/specweave/hooks/startup-health-check.sh +0 -64
  119. package/plugins/specweave/hooks/stop-auto-v5.sh +0 -276
  120. package/plugins/specweave/hooks/stop-reflect.sh +0 -336
  121. package/plugins/specweave/hooks/stop-sync.sh +0 -283
  122. package/plugins/specweave/hooks/tests/test-auto-context-integration.sh +0 -126
  123. package/plugins/specweave/hooks/tests/test-stop-auto-enriched.sh +0 -128
  124. package/plugins/specweave/hooks/universal/dispatcher.mjs +0 -336
  125. package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +0 -325
  126. package/plugins/specweave/hooks/universal/hook-wrapper.cmd +0 -26
  127. package/plugins/specweave/hooks/universal/hook-wrapper.sh +0 -69
  128. package/plugins/specweave/hooks/universal/run-hook.sh +0 -20
  129. package/plugins/specweave/hooks/universal/session-start.cmd +0 -16
  130. package/plugins/specweave/hooks/universal/session-start.ps1 +0 -16
  131. package/plugins/specweave/hooks/user-prompt-submit.sh +0 -2550
  132. package/plugins/specweave/hooks/v2/detectors/lifecycle-detector.sh +0 -87
  133. package/plugins/specweave/hooks/v2/detectors/us-completion-detector.sh +0 -186
  134. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use-analytics.sh +0 -83
  135. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +0 -447
  136. package/plugins/specweave/hooks/v2/dispatchers/pre-tool-use.sh +0 -104
  137. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +0 -270
  138. package/plugins/specweave/hooks/v2/guards/completion-guard.sh +0 -14
  139. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +0 -14
  140. package/plugins/specweave/hooks/v2/guards/increment-existence-guard.sh +0 -240
  141. package/plugins/specweave/hooks/v2/guards/interview-enforcement-guard.sh +0 -171
  142. package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +0 -14
  143. package/plugins/specweave/hooks/v2/guards/skill-chain-enforcement-guard.sh +0 -222
  144. package/plugins/specweave/hooks/v2/guards/spec-template-enforcement-guard.sh +0 -21
  145. package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +0 -14
  146. package/plugins/specweave/hooks/v2/guards/status-completion-guard.sh +0 -84
  147. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +0 -475
  148. package/plugins/specweave/hooks/v2/guards/tdd-enforcement-guard.sh +0 -268
  149. package/plugins/specweave/hooks/v2/handlers/ac-sync-dispatcher.sh +0 -332
  150. package/plugins/specweave/hooks/v2/handlers/ac-validation-handler.sh +0 -50
  151. package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +0 -347
  152. package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +0 -83
  153. package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +0 -268
  154. package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +0 -104
  155. package/plugins/specweave/hooks/v2/handlers/status-line-handler.sh +0 -165
  156. package/plugins/specweave/hooks/v2/handlers/status-update.sh +0 -61
  157. package/plugins/specweave/hooks/v2/handlers/universal-auto-create-dispatcher.sh +0 -270
  158. package/plugins/specweave/hooks/v2/integrations/ado-post-living-docs-update.sh +0 -367
  159. package/plugins/specweave/hooks/v2/integrations/ado-post-task.sh +0 -179
  160. package/plugins/specweave/hooks/v2/integrations/github-auto-create-handler.sh +0 -553
  161. package/plugins/specweave/hooks/v2/integrations/github-post-task.sh +0 -345
  162. package/plugins/specweave/hooks/v2/integrations/jira-post-task.sh +0 -180
  163. package/plugins/specweave/hooks/v2/lib/check-provider-enabled.sh +0 -52
  164. package/plugins/specweave/hooks/v2/queue/enqueue.sh +0 -81
  165. package/plugins/specweave/hooks/v2/session-end.sh +0 -139
  166. package/plugins/specweave/hooks/validate-skill-activations.sh +0 -227
@@ -1,1319 +0,0 @@
1
- #!/bin/bash
2
- # stop-auto.sh - Auto Mode Stop Hook (v4.0 - Quality Gates & Auto-Close)
3
- #
4
- # PHILOSOPHY: Smart completion detection with quality gates:
5
- # 1. Is this a SpecWeave project with auto enabled?
6
- # 2. Are there active increments?
7
- # 3. For each active increment:
8
- # a. Are all tasks marked complete in tasks.md?
9
- # b. If TDD mode: Do tests pass?
10
- # c. Are quality gates satisfied (ACs, coverage)?
11
- # d. If YES to all → AUTO-CLOSE (triggers external sync!)
12
- # e. If NO → Block with specific reason
13
- #
14
- # v4.0 CRITICAL FIX:
15
- # - Actually validates completion, not just metadata status
16
- # - Runs tests when TDD/requireTests mode is on
17
- # - Auto-closes increments that pass validation → triggers GitHub sync!
18
- # - Stop hook now has TEETH, not just a message
19
- #
20
- # Configuration is read from .specweave/config.json:
21
- # - auto.enabled: Master switch (default: true)
22
- # - auto.requireTests: Run tests before auto-close (default: false)
23
- # - testing.defaultTestMode: "tdd" enables strict test enforcement
24
- # - auto.skipQualityGates: Skip validation (DANGEROUS, default: false)
25
- #
26
- # This implements SpecWeave's autonomous execution pattern with real validation.
27
-
28
- set +e # Don't exit on errors
29
-
30
- # Capture start time for duration tracking (macOS doesn't support %N, fallback to seconds only)
31
- if [[ "$OSTYPE" == "darwin"* ]]; then
32
- _START_TIME_MS=$(($(date +%s) * 1000))
33
- else
34
- _START_TIME_MS=$(($(date +%s) * 1000 + $(date +%N 2>/dev/null | cut -c1-3 || echo "0")))
35
- fi
36
-
37
- # Read stdin (Claude Code passes context here)
38
- cat > /dev/null # Consume stdin (unused by this hook)
39
- PROJECT_ROOT="${PROJECT_ROOT:-$(pwd)}"
40
-
41
- # ============================================================================
42
- # SOURCE STRUCTURED DECISION LOGGING
43
- # ============================================================================
44
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
45
- if [ -f "$SCRIPT_DIR/log-decision.sh" ]; then
46
- source "$SCRIPT_DIR/log-decision.sh"
47
- fi
48
-
49
- # Helper to calculate duration in ms (macOS-compatible)
50
- _get_duration_ms() {
51
- local end_time_ms
52
- if [[ "$OSTYPE" == "darwin"* ]]; then
53
- end_time_ms=$(($(date +%s) * 1000))
54
- else
55
- end_time_ms=$(($(date +%s) * 1000 + $(date +%N 2>/dev/null | cut -c1-3 || echo "0")))
56
- fi
57
- echo $((end_time_ms - _START_TIME_MS))
58
- }
59
-
60
- # ============================================================================
61
- # LOGGING
62
- # ============================================================================
63
- LOGS_DIR="$PROJECT_ROOT/.specweave/logs"
64
- mkdir -p "$LOGS_DIR" 2>/dev/null
65
- LOG_FILE="$LOGS_DIR/stop-auto.log"
66
-
67
- log() {
68
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $1" >> "$LOG_FILE" 2>/dev/null
69
- if [ "${SPECWEAVE_DEBUG_HOOKS:-0}" = "1" ]; then
70
- echo -e "\033[36m[stop-auto]\033[0m $1" >&2
71
- fi
72
- }
73
-
74
- # ============================================================================
75
- # SILENT APPROVE - Normal sessions get NO output
76
- # ============================================================================
77
- silent_approve() {
78
- local reason="$1"
79
- local reason_code="${2:-session_inactive}"
80
- local context_json="${3:-"{}"}"
81
-
82
- log "APPROVE: $reason"
83
-
84
- # Log structured decision if log_decision function is available
85
- if type log_decision &>/dev/null; then
86
- log_decision "stop-auto" "approve" "$reason_code" "$reason" "$context_json" "$(_get_duration_ms)"
87
- fi
88
-
89
- echo '{"decision":"approve"}'
90
- exit 0
91
- }
92
-
93
- # ============================================================================
94
- # LOUD APPROVE - Session completions need user notification
95
- # Used when work completes, increments close, or session ends successfully
96
- # ============================================================================
97
- loud_approve() {
98
- local reason="$1"
99
- local reason_code="${2:-session_complete}"
100
- local context_json="${3:-"{}"}"
101
- local message="$4"
102
-
103
- log "APPROVE (loud): $reason"
104
-
105
- # Log structured decision if log_decision function is available
106
- if type log_decision &>/dev/null; then
107
- log_decision "stop-auto" "approve" "$reason_code" "$reason" "$context_json" "$(_get_duration_ms)"
108
- fi
109
-
110
- jq -n \
111
- --arg decision "approve" \
112
- --arg reason "$reason" \
113
- --arg msg "$message" \
114
- '{decision: $decision, reason: $reason, systemMessage: $msg}'
115
- exit 0
116
- }
117
-
118
- # ============================================================================
119
- # QUICK EXITS - Not a SpecWeave project
120
- # ============================================================================
121
-
122
- SPECWEAVE_DIR="$PROJECT_ROOT/.specweave"
123
- INCREMENTS_DIR="$SPECWEAVE_DIR/increments"
124
- CONFIG_FILE="$SPECWEAVE_DIR/config.json"
125
-
126
- # Not a SpecWeave project - silent approve
127
- [ ! -d "$SPECWEAVE_DIR" ] && silent_approve "Not a SpecWeave project" "not_specweave_project" "{}"
128
- [ ! -d "$INCREMENTS_DIR" ] && silent_approve "No increments directory" "no_increments_dir" "{}"
129
-
130
- # ============================================================================
131
- # STATE DIRECTORY (MUST be defined BEFORE AUTO_SESSION_FILE check)
132
- # ============================================================================
133
-
134
- STATE_DIR="$SPECWEAVE_DIR/state"
135
-
136
- # ============================================================================
137
- # READ PROJECT CONFIG
138
- # ============================================================================
139
-
140
- AUTO_ENABLED="true"
141
- REQUIRE_TESTS="false"
142
- REQUIRE_VALIDATION="true"
143
- REQUIRE_JUDGE_LLM="false"
144
- REQUIRE_LLM_EVAL="false" # NEW: LLM-based completion evaluation
145
- MAX_TURNS="20" # HARD STOP: Total turns in session (NEVER resets during session)
146
- MAX_RETRIES="20" # Stuck detection: Retries on same work (resets when work changes)
147
- TDD_MODE="false"
148
- SKIP_QUALITY_GATES="false"
149
- TEST_COMMAND=""
150
- MAX_SESSION_AGE="7200" # Stale session timeout (default 2 hours)
151
-
152
- if [ -f "$CONFIG_FILE" ]; then
153
- AUTO_ENABLED=$(jq -r '.auto.enabled // true' "$CONFIG_FILE" 2>/dev/null || echo "true")
154
- REQUIRE_TESTS=$(jq -r '.auto.requireTests // false' "$CONFIG_FILE" 2>/dev/null || echo "false")
155
- REQUIRE_VALIDATION=$(jq -r '.auto.requireValidation // true' "$CONFIG_FILE" 2>/dev/null || echo "true")
156
- REQUIRE_JUDGE_LLM=$(jq -r '.auto.requireJudgeLLM // false' "$CONFIG_FILE" 2>/dev/null || echo "false")
157
- REQUIRE_LLM_EVAL=$(jq -r '.auto.requireLLMEval // false' "$CONFIG_FILE" 2>/dev/null || echo "false")
158
- # maxTurns = HARD STOP (total turns in session, NEVER resets) - default 20
159
- MAX_TURNS=$(jq -r '.auto.maxTurns // 20' "$CONFIG_FILE" 2>/dev/null || echo "20")
160
- # maxRetries = stuck detection (resets when increments change) - default 20
161
- MAX_RETRIES=$(jq -r '.auto.maxRetries // 20' "$CONFIG_FILE" 2>/dev/null || echo "20")
162
- TEST_COMMAND=$(jq -r '.auto.testCommand // ""' "$CONFIG_FILE" 2>/dev/null || echo "")
163
- SKIP_QUALITY_GATES=$(jq -r '.auto.skipQualityGates // false' "$CONFIG_FILE" 2>/dev/null || echo "false")
164
- TDD_MODE_CONFIG=$(jq -r '.testing.defaultTestMode // "standard"' "$CONFIG_FILE" 2>/dev/null || echo "standard")
165
- [ "$TDD_MODE_CONFIG" = "tdd" ] || [ "$TDD_MODE_CONFIG" = "TDD" ] && TDD_MODE="true"
166
- # maxSessionAge = stale session timeout in seconds (default 2 hours = 7200s)
167
- MAX_SESSION_AGE=$(jq -r '.auto.maxSessionAge // 7200' "$CONFIG_FILE" 2>/dev/null || echo "7200")
168
- fi
169
-
170
- # Auto mode disabled in config - silent approve
171
- [ "$AUTO_ENABLED" != "true" ] && silent_approve "Auto mode disabled in config" "auto_disabled" "{}"
172
-
173
- # ============================================================================
174
- # CHECK FOR AUTO MODE SESSION - Only block if explicitly activated
175
- # Auto mode is SESSION-SCOPED: if Claude Code session ends, auto mode ends.
176
- # ============================================================================
177
-
178
- AUTO_SESSION_FILE="$STATE_DIR/auto-mode.json"
179
-
180
- # If no auto-mode.json exists, auto mode was never started this session
181
- if [ ! -f "$AUTO_SESSION_FILE" ]; then
182
- silent_approve "Auto mode not activated (no session file)" "session_inactive" '{"sessionActive":false}'
183
- fi
184
-
185
- # STALENESS CHECK: If session file is older than 30 minutes, it's stale
186
- # (A real auto mode session would have activity within 30 minutes)
187
- FILE_MTIME=$(stat -f%m "$AUTO_SESSION_FILE" 2>/dev/null || stat -c%Y "$AUTO_SESSION_FILE" 2>/dev/null || echo "0")
188
- CURRENT_TIME=$(date +%s)
189
- SESSION_AGE=$((CURRENT_TIME - FILE_MTIME))
190
- # MAX_SESSION_AGE loaded from config above (default: 7200s = 2 hours)
191
-
192
- if [ "$SESSION_AGE" -gt "$MAX_SESSION_AGE" ]; then
193
- log "STALE SESSION DETECTED: Session file is ${SESSION_AGE}s old (max: ${MAX_SESSION_AGE}s)"
194
- # Clean up stale session and ALL state files
195
- rm -f "$AUTO_SESSION_FILE" 2>/dev/null
196
- rm -f "$STATE_DIR/.stop-auto-dedup" 2>/dev/null
197
- rm -f "$STATE_DIR/.stop-auto-retry" 2>/dev/null
198
- rm -f "$STATE_DIR/.stop-auto-turns" 2>/dev/null # Also clear turn counter
199
- silent_approve "Stale auto-mode session cleared (inactive for ${SESSION_AGE}s)" "session_stale" "$(jq -n --argjson age "$SESSION_AGE" --argjson maxAge "$MAX_SESSION_AGE" '{sessionAge:$age,maxSessionAge:$maxAge}')"
200
- fi
201
-
202
- # Check if session is actually active
203
- AUTO_SESSION_ACTIVE=$(jq -r '.active // false' "$AUTO_SESSION_FILE" 2>/dev/null || echo "false")
204
- if [ "$AUTO_SESSION_ACTIVE" != "true" ]; then
205
- silent_approve "Auto mode session not active" "session_inactive" '{"sessionActive":false}'
206
- fi
207
-
208
- # Update session file mtime to keep it fresh (touch it)
209
- touch "$AUTO_SESSION_FILE" 2>/dev/null
210
-
211
- # ============================================================================
212
- # CHECK FOR CUSTOM SUCCESS CRITERIA (auto-enables LLM eval)
213
- # ============================================================================
214
-
215
- HAS_CUSTOM_CRITERIA="false"
216
- LLM_EVAL_MODEL="opus" # Default model for LLM evaluation (ultrathink)
217
- USER_GOAL=""
218
- SUCCESS_SUMMARY=""
219
-
220
- # Read success criteria from auto-mode.json
221
- CRITERIA_COUNT=$(jq -r '.successCriteria | length // 0' "$AUTO_SESSION_FILE" 2>/dev/null || echo "0")
222
- if [ "$CRITERIA_COUNT" -gt 2 ]; then
223
- # More than default criteria (tasks_complete + acs_satisfied) = custom criteria
224
- HAS_CUSTOM_CRITERIA="true"
225
- REQUIRE_LLM_EVAL="true" # Auto-enable LLM eval when custom criteria exist
226
- log "Custom success criteria detected ($CRITERIA_COUNT), enabling LLM evaluation"
227
- fi
228
-
229
- # Check if any criterion has type=llm_evaluate
230
- HAS_LLM_CRITERION=$(jq -r '.successCriteria[]? | select(.type == "llm_evaluate") | .type' "$AUTO_SESSION_FILE" 2>/dev/null | head -1)
231
- if [ -n "$HAS_LLM_CRITERION" ]; then
232
- REQUIRE_LLM_EVAL="true"
233
- log "LLM evaluation criterion found, enabling LLM evaluation"
234
- fi
235
-
236
- # Read user goal and success summary for logging
237
- USER_GOAL=$(jq -r '.userGoal // ""' "$AUTO_SESSION_FILE" 2>/dev/null || echo "")
238
- SUCCESS_SUMMARY=$(jq -r '.successSummary // "All tasks and acceptance criteria complete"' "$AUTO_SESSION_FILE" 2>/dev/null)
239
-
240
- log "Config: TDD=$TDD_MODE, RequireTests=$REQUIRE_TESTS, RequireValidation=$REQUIRE_VALIDATION, RequireJudgeLLM=$REQUIRE_JUDGE_LLM, RequireLLMEval=$REQUIRE_LLM_EVAL, MaxTurns=$MAX_TURNS, MaxRetries=$MAX_RETRIES"
241
- log "Auto mode session active: $AUTO_SESSION_ACTIVE"
242
- log "User goal: $USER_GOAL"
243
- log "Success summary: $SUCCESS_SUMMARY"
244
-
245
- # ============================================================================
246
- # TURN COUNTER - HARD STOP after maxTurns (NEVER resets during session)
247
- # This is the PRIMARY limit on session length. Unlike retry counter which
248
- # resets when increments change, this counter NEVER resets during a session.
249
- # ============================================================================
250
-
251
- TURN_FILE="$STATE_DIR/.stop-auto-turns"
252
- CURRENT_TURN=1
253
-
254
- # Read and increment turn counter (atomic operation)
255
- if [ -f "$TURN_FILE" ]; then
256
- STORED_TURN=$(cat "$TURN_FILE" 2>/dev/null || echo "0")
257
- # Validate it's a number
258
- if [[ "$STORED_TURN" =~ ^[0-9]+$ ]]; then
259
- CURRENT_TURN=$((STORED_TURN + 1))
260
- fi
261
- fi
262
-
263
- # Write new turn count (atomic via temp file)
264
- echo "$CURRENT_TURN" > "$TURN_FILE.tmp" 2>/dev/null && mv "$TURN_FILE.tmp" "$TURN_FILE" 2>/dev/null
265
-
266
- log "Turn counter: $CURRENT_TURN / $MAX_TURNS"
267
-
268
- # HARD STOP: If turn count reaches limit, end session immediately
269
- if [ "$CURRENT_TURN" -ge "$MAX_TURNS" ]; then
270
- log "HARD STOP: Turn limit reached ($CURRENT_TURN >= $MAX_TURNS)"
271
-
272
- # Clean up all state files
273
- rm -f "$TURN_FILE" 2>/dev/null
274
- rm -f "$STATE_DIR/.stop-auto-dedup" 2>/dev/null
275
- rm -f "$STATE_DIR/.stop-auto-retry" 2>/dev/null
276
- rm -f "$AUTO_SESSION_FILE" 2>/dev/null
277
-
278
- # Get active increments for the message
279
- ACTIVE_INCS=$(find "$INCREMENTS_DIR" -maxdepth 2 -name "metadata.json" \
280
- -exec grep -l '"status"[[:space:]]*:[[:space:]]*"active\|"status"[[:space:]]*:[[:space:]]*"in-progress' {} \; 2>/dev/null \
281
- | sed 's|.*/\([^/]*\)/metadata.json|\1|' | sort | tr '\n' ', ' | sed 's/,$//')
282
-
283
- TURN_LIMIT_MSG="
284
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
285
- 🛑 SESSION TURN LIMIT REACHED ($CURRENT_TURN/$MAX_TURNS)
286
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
287
-
288
- The auto mode session has reached its maximum turn limit.
289
- This is a safety mechanism to prevent runaway sessions.
290
-
291
- 📋 REMAINING WORK:
292
- • Active increment(s): ${ACTIVE_INCS:-none}
293
-
294
- 🔧 OPTIONS:
295
- 1. Start a new auto session: /sw:auto
296
- 2. Continue manually: /sw:do
297
- 3. Check status: /sw:progress
298
-
299
- 💡 To increase turn limit, set in .specweave/config.json:
300
- { \"auto\": { \"maxTurns\": 100 } }
301
-
302
- Current limit: $MAX_TURNS turns
303
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
304
-
305
- # Log structured decision for turn limit
306
- if type log_decision &>/dev/null; then
307
- _turn_context=$(jq -n \
308
- --argjson turnCurrent "$CURRENT_TURN" \
309
- --argjson turnMax "$MAX_TURNS" \
310
- --arg activeIncs "${ACTIVE_INCS:-none}" \
311
- '{
312
- sessionActive: true,
313
- turn: {current: $turnCurrent, max: $turnMax},
314
- increments: {active: ($activeIncs | split(", "))}
315
- }')
316
- log_decision "stop-auto" "approve" "turn_limit" "Turn limit reached: $CURRENT_TURN/$MAX_TURNS turns" "$_turn_context" "$(_get_duration_ms)"
317
- fi
318
-
319
- jq -n \
320
- --arg decision "approve" \
321
- --arg reason "Turn limit reached: $CURRENT_TURN/$MAX_TURNS turns" \
322
- --arg msg "$TURN_LIMIT_MSG" \
323
- '{decision: $decision, reason: $reason, systemMessage: $msg}'
324
- exit 0
325
- fi
326
-
327
- # ============================================================================
328
- # DEDUPLICATION - Prevent feedback loops (Claude Code UI bug workaround)
329
- # ============================================================================
330
-
331
- DEDUP_FILE="$STATE_DIR/.stop-auto-dedup"
332
- DEDUP_WINDOW="${SPECWEAVE_STOP_HOOK_DEDUP:-5}"
333
- NOW=$(date +%s)
334
-
335
- if [ -f "$DEDUP_FILE" ]; then
336
- LAST_FIRE=$(cat "$DEDUP_FILE" 2>/dev/null || echo "0")
337
- ELAPSED=$((NOW - LAST_FIRE))
338
- [ "$ELAPSED" -gt 3600 ] && rm -f "$DEDUP_FILE" 2>/dev/null
339
- [ "$ELAPSED" -lt "$DEDUP_WINDOW" ] && silent_approve "Deduplicated (${ELAPSED}s < ${DEDUP_WINDOW}s)" "deduplicated" "$(jq -n --argjson elapsed "$ELAPSED" --argjson window "$DEDUP_WINDOW" '{elapsed:$elapsed,dedupWindow:$window}')"
340
- fi
341
-
342
- mkdir -p "$STATE_DIR" 2>/dev/null
343
- echo "$NOW" > "$DEDUP_FILE" 2>/dev/null
344
-
345
- # ============================================================================
346
- # BLOCK RETRY COUNTER - Track how many times we block on same increments
347
- # ============================================================================
348
-
349
- RETRY_FILE="$STATE_DIR/.stop-auto-retry"
350
- RETRY_COUNT=0
351
- MAX_RETRIES_BEFORE_ESCALATE="$MAX_RETRIES" # From config (default: 20)
352
-
353
- # Read current retry state
354
- if [ -f "$RETRY_FILE" ]; then
355
- RETRY_COUNT=$(jq -r '.count // 0' "$RETRY_FILE" 2>/dev/null || echo "0")
356
- RETRY_INCS=$(jq -r '.increments // ""' "$RETRY_FILE" 2>/dev/null || echo "")
357
- fi
358
-
359
- # Function to update retry count (called when blocking)
360
- # Also tracks failure reasons for reflection
361
- update_retry_counter() {
362
- local incs="$1"
363
- local reason="$2"
364
- local new_count=$((RETRY_COUNT + 1))
365
-
366
- # Reset if different increments
367
- if [ "$RETRY_INCS" != "$incs" ]; then
368
- new_count=1
369
- fi
370
-
371
- # Get previous reasons for reflection
372
- local prev_reasons=""
373
- if [ -f "$RETRY_FILE" ]; then
374
- prev_reasons=$(jq -r '.reasons // []' "$RETRY_FILE" 2>/dev/null || echo "[]")
375
- fi
376
-
377
- # Build updated retry state with history
378
- jq -n \
379
- --argjson count "$new_count" \
380
- --arg increments "$incs" \
381
- --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
382
- --arg currentReason "$reason" \
383
- --argjson prevReasons "$prev_reasons" \
384
- '{
385
- count: $count,
386
- increments: $increments,
387
- lastUpdate: $timestamp,
388
- currentReason: $currentReason,
389
- reasons: ([$currentReason] + ($prevReasons | .[0:4]))
390
- }' \
391
- > "$RETRY_FILE" 2>/dev/null
392
-
393
- echo "$new_count"
394
- }
395
-
396
- # Function to get previous failure reasons for reflection
397
- get_failure_history() {
398
- if [ -f "$RETRY_FILE" ]; then
399
- jq -r '.reasons // [] | .[] | " - " + .' "$RETRY_FILE" 2>/dev/null || echo ""
400
- fi
401
- }
402
-
403
- # Function to clear retry counter (called when work completes)
404
- clear_retry_counter() {
405
- rm -f "$RETRY_FILE" 2>/dev/null
406
- }
407
-
408
- # Function to clear auto-mode session (called when all work completes)
409
- clear_auto_session() {
410
- rm -f "$AUTO_SESSION_FILE" 2>/dev/null
411
- rm -f "$TURN_FILE" 2>/dev/null # Also clear turn counter on session end
412
- log "Auto-mode session cleared (including turn counter)"
413
- }
414
-
415
- # ============================================================================
416
- # CHECK FOR PARALLEL SESSION
417
- # ============================================================================
418
-
419
- PARALLEL_SESSION="$STATE_DIR/parallel/session.json"
420
-
421
- if [ -f "$PARALLEL_SESSION" ]; then
422
- SESSION_STATUS=$(jq -r '.status // "unknown"' "$PARALLEL_SESSION" 2>/dev/null || echo "unknown")
423
-
424
- if [ "$SESSION_STATUS" = "active" ]; then
425
- PENDING_AGENTS=$(jq '[.agents[] | select(.status != "completed" and .status != "failed" and .status != "cancelled")] | length' "$PARALLEL_SESSION" 2>/dev/null || echo "0")
426
-
427
- if [ "$PENDING_AGENTS" -gt 0 ]; then
428
- AGENT_LIST=$(jq -r '[.agents[] | select(.status != "completed" and .status != "failed" and .status != "cancelled") | "\(.domain):\(.status)"] | join(", ")' "$PARALLEL_SESSION" 2>/dev/null || echo "unknown")
429
-
430
- MSG="🔄 $PENDING_AGENTS parallel agent(s) running: $AGENT_LIST → wait for completion"
431
- log "BLOCK: Parallel agents running: $AGENT_LIST"
432
-
433
- jq -n \
434
- --arg decision "block" \
435
- --arg reason "Parallel agents still running" \
436
- --arg msg "$MSG" \
437
- '{decision: $decision, reason: $reason, systemMessage: $msg}'
438
- exit 0
439
- fi
440
- fi
441
- fi
442
-
443
- # ============================================================================
444
- # HELPER: Count pending tasks in tasks.md
445
- # Returns: number of tasks with "**Status**: [ ] pending"
446
- # ============================================================================
447
- count_pending_tasks() {
448
- local inc_id="$1"
449
- local tasks_file="$INCREMENTS_DIR/$inc_id/tasks.md"
450
-
451
- if [ ! -f "$tasks_file" ]; then
452
- echo "0"
453
- return
454
- fi
455
-
456
- # Count tasks with **Status**: [ ] pending (case insensitive)
457
- grep -ciE '\*\*Status\*\*:\s*\[\s*\]\s*pending' "$tasks_file" 2>/dev/null || echo "0"
458
- }
459
-
460
- # ============================================================================
461
- # HELPER: Check if increment has all ACs completed
462
- # Returns: 0 if all complete, count of open ACs otherwise
463
- # ============================================================================
464
- count_open_acs() {
465
- local inc_id="$1"
466
- local spec_file="$INCREMENTS_DIR/$inc_id/spec.md"
467
-
468
- if [ ! -f "$spec_file" ]; then
469
- echo "0"
470
- return
471
- fi
472
-
473
- # Count unchecked ACs: - [ ] **AC-
474
- # Note: grep -c returns exit code 1 when count is 0, so use a temp var
475
- local count
476
- count=$(grep -c '^- \[ \] \*\*AC-' "$spec_file" 2>/dev/null) || count=0
477
- echo "$count"
478
- }
479
-
480
- # ============================================================================
481
- # HELPER: Run tests and return result
482
- # Returns: "pass" or "fail:reason"
483
- # ============================================================================
484
- run_tests() {
485
- local inc_id="$1"
486
-
487
- # Try to find and run tests
488
- # Priority: npm test, yarn test, bun test, pytest, go test
489
-
490
- if [ -f "$PROJECT_ROOT/package.json" ]; then
491
- # Check if test script exists
492
- local has_test=$(jq -r '.scripts.test // ""' "$PROJECT_ROOT/package.json" 2>/dev/null)
493
- if [ -n "$has_test" ] && [ "$has_test" != "null" ]; then
494
- log "Running: npm test"
495
- # Run tests with timeout, capture exit code
496
- cd "$PROJECT_ROOT" && timeout 300 npm test >/dev/null 2>&1
497
- local exit_code=$?
498
- if [ $exit_code -eq 0 ]; then
499
- echo "pass"
500
- else
501
- echo "fail:npm test failed (exit $exit_code)"
502
- fi
503
- return
504
- fi
505
- fi
506
-
507
- # Check for Python tests
508
- if [ -f "$PROJECT_ROOT/pytest.ini" ] || [ -d "$PROJECT_ROOT/tests" ]; then
509
- if command -v pytest >/dev/null 2>&1; then
510
- log "Running: pytest"
511
- cd "$PROJECT_ROOT" && timeout 300 pytest -q >/dev/null 2>&1
512
- local exit_code=$?
513
- if [ $exit_code -eq 0 ]; then
514
- echo "pass"
515
- else
516
- echo "fail:pytest failed (exit $exit_code)"
517
- fi
518
- return
519
- fi
520
- fi
521
-
522
- # Check for Go tests
523
- if [ -f "$PROJECT_ROOT/go.mod" ]; then
524
- if command -v go >/dev/null 2>&1; then
525
- log "Running: go test"
526
- cd "$PROJECT_ROOT" && timeout 300 go test ./... >/dev/null 2>&1
527
- local exit_code=$?
528
- if [ $exit_code -eq 0 ]; then
529
- echo "pass"
530
- else
531
- echo "fail:go test failed (exit $exit_code)"
532
- fi
533
- return
534
- fi
535
- fi
536
-
537
- # No tests found - pass by default (user should configure tests)
538
- echo "pass:no-tests-configured"
539
- }
540
-
541
- # ============================================================================
542
- # HELPER: Validate increment completion
543
- # Returns: "valid" or "invalid:reason"
544
- # ============================================================================
545
- validate_increment() {
546
- local inc_id="$1"
547
-
548
- # Check pending tasks
549
- local pending=$(count_pending_tasks "$inc_id")
550
- if [ "$pending" -gt 0 ]; then
551
- echo "invalid:$pending tasks still pending"
552
- return
553
- fi
554
-
555
- # Check open ACs
556
- local open_acs=$(count_open_acs "$inc_id")
557
- if [ "$open_acs" -gt 0 ]; then
558
- echo "invalid:$open_acs acceptance criteria still open"
559
- return
560
- fi
561
-
562
- # If TDD mode or requireTests, run tests
563
- if [ "$TDD_MODE" = "true" ] || [ "$REQUIRE_TESTS" = "true" ]; then
564
- local test_result=$(run_tests "$inc_id")
565
- if [[ "$test_result" == fail:* ]]; then
566
- local reason="${test_result#fail:}"
567
- echo "invalid:Tests failed - $reason"
568
- return
569
- fi
570
- log "Tests passed for $inc_id"
571
- fi
572
-
573
- # If requireLLMEval, run LLM-based completion evaluation
574
- if [ "$REQUIRE_LLM_EVAL" = "true" ]; then
575
- log "Running LLM completion evaluation for $inc_id..."
576
-
577
- # Check if specweave CLI is available
578
- if command -v specweave >/dev/null 2>&1; then
579
- local eval_result
580
- eval_result=$(cd "$PROJECT_ROOT" && timeout 60 specweave evaluate-completion "$inc_id" --model opus --silent 2>/dev/null)
581
- local eval_exit=$?
582
-
583
- if [ "$eval_exit" -eq 0 ]; then
584
- log "LLM evaluation: COMPLETE for $inc_id"
585
- else
586
- # Parse the reason from JSON if possible
587
- local eval_reason=""
588
- if [ -n "$eval_result" ]; then
589
- eval_reason=$(echo "$eval_result" | jq -r '.overallReason // "Unknown reason"' 2>/dev/null || echo "Unknown reason")
590
- else
591
- eval_reason="LLM evaluation returned incomplete"
592
- fi
593
- log "LLM evaluation: NOT COMPLETE - $eval_reason"
594
- echo "invalid:LLM evaluation incomplete - $eval_reason"
595
- return
596
- fi
597
- else
598
- log "WARN: specweave CLI not available, skipping LLM evaluation"
599
- fi
600
- fi
601
-
602
- echo "valid"
603
- }
604
-
605
- # ============================================================================
606
- # HELPER: Auto-close increment (update status to completed)
607
- # This triggers StatusChangeSyncTrigger → external sync!
608
- # ============================================================================
609
- auto_close_increment() {
610
- local inc_id="$1"
611
- local metadata_file="$INCREMENTS_DIR/$inc_id/metadata.json"
612
-
613
- if [ ! -f "$metadata_file" ]; then
614
- log "ERROR: metadata.json not found for $inc_id"
615
- return 1
616
- fi
617
-
618
- log "AUTO-CLOSE: Updating $inc_id to completed"
619
-
620
- # Use specweave CLI if available (triggers proper hooks and sync)
621
- if command -v specweave >/dev/null 2>&1; then
622
- cd "$PROJECT_ROOT" && specweave complete "$inc_id" --yes --silent 2>/dev/null
623
- if [ $? -eq 0 ]; then
624
- log "AUTO-CLOSE: specweave complete succeeded for $inc_id"
625
- return 0
626
- fi
627
- log "WARN: specweave complete failed, falling back to direct update"
628
- fi
629
-
630
- # Fallback: Direct metadata update (triggers StatusChangeSyncTrigger via node)
631
- # This is less ideal but ensures we don't get stuck
632
- local now=$(date -u +%Y-%m-%dT%H:%M:%SZ)
633
- local temp_file=$(mktemp)
634
-
635
- jq --arg now "$now" '.status = "completed" | .completedAt = $now | .updated = $now' \
636
- "$metadata_file" > "$temp_file" && mv "$temp_file" "$metadata_file"
637
-
638
- if [ $? -eq 0 ]; then
639
- log "AUTO-CLOSE: Direct metadata update succeeded for $inc_id"
640
-
641
- # Trigger sync via node helper if available
642
- local sync_helper="$PROJECT_ROOT/node_modules/specweave/dist/hooks/trigger-sync.js"
643
- if [ -f "$sync_helper" ]; then
644
- node "$sync_helper" "$inc_id" 2>/dev/null &
645
- fi
646
- return 0
647
- else
648
- log "ERROR: Failed to update metadata for $inc_id"
649
- return 1
650
- fi
651
- }
652
-
653
- # ============================================================================
654
- # MAIN: Find active increments and validate each
655
- # ============================================================================
656
-
657
- # Find all active increments
658
- ACTIVE_METADATA_FILES=$(find "$INCREMENTS_DIR" -maxdepth 2 -name "metadata.json" \
659
- -exec grep -l '"status"[[:space:]]*:[[:space:]]*"active\|"status"[[:space:]]*:[[:space:]]*"in-progress' {} \; 2>/dev/null)
660
-
661
- # Arrays to track status
662
- INCOMPLETE_INCS=""
663
- READY_TO_CLOSE=""
664
- VALIDATION_ERRORS=""
665
-
666
- # Cache for first incomplete increment (avoids redundant calculation later)
667
- FIRST_INC_PENDING_CACHED=""
668
- FIRST_INC_ACS_CACHED=""
669
- FIRST_INC_CACHED=""
670
-
671
- for meta_file in $ACTIVE_METADATA_FILES; do
672
- # Extract increment ID from path
673
- inc_id=$(echo "$meta_file" | sed 's|.*/\([^/]*\)/metadata.json|\1|')
674
-
675
- log "Checking increment: $inc_id"
676
-
677
- if [ "$SKIP_QUALITY_GATES" = "true" ]; then
678
- # Skip validation, just check task status
679
- pending=$(count_pending_tasks "$inc_id")
680
- open_acs=$(count_open_acs "$inc_id")
681
- if [ "$pending" -eq 0 ]; then
682
- log "SKIP_QUALITY_GATES: $inc_id has no pending tasks, marking ready"
683
- READY_TO_CLOSE="$READY_TO_CLOSE $inc_id"
684
- else
685
- INCOMPLETE_INCS="$INCOMPLETE_INCS $inc_id"
686
- VALIDATION_ERRORS="$VALIDATION_ERRORS|$inc_id:$pending tasks pending"
687
- # Cache first incomplete increment's stats
688
- if [ -z "$FIRST_INC_CACHED" ]; then
689
- FIRST_INC_CACHED="$inc_id"
690
- FIRST_INC_PENDING_CACHED="$pending"
691
- FIRST_INC_ACS_CACHED="$open_acs"
692
- fi
693
- fi
694
- else
695
- # Full validation - get counts first for caching
696
- pending=$(count_pending_tasks "$inc_id")
697
- open_acs=$(count_open_acs "$inc_id")
698
- result=$(validate_increment "$inc_id")
699
-
700
- if [ "$result" = "valid" ]; then
701
- log "VALID: $inc_id ready to close"
702
- READY_TO_CLOSE="$READY_TO_CLOSE $inc_id"
703
- else
704
- reason="${result#invalid:}"
705
- log "INVALID: $inc_id - $reason"
706
- INCOMPLETE_INCS="$INCOMPLETE_INCS $inc_id"
707
- VALIDATION_ERRORS="$VALIDATION_ERRORS|$inc_id:$reason"
708
- # Cache first incomplete increment's stats
709
- if [ -z "$FIRST_INC_CACHED" ]; then
710
- FIRST_INC_CACHED="$inc_id"
711
- FIRST_INC_PENDING_CACHED="$pending"
712
- FIRST_INC_ACS_CACHED="$open_acs"
713
- fi
714
- fi
715
- fi
716
- done
717
-
718
- # ============================================================================
719
- # AUTO-CLOSE: Close increments that passed validation
720
- # This is the KEY fix - actually transition status to trigger sync!
721
- # ============================================================================
722
-
723
- CLOSED_COUNT=0
724
- for inc_id in $READY_TO_CLOSE; do
725
- [ -z "$inc_id" ] && continue
726
-
727
- auto_close_increment "$inc_id"
728
- if [ $? -eq 0 ]; then
729
- CLOSED_COUNT=$((CLOSED_COUNT + 1))
730
- log "CLOSED: $inc_id"
731
- fi
732
- done
733
-
734
- # ============================================================================
735
- # SKILL VALIDATION: Run domain-specific validation if skills were activated
736
- # Only runs in auto mode when skills/agents were used during session
737
- # ============================================================================
738
-
739
- SKILL_ACTIVATIONS_FILE="$STATE_DIR/skill-activations.json"
740
- SKILL_VALIDATION_FAILED="false"
741
- VALIDATION_SUMMARY="" # Track what validations were run
742
-
743
- if [ -f "$SKILL_ACTIVATIONS_FILE" ]; then
744
- # Skills were activated during this session
745
- ACTIVATED_DOMAINS=$(jq -r '.domains | join(", ")' "$SKILL_ACTIVATIONS_FILE" 2>/dev/null || echo "")
746
- ACTIVATION_COUNT=$(jq -r '.activations | length' "$SKILL_ACTIVATIONS_FILE" 2>/dev/null || echo "0")
747
-
748
- if [ -n "$ACTIVATED_DOMAINS" ] && [ "$ACTIVATED_DOMAINS" != "null" ]; then
749
- log "SKILL VALIDATION: Activated domains: $ACTIVATED_DOMAINS"
750
-
751
- # Run domain-specific validation
752
- VALIDATION_SCRIPT="$PROJECT_ROOT/plugins/specweave/hooks/validate-skill-activations.sh"
753
-
754
- # Try installed location first, then package location
755
- if [ ! -f "$VALIDATION_SCRIPT" ]; then
756
- VALIDATION_SCRIPT="$(dirname "$0")/validate-skill-activations.sh"
757
- fi
758
-
759
- if [ -f "$VALIDATION_SCRIPT" ] && [ -x "$VALIDATION_SCRIPT" ]; then
760
- log "Running skill validation: $VALIDATION_SCRIPT"
761
-
762
- SKILL_VALIDATION_START=$(date +%s)
763
- if ! PROJECT_ROOT="$PROJECT_ROOT" "$VALIDATION_SCRIPT" 2>&1 | tee -a "$LOG_FILE"; then
764
- SKILL_VALIDATION_FAILED="true"
765
- SKILL_VALIDATION_END=$(date +%s)
766
- SKILL_VALIDATION_DURATION=$((SKILL_VALIDATION_END - SKILL_VALIDATION_START))
767
- log "SKILL VALIDATION: Failed - blocking completion"
768
- VALIDATION_ERRORS="$VALIDATION_ERRORS|skill_validation:Domain validation failed for: $ACTIVATED_DOMAINS"
769
- VALIDATION_SUMMARY="${VALIDATION_SUMMARY}
770
- ❌ Skill Validation ($ACTIVATED_DOMAINS): FAILED in ${SKILL_VALIDATION_DURATION}s"
771
- else
772
- SKILL_VALIDATION_END=$(date +%s)
773
- SKILL_VALIDATION_DURATION=$((SKILL_VALIDATION_END - SKILL_VALIDATION_START))
774
- log "SKILL VALIDATION: Passed"
775
- VALIDATION_SUMMARY="${VALIDATION_SUMMARY}
776
- ✅ Skill Validation ($ACTIVATED_DOMAINS): PASSED in ${SKILL_VALIDATION_DURATION}s"
777
- fi
778
- else
779
- log "SKILL VALIDATION: Script not found, skipping"
780
- VALIDATION_SUMMARY="${VALIDATION_SUMMARY}
781
- ⏭️ Skill Validation: Skipped (no validator)"
782
- fi
783
- fi
784
- else
785
- # No skills activated - note this in log
786
- log "SKILL VALIDATION: No skills activated this session"
787
- fi
788
-
789
- # Add increment validation summary
790
- if [ -n "$READY_TO_CLOSE" ]; then
791
- for inc in $READY_TO_CLOSE; do
792
- [ -z "$inc" ] && continue
793
- VALIDATION_SUMMARY="${VALIDATION_SUMMARY}
794
- ✅ Increment $inc: All tasks complete, ACs satisfied"
795
- done
796
- fi
797
-
798
- if [ -n "$INCOMPLETE_INCS" ]; then
799
- for inc in $INCOMPLETE_INCS; do
800
- [ -z "$inc" ] && continue
801
- VALIDATION_SUMMARY="${VALIDATION_SUMMARY}
802
- ⏳ Increment $inc: Work remaining"
803
- done
804
- fi
805
-
806
- # Log validation summary
807
- if [ -n "$VALIDATION_SUMMARY" ]; then
808
- log "VALIDATION SUMMARY:$VALIDATION_SUMMARY"
809
- fi
810
-
811
- # ============================================================================
812
- # DECISION: Continue or Complete
813
- # ============================================================================
814
-
815
- # Recount after auto-close
816
- REMAINING_COUNT=$(find "$INCREMENTS_DIR" -maxdepth 2 -name "metadata.json" \
817
- -exec grep -l '"status"[[:space:]]*:[[:space:]]*"active\|"status"[[:space:]]*:[[:space:]]*"in-progress' {} \; 2>/dev/null | wc -l | tr -d ' ')
818
-
819
- if [ "$REMAINING_COUNT" -eq 0 ] && [ "$SKILL_VALIDATION_FAILED" != "true" ]; then
820
- # All increments closed AND skill validation passed - approve exit
821
- rm -f "$DEDUP_FILE" 2>/dev/null
822
- clear_retry_counter
823
- clear_auto_session # Clear session marker - work is done!
824
-
825
- # Build context for logging
826
- _complete_context=$(jq -n \
827
- --argjson turnCurrent "$CURRENT_TURN" \
828
- --argjson turnMax "$MAX_TURNS" \
829
- --argjson closedCount "$CLOSED_COUNT" \
830
- '{
831
- sessionActive: false,
832
- turn: {current: $turnCurrent, max: $turnMax},
833
- closedIncrements: $closedCount
834
- }')
835
-
836
- if [ "$CLOSED_COUNT" -gt 0 ]; then
837
- log "APPROVE: Auto-closed $CLOSED_COUNT increment(s), all work complete"
838
- # Show completion message to user
839
- COMPLETE_MSG="
840
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
841
- ✅ AUTO MODE COMPLETE (Turn $CURRENT_TURN/$MAX_TURNS)
842
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
843
-
844
- 🎉 Auto-closed $CLOSED_COUNT increment(s) successfully!
845
-
846
- 📊 Session Summary:
847
- • Turns used: $CURRENT_TURN
848
- • Increments completed: $CLOSED_COUNT
849
-
850
- 💡 Next steps:
851
- • Start new work: /sw:increment \"feature name\"
852
- • View completed: /sw:status
853
- • Review logs: specweave decision-log --since 1h
854
-
855
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
856
- loud_approve "Auto-closed $CLOSED_COUNT increment(s)" "all_complete" "$_complete_context" "$COMPLETE_MSG"
857
- else
858
- # No active increments - show brief message
859
- NO_WORK_MSG="
860
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
861
- ✅ NO ACTIVE INCREMENTS
862
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
863
-
864
- No active increments to work on. Session ended.
865
-
866
- 💡 Start new work: /sw:increment \"feature name\"
867
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
868
- loud_approve "No active increments" "all_complete" "$_complete_context" "$NO_WORK_MSG"
869
- fi
870
- fi
871
-
872
- # ============================================================================
873
- # KEY FIX: APPROVE if all tasks complete even if increments still "active"
874
- # This handles: auto-close failed, user prefers manual close, etc.
875
- # ============================================================================
876
-
877
- # Count how many active increments have incomplete work
878
- INCOMPLETE_COUNT=$(echo "$INCOMPLETE_INCS" | tr -s ' ' | sed 's/^ //' | grep -v '^$' | wc -w | tr -d ' ')
879
-
880
- if [ "$INCOMPLETE_COUNT" -eq 0 ] && [ "$SKILL_VALIDATION_FAILED" != "true" ]; then
881
- # All active increments have complete tasks/ACs - approve even if still "active" status
882
- rm -f "$DEDUP_FILE" 2>/dev/null
883
- clear_retry_counter
884
- clear_auto_session # Clear session marker - work is done!
885
-
886
- log "APPROVE: All tasks complete in active increments (manual close pending)"
887
-
888
- # Log structured decision
889
- if type log_decision &>/dev/null; then
890
- _work_complete_context=$(jq -n \
891
- --argjson turnCurrent "$CURRENT_TURN" \
892
- --argjson turnMax "$MAX_TURNS" \
893
- --arg readyToClose "$READY_TO_CLOSE" \
894
- '{sessionActive: false, turn: {current: $turnCurrent, max: $turnMax}, increments: {active: [], readyToClose: ($readyToClose | split(" ") | map(select(length > 0)))}}')
895
- log_decision "stop-auto" "approve" "work_complete" "All tasks complete - increments ready for manual close" "$_work_complete_context" "$(_get_duration_ms)"
896
- fi
897
-
898
- # Show message about manual close needed
899
- CLOSE_MSG="
900
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
901
- ✅ ALL TASKS COMPLETE - Session can end
902
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
903
-
904
- All work is done! Increments need manual closing:
905
-
906
- $READY_TO_CLOSE
907
-
908
- To close: /sw:done <increment-id>
909
-
910
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
911
-
912
- jq -n \
913
- --arg decision "approve" \
914
- --arg reason "All tasks complete - increments ready for manual close" \
915
- --arg msg "$CLOSE_MSG" \
916
- '{decision: $decision, reason: $reason, systemMessage: $msg}'
917
- exit 0
918
- fi
919
-
920
- # Handle skill validation failure (block even if tasks are done)
921
- if [ "$SKILL_VALIDATION_FAILED" = "true" ]; then
922
- log "BLOCK: Skill validation failed - fix issues before completion"
923
- fi
924
-
925
- # Work remains - show what's blocking
926
- # IMPORTANT: Sort to ensure deterministic order (prevents retry counter resets)
927
- REMAINING_INCS=$(find "$INCREMENTS_DIR" -maxdepth 2 -name "metadata.json" \
928
- -exec grep -l '"status"[[:space:]]*:[[:space:]]*"active\|"status"[[:space:]]*:[[:space:]]*"in-progress' {} \; 2>/dev/null \
929
- | sed 's|.*/\([^/]*\)/metadata.json|\1|' | sort | tr '\n' ', ' | sed 's/,$//')
930
-
931
- # Build reason string for retry tracking
932
- BLOCK_REASON="${VALIDATION_ERRORS:-tasks_or_acs_pending}"
933
-
934
- # Update retry counter with reason
935
- CURRENT_RETRY=$(update_retry_counter "$REMAINING_INCS" "$BLOCK_REASON")
936
- log "Retry count: $CURRENT_RETRY for increments: $REMAINING_INCS"
937
-
938
- # Get first increment ID for commands
939
- FIRST_INC=$(echo "$REMAINING_INCS" | cut -d',' -f1 | tr -d ' ')
940
-
941
- # ============================================================================
942
- # DETECT PROJECT CAPABILITIES (conditional steps)
943
- # ============================================================================
944
-
945
- HAS_TESTS="false"
946
- TEST_CMD=""
947
-
948
- # Check for test capability
949
- if [ -f "$PROJECT_ROOT/package.json" ]; then
950
- TEST_SCRIPT=$(jq -r '.scripts.test // ""' "$PROJECT_ROOT/package.json" 2>/dev/null)
951
- if [ -n "$TEST_SCRIPT" ] && [ "$TEST_SCRIPT" != "null" ]; then
952
- HAS_TESTS="true"
953
- TEST_CMD="npm test"
954
- fi
955
- elif [ -f "$PROJECT_ROOT/pytest.ini" ] || [ -d "$PROJECT_ROOT/tests" ]; then
956
- HAS_TESTS="true"
957
- TEST_CMD="pytest"
958
- elif [ -f "$PROJECT_ROOT/go.mod" ]; then
959
- HAS_TESTS="true"
960
- TEST_CMD="go test ./..."
961
- fi
962
-
963
- # Use configured test command if provided
964
- [ -n "$TEST_COMMAND" ] && TEST_CMD="$TEST_COMMAND" && HAS_TESTS="true"
965
-
966
- # ============================================================================
967
- # BUILD MESSAGE WITH REFLECTION ON PREVIOUS FAILURES
968
- # ============================================================================
969
-
970
- MSG=""
971
-
972
- # Add validation summary at the top if any validations were run
973
- if [ -n "$VALIDATION_SUMMARY" ]; then
974
- MSG="━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
975
- 📊 VALIDATION SUMMARY
976
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
977
- $VALIDATION_SUMMARY
978
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
979
-
980
- "
981
- fi
982
-
983
- # Add closed count if any
984
- if [ "$CLOSED_COUNT" -gt 0 ]; then
985
- MSG="${MSG}✅ Auto-closed $CLOSED_COUNT increment(s)
986
-
987
- "
988
- fi
989
-
990
- # Build header based on retry count
991
- if [ "$CURRENT_RETRY" -ge "$MAX_RETRIES_BEFORE_ESCALATE" ]; then
992
- MSG="${MSG}🚨 STUCK SESSION (attempt $CURRENT_RETRY/$MAX_RETRIES_BEFORE_ESCALATE)
993
-
994
- ⚠️ This session has failed to complete $CURRENT_RETRY times.
995
- Consider: pausing (/sw:pause $FIRST_INC) or abandoning (/sw:abandon $FIRST_INC)
996
-
997
- "
998
- fi
999
-
1000
- MSG="${MSG}🔄 $REMAINING_COUNT increment(s) need work: $REMAINING_INCS
1001
- 📊 Session: turn $CURRENT_TURN/$MAX_TURNS | stuck retry $CURRENT_RETRY/$MAX_RETRIES_BEFORE_ESCALATE"
1002
-
1003
- # Add validation errors if any
1004
- if [ -n "$VALIDATION_ERRORS" ]; then
1005
- error_details=$(echo "$VALIDATION_ERRORS" | tr '|' '\n' | grep -v '^$' | head -5 | while read line; do
1006
- echo " • $line"
1007
- done)
1008
- MSG="$MSG
1009
-
1010
- 📋 Current blocking issues:
1011
- $error_details"
1012
- fi
1013
-
1014
- # ============================================================================
1015
- # REFLECTION: Why did previous attempts fail?
1016
- # ============================================================================
1017
-
1018
- if [ "$CURRENT_RETRY" -gt 1 ]; then
1019
- FAILURE_HISTORY=$(get_failure_history)
1020
- if [ -n "$FAILURE_HISTORY" ]; then
1021
- MSG="$MSG
1022
-
1023
- 🔍 REFLECT: Why previous attempts failed:
1024
- $FAILURE_HISTORY
1025
-
1026
- 💡 THINK: What can you do differently this time?
1027
- • Is there a different approach to fix the issue?
1028
- • Are you stuck in a loop doing the same thing?
1029
- • Should you ask the user for help?"
1030
- fi
1031
- fi
1032
-
1033
- # Add mode indicators
1034
- if [ "$TDD_MODE" = "true" ]; then
1035
- MSG="🔴 TDD MODE | $MSG"
1036
- elif [ "$REQUIRE_TESTS" = "true" ]; then
1037
- MSG="🧪 TEST MODE | $MSG"
1038
- fi
1039
-
1040
- # ============================================================================
1041
- # BUILD CONDITIONAL COMPLETION STEPS
1042
- # ============================================================================
1043
-
1044
- STEP_NUM=1
1045
- STEPS=""
1046
-
1047
- # Step 1: Complete tasks (always required)
1048
- STEPS="${STEPS}
1049
- ${STEP_NUM}️⃣ COMPLETE ALL TASKS
1050
- → Run: /sw:do
1051
- → Mark all tasks [x] completed in tasks.md
1052
- → Mark all ACs [x] completed in spec.md"
1053
- STEP_NUM=$((STEP_NUM + 1))
1054
-
1055
- # Step 2: Run tests (if available and required)
1056
- if [ "$HAS_TESTS" = "true" ] && { [ "$REQUIRE_TESTS" = "true" ] || [ "$TDD_MODE" = "true" ]; }; then
1057
- STEPS="${STEPS}
1058
-
1059
- ${STEP_NUM}️⃣ RUN TESTS (REQUIRED by config)
1060
- → Run: $TEST_CMD
1061
- → ALL tests MUST pass before proceeding
1062
- → If tests fail, FIX them and re-run"
1063
- STEP_NUM=$((STEP_NUM + 1))
1064
- elif [ "$HAS_TESTS" = "true" ]; then
1065
- STEPS="${STEPS}
1066
-
1067
- ${STEP_NUM}️⃣ RUN TESTS (recommended)
1068
- → Run: $TEST_CMD
1069
- → Verify your changes work correctly"
1070
- STEP_NUM=$((STEP_NUM + 1))
1071
- fi
1072
-
1073
- # Step 3: Validate (if required by config)
1074
- if [ "$REQUIRE_VALIDATION" = "true" ]; then
1075
- STEPS="${STEPS}
1076
-
1077
- ${STEP_NUM}️⃣ VALIDATE QUALITY GATES (REQUIRED)
1078
- → Run: /sw:validate $FIRST_INC
1079
- → Review and fix any blocking issues"
1080
- STEP_NUM=$((STEP_NUM + 1))
1081
- fi
1082
-
1083
- # Step 4: Judge LLM (if required by config)
1084
- if [ "$REQUIRE_JUDGE_LLM" = "true" ]; then
1085
- STEPS="${STEPS}
1086
-
1087
- ${STEP_NUM}️⃣ AI QUALITY VERIFICATION (REQUIRED)
1088
- → Run: /sw:judge-llm $FIRST_INC
1089
- → Must pass before closing"
1090
- STEP_NUM=$((STEP_NUM + 1))
1091
- fi
1092
-
1093
- # Final step: Close increment (always required)
1094
- STEPS="${STEPS}
1095
-
1096
- ${STEP_NUM}️⃣ CLOSE INCREMENT (FINAL STEP)
1097
- → Run: /sw:done $FIRST_INC
1098
- → This validates gates and closes properly"
1099
-
1100
- MSG="$MSG
1101
-
1102
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1103
- 📋 COMPLETION STEPS for $FIRST_INC:
1104
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1105
- $STEPS
1106
-
1107
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1108
- 🎯 Execute these steps IN ORDER, then session will complete automatically.
1109
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1110
-
1111
- # ============================================================================
1112
- # CIRCUIT BREAKER: Prevent infinite loops after MAX_RETRIES
1113
- # NOTE: Uses -ge (>=) to trigger AT the limit, not after it
1114
- # ============================================================================
1115
-
1116
- if [ "$CURRENT_RETRY" -ge "$MAX_RETRIES_BEFORE_ESCALATE" ]; then
1117
- log "CIRCUIT BREAKER: Reached $MAX_RETRIES_BEFORE_ESCALATE stuck retries (on same work), approving to break loop"
1118
-
1119
- CIRCUIT_MSG="
1120
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1121
- 🛑 STUCK SESSION BREAKER (retry $CURRENT_RETRY/$MAX_RETRIES_BEFORE_ESCALATE on same work)
1122
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1123
-
1124
- ⚠️ Stop hook has fired $CURRENT_RETRY times on the SAME incomplete work.
1125
- This suggests the session is stuck and not making progress.
1126
-
1127
- 📋 REMAINING WORK:
1128
- • $REMAINING_COUNT active increment(s): $REMAINING_INCS
1129
-
1130
- 🔧 RECOMMENDED ACTIONS:
1131
- 1. Run /sw:status to see what's incomplete
1132
- 2. Run /sw:do to complete remaining tasks
1133
- 3. Run /sw:done $FIRST_INC when ready to close
1134
-
1135
- 💡 To increase stuck retry limit, set in .specweave/config.json:
1136
- { \"auto\": { \"maxRetries\": 50 } }
1137
-
1138
- NOTE: maxRetries tracks stuck retries on SAME work.
1139
- For total session limit, use maxTurns instead.
1140
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1141
-
1142
- # Clear retry counter and auto session to prevent immediate re-triggering
1143
- clear_retry_counter
1144
- clear_auto_session # End auto mode on circuit breaker
1145
-
1146
- # Log structured decision
1147
- if type log_decision &>/dev/null; then
1148
- _circuit_context=$(jq -n \
1149
- --argjson turnCurrent "$CURRENT_TURN" \
1150
- --argjson turnMax "$MAX_TURNS" \
1151
- --argjson retryCurrent "$CURRENT_RETRY" \
1152
- --argjson retryMax "$MAX_RETRIES_BEFORE_ESCALATE" \
1153
- --arg activeIncs "$REMAINING_INCS" \
1154
- '{sessionActive: false, turn: {current: $turnCurrent, max: $turnMax}, retry: {current: $retryCurrent, max: $retryMax, stuck: true}, increments: {active: ($activeIncs | split(", ") | map(select(length > 0)))}}')
1155
- log_decision "stop-auto" "approve" "retry_limit" "Stuck session: $CURRENT_RETRY retries on same incomplete work" "$_circuit_context" "$(_get_duration_ms)"
1156
- fi
1157
-
1158
- jq -n \
1159
- --arg decision "approve" \
1160
- --arg reason "Stuck session: $CURRENT_RETRY retries on same incomplete work" \
1161
- --arg msg "$CIRCUIT_MSG" \
1162
- '{decision: $decision, reason: $reason, systemMessage: $msg}'
1163
- exit 0
1164
- fi
1165
-
1166
- # ============================================================================
1167
- # ESCAPE GUIDANCE: Help user understand when to pivot vs continue
1168
- # Adds guidance for new functionality/bug fixes that don't fit current increment
1169
- # ============================================================================
1170
-
1171
- # Build escape options section for the message
1172
- ESCAPE_OPTIONS="
1173
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1174
- 🔄 WORKING ON SOMETHING DIFFERENT? (New functionality, bug fix, or urgent work)
1175
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1176
-
1177
- If the current increment ($FIRST_INC) doesn't match what you need to work on:
1178
-
1179
- 📋 OPTION 1: Create a NEW increment for new functionality
1180
- → Run: /sw:cancel-auto (stop auto mode first)
1181
- → Then: /sw:increment \"your new feature or bug fix description\"
1182
- → This will involve PM and Architect roles to properly plan
1183
-
1184
- 📋 OPTION 2: Pause current work and start something new
1185
- → Run: /sw:pause $FIRST_INC
1186
- → Then: /sw:increment \"urgent bug fix\" --type=bug
1187
- → Resume later: /sw:resume $FIRST_INC
1188
-
1189
- 📋 OPTION 3: Emergency quick fix (skip planning)
1190
- → Run: /sw:cancel-auto
1191
- → Work directly without increment tracking
1192
- → Better for tiny fixes that don't need tracking
1193
-
1194
- 💡 TIP: If you're stuck in a loop, the RIGHT answer is often to:
1195
- 1. Cancel auto mode: /sw:cancel-auto
1196
- 2. Think about what you actually need
1197
- 3. Start fresh with a proper increment
1198
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1199
-
1200
- # Show escape options after 3+ retries or when turn count is high
1201
- if [ "$CURRENT_RETRY" -ge 3 ] || [ "$CURRENT_TURN" -ge $((MAX_TURNS / 2)) ]; then
1202
- log "Adding escape guidance (retry=$CURRENT_RETRY, turn=$CURRENT_TURN)"
1203
- MSG="$MSG
1204
- $ESCAPE_OPTIONS"
1205
- fi
1206
-
1207
- # ============================================================================
1208
- # BUILD CLEAR SUCCESS CRITERIA FOR THE REASON FIELD
1209
- # This is what shows in Claude Code UI - must be ACTIONABLE
1210
- # ============================================================================
1211
-
1212
- # Extract first blocking issue
1213
- FIRST_BLOCKER=""
1214
- if [ -n "$VALIDATION_ERRORS" ]; then
1215
- FIRST_BLOCKER=$(echo "$VALIDATION_ERRORS" | tr '|' '\n' | grep -v '^$' | head -1)
1216
- fi
1217
-
1218
- # Build success criteria string with MANDATORY instructions
1219
- SUCCESS_CRITERIA=""
1220
- NEXT_COMMAND=""
1221
-
1222
- # Use cached values if available, otherwise calculate (fallback)
1223
- if [ -n "$FIRST_INC_CACHED" ] && [ "$FIRST_INC_CACHED" = "$FIRST_INC" ]; then
1224
- pending_count="$FIRST_INC_PENDING_CACHED"
1225
- open_acs="$FIRST_INC_ACS_CACHED"
1226
- else
1227
- pending_count=$(count_pending_tasks "$FIRST_INC")
1228
- open_acs=$(count_open_acs "$FIRST_INC")
1229
- fi
1230
-
1231
- if [ -n "$FIRST_BLOCKER" ]; then
1232
- SUCCESS_CRITERIA="FIX: $FIRST_BLOCKER"
1233
- NEXT_COMMAND="/sw:do"
1234
- elif [ "$pending_count" -gt 0 ]; then
1235
- SUCCESS_CRITERIA="MUST COMPLETE $pending_count pending task(s)"
1236
- NEXT_COMMAND="/sw:do"
1237
- elif [ "$open_acs" -gt 0 ]; then
1238
- SUCCESS_CRITERIA="MUST SATISFY $open_acs open AC(s)"
1239
- NEXT_COMMAND="/sw:do"
1240
- else
1241
- # All tasks and ACs done - MUST close now!
1242
- SUCCESS_CRITERIA="ALL DONE → MUST RUN /sw:done $FIRST_INC NOW"
1243
- NEXT_COMMAND="/sw:done $FIRST_INC"
1244
- fi
1245
-
1246
- log "BLOCK: $REMAINING_COUNT increment(s) remaining - turn $CURRENT_TURN/$MAX_TURNS, retry $CURRENT_RETRY - $SUCCESS_CRITERIA"
1247
-
1248
- # Build actionable reason for Claude Code UI - this is the INSTRUCTION
1249
- # Show turn/maxTurns (total session limit) in reason for visibility
1250
- if [ "$pending_count" -eq 0 ] && [ "$open_acs" -eq 0 ]; then
1251
- # Everything complete - direct to close
1252
- ACTIONABLE_REASON="✅ [turn $CURRENT_TURN/$MAX_TURNS] ALL COMPLETE! RUN NOW: /sw:done $FIRST_INC"
1253
- else
1254
- # Work remains - show what and then remind to close
1255
- ACTIONABLE_REASON="⏳ [turn $CURRENT_TURN/$MAX_TURNS] $SUCCESS_CRITERIA → RUN: $NEXT_COMMAND → THEN: /sw:done $FIRST_INC"
1256
- fi
1257
-
1258
- # Append mandatory instruction to systemMessage
1259
- MSG="$MSG
1260
-
1261
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1262
- 🚨 MANDATORY INSTRUCTION (YOU MUST FOLLOW THIS):
1263
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1264
-
1265
- 1. COMPLETE ALL PENDING TASKS: Run /sw:do and finish every [ ] task
1266
- 2. SATISFY ALL ACCEPTANCE CRITERIA: Mark all ACs as [x] in spec.md
1267
- 3. CLOSE THE INCREMENT: Run /sw:done $FIRST_INC
1268
-
1269
- ⚠️ DO NOT STOP until you have run /sw:done $FIRST_INC successfully!
1270
- ⚠️ Session: turn $CURRENT_TURN of $MAX_TURNS (hard limit)
1271
-
1272
- Current status: $pending_count pending task(s), $open_acs open AC(s)
1273
- Next command to run: $NEXT_COMMAND
1274
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1275
-
1276
- # Log structured decision before blocking
1277
- if type log_decision &>/dev/null; then
1278
- # Get previous reasons from retry file
1279
- _prev_reasons_json="[]"
1280
- if [ -f "$RETRY_FILE" ] && [ -s "$RETRY_FILE" ]; then
1281
- _tmp_reasons=$(jq -c '.reasons // []' "$RETRY_FILE" 2>/dev/null)
1282
- [ -n "$_tmp_reasons" ] && _prev_reasons_json="$_tmp_reasons"
1283
- fi
1284
-
1285
- # Build blocked increments array
1286
- _blocked_json="[]"
1287
- if [ -n "$FIRST_INC" ]; then
1288
- _blocked_json=$(jq -n \
1289
- --arg id "$FIRST_INC" \
1290
- --argjson tasksPending "$pending_count" \
1291
- --argjson acsOpen "$open_acs" \
1292
- --arg reason "$SUCCESS_CRITERIA" \
1293
- '[{id: $id, tasksPending: $tasksPending, acsOpen: $acsOpen, reason: $reason}]')
1294
- fi
1295
-
1296
- # Pre-compute stuck value (avoid subshell in jq args)
1297
- _stuck_val="false"
1298
- [ "$CURRENT_RETRY" -ge "$MAX_RETRIES_BEFORE_ESCALATE" ] && _stuck_val="true"
1299
-
1300
- # Build full context JSON
1301
- _block_context=$(jq -n \
1302
- --argjson turnCurrent "$CURRENT_TURN" \
1303
- --argjson turnMax "$MAX_TURNS" \
1304
- --argjson retryCurrent "$CURRENT_RETRY" \
1305
- --argjson retryMax "$MAX_RETRIES_BEFORE_ESCALATE" \
1306
- --argjson stuck "$_stuck_val" \
1307
- --arg activeIncs "$REMAINING_INCS" \
1308
- --argjson blocked "$_blocked_json" \
1309
- --argjson previousReasons "$_prev_reasons_json" \
1310
- '{sessionActive: true, turn: {current: $turnCurrent, max: $turnMax}, retry: {current: $retryCurrent, max: $retryMax, stuck: $stuck}, increments: {active: ($activeIncs | split(", ") | map(select(length > 0))), blocked: $blocked}, previousReasons: $previousReasons}')
1311
-
1312
- log_decision "stop-auto" "block" "work_remaining" "$SUCCESS_CRITERIA" "$_block_context" "$(_get_duration_ms)"
1313
- fi
1314
-
1315
- jq -n \
1316
- --arg decision "block" \
1317
- --arg reason "$ACTIONABLE_REASON" \
1318
- --arg msg "$MSG" \
1319
- '{decision: $decision, reason: $reason, systemMessage: $msg}'