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.
- package/CLAUDE.md +1 -1
- package/bin/specweave.js +23 -1
- package/dist/src/cli/commands/hook.d.ts +15 -0
- package/dist/src/cli/commands/hook.d.ts.map +1 -0
- package/dist/src/cli/commands/hook.js +61 -0
- package/dist/src/cli/commands/hook.js.map +1 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +5 -0
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.js +11 -1
- package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
- package/dist/src/cli/commands/sync-setup.d.ts.map +1 -1
- package/dist/src/cli/commands/sync-setup.js +7 -3
- package/dist/src/cli/commands/sync-setup.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts +9 -0
- package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js +9 -3
- package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js.map +1 -1
- package/dist/src/config/types.d.ts +2 -2
- package/dist/src/core/config/types.d.ts +18 -2
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/hooks/handlers/hook-router.d.ts +19 -0
- package/dist/src/core/hooks/handlers/hook-router.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/hook-router.js +75 -0
- package/dist/src/core/hooks/handlers/hook-router.js.map +1 -0
- package/dist/src/core/hooks/handlers/index.d.ts +10 -0
- package/dist/src/core/hooks/handlers/index.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/index.js +9 -0
- package/dist/src/core/hooks/handlers/index.js.map +1 -0
- package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts +11 -0
- package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/post-tool-use-analytics.js +73 -0
- package/dist/src/core/hooks/handlers/post-tool-use-analytics.js.map +1 -0
- package/dist/src/core/hooks/handlers/post-tool-use.d.ts +11 -0
- package/dist/src/core/hooks/handlers/post-tool-use.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/post-tool-use.js +76 -0
- package/dist/src/core/hooks/handlers/post-tool-use.js.map +1 -0
- package/dist/src/core/hooks/handlers/pre-compact.d.ts +11 -0
- package/dist/src/core/hooks/handlers/pre-compact.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/pre-compact.js +77 -0
- package/dist/src/core/hooks/handlers/pre-compact.js.map +1 -0
- package/dist/src/core/hooks/handlers/pre-tool-use.d.ts +11 -0
- package/dist/src/core/hooks/handlers/pre-tool-use.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/pre-tool-use.js +318 -0
- package/dist/src/core/hooks/handlers/pre-tool-use.js.map +1 -0
- package/dist/src/core/hooks/handlers/session-start.d.ts +9 -0
- package/dist/src/core/hooks/handlers/session-start.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/session-start.js +111 -0
- package/dist/src/core/hooks/handlers/session-start.js.map +1 -0
- package/dist/src/core/hooks/handlers/stop-auto.d.ts +16 -0
- package/dist/src/core/hooks/handlers/stop-auto.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/stop-auto.js +122 -0
- package/dist/src/core/hooks/handlers/stop-auto.js.map +1 -0
- package/dist/src/core/hooks/handlers/stop-reflect.d.ts +14 -0
- package/dist/src/core/hooks/handlers/stop-reflect.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/stop-reflect.js +43 -0
- package/dist/src/core/hooks/handlers/stop-reflect.js.map +1 -0
- package/dist/src/core/hooks/handlers/stop-sync.d.ts +15 -0
- package/dist/src/core/hooks/handlers/stop-sync.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/stop-sync.js +68 -0
- package/dist/src/core/hooks/handlers/stop-sync.js.map +1 -0
- package/dist/src/core/hooks/handlers/types.d.ts +63 -0
- package/dist/src/core/hooks/handlers/types.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/types.js +27 -0
- package/dist/src/core/hooks/handlers/types.js.map +1 -0
- package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts +14 -0
- package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/user-prompt-submit.js +173 -0
- package/dist/src/core/hooks/handlers/user-prompt-submit.js.map +1 -0
- package/dist/src/core/hooks/handlers/utils.d.ts +25 -0
- package/dist/src/core/hooks/handlers/utils.d.ts.map +1 -0
- package/dist/src/core/hooks/handlers/utils.js +64 -0
- package/dist/src/core/hooks/handlers/utils.js.map +1 -0
- package/dist/src/core/increment/completion-validator.d.ts.map +1 -1
- package/dist/src/core/increment/completion-validator.js +32 -0
- package/dist/src/core/increment/completion-validator.js.map +1 -1
- package/dist/src/init/research/types.d.ts +1 -1
- package/dist/src/sync/sync-target-resolver.js.map +1 -1
- package/dist/src/utils/lock-manager.d.ts.map +1 -1
- package/dist/src/utils/lock-manager.js +5 -0
- package/dist/src/utils/lock-manager.js.map +1 -1
- package/dist/src/utils/plugin-copier.d.ts +10 -0
- package/dist/src/utils/plugin-copier.d.ts.map +1 -1
- package/dist/src/utils/plugin-copier.js +63 -35
- package/dist/src/utils/plugin-copier.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/agents/sw-closer.md +3 -2
- package/plugins/specweave/hooks/hooks.json +10 -10
- package/plugins/specweave/skills/code-reviewer/SKILL.md +180 -16
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-comments.md +83 -0
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-silent-failures.md +19 -0
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-spec-compliance.md +19 -0
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-tests.md +101 -0
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-types.md +20 -0
- package/plugins/specweave/skills/done/SKILL.md +56 -21
- package/plugins/specweave/skills/grill/SKILL.md +1 -1
- package/plugins/specweave/skills/team-lead/agents/reviewer-logic.md +19 -0
- package/plugins/specweave/skills/team-lead/agents/reviewer-performance.md +20 -0
- package/plugins/specweave/skills/team-lead/agents/reviewer-security.md +20 -0
- package/src/templates/CLAUDE.md.template +7 -4
- package/plugins/specweave/hooks/README.md +0 -493
- package/plugins/specweave/hooks/_archive/stop-auto-v4-legacy.sh +0 -1319
- package/plugins/specweave/hooks/lib/common-setup.sh +0 -144
- package/plugins/specweave/hooks/lib/hook-errors.sh +0 -414
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh +0 -245
- package/plugins/specweave/hooks/lib/resolve-package.sh +0 -146
- package/plugins/specweave/hooks/lib/scheduler-startup.sh +0 -135
- package/plugins/specweave/hooks/lib/score-increment.sh +0 -87
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +0 -193
- package/plugins/specweave/hooks/lib/update-active-increment.sh +0 -95
- package/plugins/specweave/hooks/lib/update-status-line.sh +0 -233
- package/plugins/specweave/hooks/lib/validate-spec-status.sh +0 -171
- package/plugins/specweave/hooks/llm-judge-validator.sh +0 -219
- package/plugins/specweave/hooks/log-decision.sh +0 -168
- package/plugins/specweave/hooks/pre-compact.sh +0 -64
- package/plugins/specweave/hooks/startup-health-check.sh +0 -64
- package/plugins/specweave/hooks/stop-auto-v5.sh +0 -276
- package/plugins/specweave/hooks/stop-reflect.sh +0 -336
- package/plugins/specweave/hooks/stop-sync.sh +0 -283
- package/plugins/specweave/hooks/tests/test-auto-context-integration.sh +0 -126
- package/plugins/specweave/hooks/tests/test-stop-auto-enriched.sh +0 -128
- package/plugins/specweave/hooks/universal/dispatcher.mjs +0 -336
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +0 -325
- package/plugins/specweave/hooks/universal/hook-wrapper.cmd +0 -26
- package/plugins/specweave/hooks/universal/hook-wrapper.sh +0 -69
- package/plugins/specweave/hooks/universal/run-hook.sh +0 -20
- package/plugins/specweave/hooks/universal/session-start.cmd +0 -16
- package/plugins/specweave/hooks/universal/session-start.ps1 +0 -16
- package/plugins/specweave/hooks/user-prompt-submit.sh +0 -2550
- package/plugins/specweave/hooks/v2/detectors/lifecycle-detector.sh +0 -87
- package/plugins/specweave/hooks/v2/detectors/us-completion-detector.sh +0 -186
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use-analytics.sh +0 -83
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +0 -447
- package/plugins/specweave/hooks/v2/dispatchers/pre-tool-use.sh +0 -104
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +0 -270
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +0 -14
- package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +0 -14
- package/plugins/specweave/hooks/v2/guards/increment-existence-guard.sh +0 -240
- package/plugins/specweave/hooks/v2/guards/interview-enforcement-guard.sh +0 -171
- package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +0 -14
- package/plugins/specweave/hooks/v2/guards/skill-chain-enforcement-guard.sh +0 -222
- package/plugins/specweave/hooks/v2/guards/spec-template-enforcement-guard.sh +0 -21
- package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +0 -14
- package/plugins/specweave/hooks/v2/guards/status-completion-guard.sh +0 -84
- package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +0 -475
- package/plugins/specweave/hooks/v2/guards/tdd-enforcement-guard.sh +0 -268
- package/plugins/specweave/hooks/v2/handlers/ac-sync-dispatcher.sh +0 -332
- package/plugins/specweave/hooks/v2/handlers/ac-validation-handler.sh +0 -50
- package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +0 -347
- package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +0 -83
- package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +0 -268
- package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +0 -104
- package/plugins/specweave/hooks/v2/handlers/status-line-handler.sh +0 -165
- package/plugins/specweave/hooks/v2/handlers/status-update.sh +0 -61
- package/plugins/specweave/hooks/v2/handlers/universal-auto-create-dispatcher.sh +0 -270
- package/plugins/specweave/hooks/v2/integrations/ado-post-living-docs-update.sh +0 -367
- package/plugins/specweave/hooks/v2/integrations/ado-post-task.sh +0 -179
- package/plugins/specweave/hooks/v2/integrations/github-auto-create-handler.sh +0 -553
- package/plugins/specweave/hooks/v2/integrations/github-post-task.sh +0 -345
- package/plugins/specweave/hooks/v2/integrations/jira-post-task.sh +0 -180
- package/plugins/specweave/hooks/v2/lib/check-provider-enabled.sh +0 -52
- package/plugins/specweave/hooks/v2/queue/enqueue.sh +0 -81
- package/plugins/specweave/hooks/v2/session-end.sh +0 -139
- 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}'
|