get-shit-done-cc 1.9.13 → 1.10.0-experimental.0
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/README.md +8 -0
- package/agents/design-specialist.md +222 -0
- package/agents/gsd-executor.md +37 -375
- package/agents/gsd-planner.md +15 -108
- package/bin/install.js +92 -5
- package/commands/gsd/autopilot.md +518 -0
- package/commands/gsd/checkpoints.md +229 -0
- package/commands/gsd/design-system.md +70 -0
- package/commands/gsd/discuss-design.md +77 -0
- package/commands/gsd/extend.md +80 -0
- package/commands/gsd/help.md +43 -0
- package/commands/gsd/new-project.md +94 -8
- package/commands/gsd/plan-phase.md +35 -5
- package/get-shit-done/references/ccr-integration.md +468 -0
- package/get-shit-done/references/checkpoint-execution.md +369 -0
- package/get-shit-done/references/checkpoint-types.md +728 -0
- package/get-shit-done/references/deviation-rules.md +215 -0
- package/get-shit-done/references/framework-patterns.md +543 -0
- package/get-shit-done/references/ui-principles.md +258 -0
- package/get-shit-done/references/verification-patterns.md +1 -1
- package/get-shit-done/skills/gsd-extend/SKILL.md +154 -0
- package/get-shit-done/skills/gsd-extend/references/agent-structure.md +305 -0
- package/get-shit-done/skills/gsd-extend/references/extension-anatomy.md +123 -0
- package/get-shit-done/skills/gsd-extend/references/reference-structure.md +408 -0
- package/get-shit-done/skills/gsd-extend/references/template-structure.md +370 -0
- package/get-shit-done/skills/gsd-extend/references/validation-rules.md +140 -0
- package/get-shit-done/skills/gsd-extend/references/workflow-structure.md +253 -0
- package/get-shit-done/skills/gsd-extend/templates/agent-template.md +234 -0
- package/get-shit-done/skills/gsd-extend/templates/reference-template.md +239 -0
- package/get-shit-done/skills/gsd-extend/templates/workflow-template.md +169 -0
- package/get-shit-done/skills/gsd-extend/workflows/create-approach.md +332 -0
- package/get-shit-done/skills/gsd-extend/workflows/list-extensions.md +133 -0
- package/get-shit-done/skills/gsd-extend/workflows/remove-extension.md +93 -0
- package/get-shit-done/skills/gsd-extend/workflows/validate-extension.md +184 -0
- package/get-shit-done/templates/autopilot-script-simple.sh +181 -0
- package/get-shit-done/templates/autopilot-script.sh +1142 -0
- package/get-shit-done/templates/autopilot-script.sh.backup +1142 -0
- package/get-shit-done/templates/design-system.md +238 -0
- package/get-shit-done/templates/phase-design.md +205 -0
- package/get-shit-done/templates/phase-models-template.json +71 -0
- package/get-shit-done/templates/phase-prompt.md +4 -4
- package/get-shit-done/templates/state.md +37 -0
- package/get-shit-done/tui/App.tsx +169 -0
- package/get-shit-done/tui/README.md +107 -0
- package/get-shit-done/tui/build.js +37 -0
- package/get-shit-done/tui/components/ActivityFeed.tsx +126 -0
- package/get-shit-done/tui/components/PhaseCard.tsx +86 -0
- package/get-shit-done/tui/components/StatsBar.tsx +147 -0
- package/get-shit-done/tui/dist/index.js +387 -0
- package/get-shit-done/tui/index.tsx +12 -0
- package/get-shit-done/tui/package-lock.json +1074 -0
- package/get-shit-done/tui/package.json +22 -0
- package/get-shit-done/tui/utils/pipeReader.ts +129 -0
- package/get-shit-done/workflows/design-system.md +245 -0
- package/get-shit-done/workflows/discuss-design.md +330 -0
- package/get-shit-done/workflows/execute-phase.md +44 -1
- package/get-shit-done/workflows/execute-plan-auth.md +122 -0
- package/get-shit-done/workflows/execute-plan-checkpoints.md +541 -0
- package/get-shit-done/workflows/execute-plan.md +34 -856
- package/package.json +8 -3
|
@@ -0,0 +1,1142 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
3
|
+
# GSD Autopilot Script
|
|
4
|
+
# Generated: {{timestamp}}
|
|
5
|
+
# Project: {{project_name}}
|
|
6
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
#
|
|
8
|
+
# Autonomous execution of all remaining phases in the milestone.
|
|
9
|
+
# Each phase gets fresh 200k context via claude -p.
|
|
10
|
+
# State persists in .planning/ - safe to interrupt and resume.
|
|
11
|
+
#
|
|
12
|
+
# Features:
|
|
13
|
+
# - Real-time activity display via hooks
|
|
14
|
+
# - Stage tracking (research -> planning -> building -> verifying)
|
|
15
|
+
# - Git safety checks (no uncommitted files left behind)
|
|
16
|
+
# - Phase context display (what we're building and why)
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# bash .planning/autopilot.sh # Run attached
|
|
20
|
+
# nohup bash .planning/autopilot.sh & # Run in background
|
|
21
|
+
#
|
|
22
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
23
|
+
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
# Signal to GSD commands that we're in autopilot mode
|
|
27
|
+
export GSD_AUTOPILOT=1
|
|
28
|
+
|
|
29
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
# Configuration (filled by /gsd:autopilot)
|
|
31
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
PROJECT_DIR="{{project_dir}}"
|
|
34
|
+
PROJECT_NAME="{{project_name}}"
|
|
35
|
+
PHASES=({{phases}})
|
|
36
|
+
CHECKPOINT_MODE="{{checkpoint_mode}}"
|
|
37
|
+
MAX_RETRIES={{max_retries}}
|
|
38
|
+
BUDGET_LIMIT={{budget_limit}}
|
|
39
|
+
WEBHOOK_URL="{{webhook_url}}"
|
|
40
|
+
|
|
41
|
+
# Model selection (from config.json)
|
|
42
|
+
AUTOPILOT_MODEL="default"
|
|
43
|
+
|
|
44
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
# Derived paths
|
|
46
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
LOG_DIR="$PROJECT_DIR/.planning/logs"
|
|
49
|
+
CHECKPOINT_DIR="$PROJECT_DIR/.planning/checkpoints"
|
|
50
|
+
STATE_FILE="$PROJECT_DIR/.planning/STATE.md"
|
|
51
|
+
ACTIVITY_PIPE="$PROJECT_DIR/.planning/logs/activity.pipe"
|
|
52
|
+
|
|
53
|
+
# Export for hooks
|
|
54
|
+
export GSD_ACTIVITY_PIPE="$ACTIVITY_PIPE"
|
|
55
|
+
export GSD_PROJECT_DIR="$PROJECT_DIR"
|
|
56
|
+
export GSD_LOG_DIR="$LOG_DIR"
|
|
57
|
+
|
|
58
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
# Setup
|
|
60
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
cd "$PROJECT_DIR"
|
|
63
|
+
mkdir -p "$LOG_DIR" "$CHECKPOINT_DIR/pending" "$CHECKPOINT_DIR/approved"
|
|
64
|
+
|
|
65
|
+
# Create activity pipe for hook communication
|
|
66
|
+
rm -f "$ACTIVITY_PIPE"
|
|
67
|
+
mkfifo "$ACTIVITY_PIPE" 2>/dev/null || true
|
|
68
|
+
|
|
69
|
+
# Try to start Ink TUI (fallback to bash TUI if not available)
|
|
70
|
+
INK_TUI_AVAILABLE=false
|
|
71
|
+
INK_TUI_PID=""
|
|
72
|
+
INK_TUI_BIN=""
|
|
73
|
+
|
|
74
|
+
if command -v node &> /dev/null; then
|
|
75
|
+
# Check for Ink TUI in the project
|
|
76
|
+
if [ -f "$PROJECT_DIR/node_modules/.bin/gsd-autopilot-tui" ]; then
|
|
77
|
+
INK_TUI_BIN="$PROJECT_DIR/node_modules/.bin/gsd-autopilot-tui"
|
|
78
|
+
INK_TUI_AVAILABLE=true
|
|
79
|
+
elif [ -f "$HOME/.npm-global/bin/gsd-autopilot-tui" ]; then
|
|
80
|
+
INK_TUI_BIN="$HOME/.npm-global/bin/gsd-autopilot-tui"
|
|
81
|
+
INK_TUI_AVAILABLE=true
|
|
82
|
+
elif command -v gsd-autopilot-tui &> /dev/null; then
|
|
83
|
+
INK_TUI_BIN="gsd-autopilot-tui"
|
|
84
|
+
INK_TUI_AVAILABLE=true
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [ "$INK_TUI_AVAILABLE" = true ]; then
|
|
89
|
+
log "INFO" "Starting Ink TUI: $INK_TUI_BIN"
|
|
90
|
+
export GSD_ACTIVITY_PIPE="$ACTIVITY_PIPE"
|
|
91
|
+
export GSD_PROJECT_DIR="$PROJECT_DIR"
|
|
92
|
+
export GSD_LOG_DIR="$LOG_DIR"
|
|
93
|
+
export GSD_PHASE="$CURRENT_PHASE"
|
|
94
|
+
export GSD_PHASE_NAME="$CURRENT_PHASE_NAME"
|
|
95
|
+
export GSD_TOTAL_PHASES="${#PHASES[@]}"
|
|
96
|
+
|
|
97
|
+
# Start TUI in background
|
|
98
|
+
if [ -n "$INK_TUI_BIN" ]; then
|
|
99
|
+
setsid "$INK_TUI_BIN" > "$LOG_DIR/tui.log" 2>&1 &
|
|
100
|
+
INK_TUI_PID=$!
|
|
101
|
+
log "INFO" "Ink TUI started (PID: $INK_TUI_PID)"
|
|
102
|
+
|
|
103
|
+
# Wait a moment for TUI to initialize
|
|
104
|
+
sleep 1
|
|
105
|
+
|
|
106
|
+
# Check if TUI is still running
|
|
107
|
+
if ! kill -0 $INK_TUI_PID 2>/dev/null; then
|
|
108
|
+
log "WARN" "Ink TUI failed to start, using bash fallback"
|
|
109
|
+
INK_TUI_AVAILABLE=false
|
|
110
|
+
INK_TUI_PID=""
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
else
|
|
114
|
+
log "INFO" "Ink TUI not available, using bash TUI"
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# Lock directory (atomic creation prevents race condition)
|
|
118
|
+
LOCK_DIR="$PROJECT_DIR/.planning/autopilot.lock.d"
|
|
119
|
+
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
120
|
+
echo "ERROR: Autopilot already running (lock exists: $LOCK_DIR)"
|
|
121
|
+
echo "If previous run crashed, remove manually: rmdir '$LOCK_DIR'"
|
|
122
|
+
exit 1
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
cleanup() {
|
|
126
|
+
# Kill background processes
|
|
127
|
+
[ -n "${READER_PID:-}" ] && kill $READER_PID 2>/dev/null
|
|
128
|
+
[ -n "${DISPLAY_PID:-}" ] && kill $DISPLAY_PID 2>/dev/null
|
|
129
|
+
|
|
130
|
+
# Kill Ink TUI if running
|
|
131
|
+
[ -n "${INK_TUI_PID:-}" ] && kill $INK_TUI_PID 2>/dev/null || true
|
|
132
|
+
|
|
133
|
+
# Remove lock and pipe
|
|
134
|
+
rmdir "$LOCK_DIR" 2>/dev/null || true
|
|
135
|
+
rm -f "$ACTIVITY_PIPE" 2>/dev/null || true
|
|
136
|
+
|
|
137
|
+
# Restore cursor
|
|
138
|
+
printf "\033[?25h" 2>/dev/null
|
|
139
|
+
}
|
|
140
|
+
trap cleanup EXIT INT TERM
|
|
141
|
+
|
|
142
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
143
|
+
# Cross-platform helpers
|
|
144
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
iso_timestamp() {
|
|
147
|
+
date '+%Y-%m-%dT%H:%M:%S%z'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
elapsed_since() {
|
|
151
|
+
local start=$1
|
|
152
|
+
local now=$(date +%s)
|
|
153
|
+
local elapsed=$((now - start))
|
|
154
|
+
local min=$((elapsed / 60))
|
|
155
|
+
local sec=$((elapsed % 60))
|
|
156
|
+
printf "%d:%02d" $min $sec
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
160
|
+
# Terminal UI
|
|
161
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
# Colors and formatting (auto-disabled if not a terminal)
|
|
164
|
+
if [ -t 1 ]; then
|
|
165
|
+
C_RESET='\033[0m'
|
|
166
|
+
C_BOLD='\033[1m'
|
|
167
|
+
C_DIM='\033[2m'
|
|
168
|
+
C_RED='\033[31m'
|
|
169
|
+
C_GREEN='\033[32m'
|
|
170
|
+
C_YELLOW='\033[33m'
|
|
171
|
+
C_BLUE='\033[34m'
|
|
172
|
+
C_CYAN='\033[36m'
|
|
173
|
+
C_WHITE='\033[37m'
|
|
174
|
+
CURSOR_HOME='\033[H'
|
|
175
|
+
CURSOR_CLEAR='\033[J'
|
|
176
|
+
CURSOR_LINE_CLEAR='\033[K'
|
|
177
|
+
CURSOR_HIDE='\033[?25l'
|
|
178
|
+
CURSOR_SHOW='\033[?25h'
|
|
179
|
+
else
|
|
180
|
+
C_RESET='' C_BOLD='' C_DIM='' C_RED='' C_GREEN='' C_YELLOW=''
|
|
181
|
+
C_BLUE='' C_CYAN='' C_WHITE=''
|
|
182
|
+
CURSOR_HOME='' CURSOR_CLEAR='' CURSOR_LINE_CLEAR=''
|
|
183
|
+
CURSOR_HIDE='' CURSOR_SHOW=''
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
187
|
+
# Display State (shared via temp files for subprocess communication)
|
|
188
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
DISPLAY_STATE_DIR="$LOG_DIR/.display"
|
|
191
|
+
mkdir -p "$DISPLAY_STATE_DIR"
|
|
192
|
+
|
|
193
|
+
# Initialize display state files
|
|
194
|
+
echo "" > "$DISPLAY_STATE_DIR/current_stage"
|
|
195
|
+
echo "" > "$DISPLAY_STATE_DIR/stage_desc"
|
|
196
|
+
echo "0" > "$DISPLAY_STATE_DIR/stage_start"
|
|
197
|
+
echo "" > "$DISPLAY_STATE_DIR/completed_stages"
|
|
198
|
+
echo "" > "$DISPLAY_STATE_DIR/activity"
|
|
199
|
+
|
|
200
|
+
MAX_ACTIVITY_LINES=8
|
|
201
|
+
|
|
202
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
203
|
+
# Stage Management
|
|
204
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
stage_display_name() {
|
|
207
|
+
local subagent_type="$1"
|
|
208
|
+
case "$subagent_type" in
|
|
209
|
+
gsd-phase-researcher) echo "RESEARCH" ;;
|
|
210
|
+
gsd-planner) echo "PLANNING" ;;
|
|
211
|
+
gsd-plan-checker) echo "CHECKING" ;;
|
|
212
|
+
gsd-executor) echo "BUILDING" ;;
|
|
213
|
+
gsd-verifier) echo "VERIFYING" ;;
|
|
214
|
+
gsd-integration-checker) echo "INTEGRATING" ;;
|
|
215
|
+
*) echo "WORKING" ;;
|
|
216
|
+
esac
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
set_stage() {
|
|
220
|
+
local subagent_type="$1"
|
|
221
|
+
local description="$2"
|
|
222
|
+
|
|
223
|
+
# Complete previous stage if exists
|
|
224
|
+
local prev_stage=$(cat "$DISPLAY_STATE_DIR/current_stage" 2>/dev/null)
|
|
225
|
+
if [ -n "$prev_stage" ]; then
|
|
226
|
+
local prev_start=$(cat "$DISPLAY_STATE_DIR/stage_start" 2>/dev/null)
|
|
227
|
+
local elapsed=$(elapsed_since "$prev_start")
|
|
228
|
+
echo "$prev_stage:$elapsed" >> "$DISPLAY_STATE_DIR/completed_stages"
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
local stage_name=$(stage_display_name "$subagent_type")
|
|
232
|
+
echo "$stage_name" > "$DISPLAY_STATE_DIR/current_stage"
|
|
233
|
+
echo "$description" > "$DISPLAY_STATE_DIR/stage_desc"
|
|
234
|
+
echo "$(date +%s)" > "$DISPLAY_STATE_DIR/stage_start"
|
|
235
|
+
|
|
236
|
+
# Send to activity pipe for Ink TUI
|
|
237
|
+
if [ -p "$ACTIVITY_PIPE" ]; then
|
|
238
|
+
echo "STAGE:$subagent_type:$description" > "$ACTIVITY_PIPE"
|
|
239
|
+
fi
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
complete_current_stage() {
|
|
243
|
+
local curr_stage=$(cat "$DISPLAY_STATE_DIR/current_stage" 2>/dev/null)
|
|
244
|
+
if [ -n "$curr_stage" ]; then
|
|
245
|
+
local stage_start=$(cat "$DISPLAY_STATE_DIR/stage_start" 2>/dev/null)
|
|
246
|
+
local elapsed=$(elapsed_since "$stage_start")
|
|
247
|
+
echo "$curr_stage:$elapsed" >> "$DISPLAY_STATE_DIR/completed_stages"
|
|
248
|
+
echo "" > "$DISPLAY_STATE_DIR/current_stage"
|
|
249
|
+
echo "" > "$DISPLAY_STATE_DIR/stage_desc"
|
|
250
|
+
fi
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
reset_stages() {
|
|
254
|
+
echo "" > "$DISPLAY_STATE_DIR/current_stage"
|
|
255
|
+
echo "" > "$DISPLAY_STATE_DIR/stage_desc"
|
|
256
|
+
echo "0" > "$DISPLAY_STATE_DIR/stage_start"
|
|
257
|
+
echo "" > "$DISPLAY_STATE_DIR/completed_stages"
|
|
258
|
+
echo "" > "$DISPLAY_STATE_DIR/activity"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
262
|
+
# Activity Feed
|
|
263
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
add_activity() {
|
|
266
|
+
local type="$1"
|
|
267
|
+
local detail="$2"
|
|
268
|
+
|
|
269
|
+
local prefix=""
|
|
270
|
+
case "$type" in
|
|
271
|
+
read) prefix="read " ;;
|
|
272
|
+
write) prefix="write " ;;
|
|
273
|
+
edit) prefix="edit " ;;
|
|
274
|
+
commit) prefix="commit " ;;
|
|
275
|
+
test) prefix="test " ;;
|
|
276
|
+
*) prefix=" " ;;
|
|
277
|
+
esac
|
|
278
|
+
|
|
279
|
+
# Truncate long paths/messages
|
|
280
|
+
if [ ${#detail} -gt 50 ]; then
|
|
281
|
+
detail="${detail:0:47}..."
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
# Append to activity file, keep last N lines
|
|
285
|
+
echo "$prefix $detail" >> "$DISPLAY_STATE_DIR/activity"
|
|
286
|
+
tail -n $MAX_ACTIVITY_LINES "$DISPLAY_STATE_DIR/activity" > "$DISPLAY_STATE_DIR/activity.tmp"
|
|
287
|
+
mv "$DISPLAY_STATE_DIR/activity.tmp" "$DISPLAY_STATE_DIR/activity"
|
|
288
|
+
|
|
289
|
+
# Also write to activity pipe for Ink TUI
|
|
290
|
+
if [ -p "$ACTIVITY_PIPE" ]; then
|
|
291
|
+
case "$type" in
|
|
292
|
+
read) echo "FILE:read:$detail" > "$ACTIVITY_PIPE" ;;
|
|
293
|
+
write) echo "FILE:write:$detail" > "$ACTIVITY_PIPE" ;;
|
|
294
|
+
edit) echo "FILE:edit:$detail" > "$ACTIVITY_PIPE" ;;
|
|
295
|
+
commit) echo "COMMIT:$detail" > "$ACTIVITY_PIPE" ;;
|
|
296
|
+
test) echo "TEST:test" > "$ACTIVITY_PIPE" ;;
|
|
297
|
+
*) echo "INFO:$detail" > "$ACTIVITY_PIPE" ;;
|
|
298
|
+
esac
|
|
299
|
+
fi
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
303
|
+
# Phase Context
|
|
304
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
305
|
+
|
|
306
|
+
CURRENT_PHASE=""
|
|
307
|
+
CURRENT_PHASE_NAME=""
|
|
308
|
+
CURRENT_PHASE_CONTEXT=""
|
|
309
|
+
|
|
310
|
+
load_phase_context() {
|
|
311
|
+
local phase="$1"
|
|
312
|
+
local roadmap=".planning/ROADMAP.md"
|
|
313
|
+
|
|
314
|
+
[ ! -f "$roadmap" ] && return
|
|
315
|
+
|
|
316
|
+
# Extract phase name
|
|
317
|
+
CURRENT_PHASE_NAME=$(grep -E "Phase $phase:" "$roadmap" 2>/dev/null | head -1 | sed 's/.*Phase [0-9]*: //' | sed 's/ *$//' | sed 's/\*//g')
|
|
318
|
+
[ -z "$CURRENT_PHASE_NAME" ] && CURRENT_PHASE_NAME="Phase $phase"
|
|
319
|
+
|
|
320
|
+
# Extract goal and deliverables
|
|
321
|
+
local in_phase=0
|
|
322
|
+
local context=""
|
|
323
|
+
local line_count=0
|
|
324
|
+
|
|
325
|
+
while IFS= read -r line; do
|
|
326
|
+
if echo "$line" | grep -qE "Phase $phase:"; then
|
|
327
|
+
in_phase=1
|
|
328
|
+
continue
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
if [ $in_phase -eq 1 ]; then
|
|
332
|
+
# Stop at next phase
|
|
333
|
+
if echo "$line" | grep -qE "^###.*Phase [0-9]"; then
|
|
334
|
+
break
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
# Capture goal
|
|
338
|
+
if echo "$line" | grep -q "Goal:"; then
|
|
339
|
+
context=$(echo "$line" | sed 's/.*Goal:[[:space:]]*//' | sed 's/\*//g')
|
|
340
|
+
fi
|
|
341
|
+
|
|
342
|
+
# Capture must-haves (first few)
|
|
343
|
+
if echo "$line" | grep -qE "^[[:space:]]*-[[:space:]]" && [ $line_count -lt 4 ]; then
|
|
344
|
+
local item=$(echo "$line" | sed 's/^[[:space:]]*-[[:space:]]*//' | sed 's/\*//g')
|
|
345
|
+
if [ -n "$item" ]; then
|
|
346
|
+
context="$context
|
|
347
|
+
$item"
|
|
348
|
+
((line_count++))
|
|
349
|
+
fi
|
|
350
|
+
fi
|
|
351
|
+
fi
|
|
352
|
+
done < "$roadmap"
|
|
353
|
+
|
|
354
|
+
CURRENT_PHASE_CONTEXT="$context"
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
358
|
+
# Display Rendering
|
|
359
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
render_display() {
|
|
362
|
+
local total_phases=$1
|
|
363
|
+
local current_idx=$2
|
|
364
|
+
|
|
365
|
+
# Read current state from files
|
|
366
|
+
local current_stage=$(cat "$DISPLAY_STATE_DIR/current_stage" 2>/dev/null)
|
|
367
|
+
local stage_desc=$(cat "$DISPLAY_STATE_DIR/stage_desc" 2>/dev/null)
|
|
368
|
+
local stage_start=$(cat "$DISPLAY_STATE_DIR/stage_start" 2>/dev/null)
|
|
369
|
+
|
|
370
|
+
# Header
|
|
371
|
+
printf "${C_BOLD}${C_CYAN}"
|
|
372
|
+
printf "═══════════════════════════════════════════════════════════════\n"
|
|
373
|
+
printf " GSD AUTOPILOT"
|
|
374
|
+
printf "%*s" $((46 - ${#PROJECT_NAME})) ""
|
|
375
|
+
printf "Phase %s/%s\n" "$((current_idx + 1))" "$total_phases"
|
|
376
|
+
printf "═══════════════════════════════════════════════════════════════${C_RESET}\n"
|
|
377
|
+
printf "\n"
|
|
378
|
+
|
|
379
|
+
# Phase info
|
|
380
|
+
printf " ${C_BOLD}${C_WHITE}PHASE %s: %s${C_RESET}\n" "$CURRENT_PHASE" "$CURRENT_PHASE_NAME"
|
|
381
|
+
printf "\n"
|
|
382
|
+
|
|
383
|
+
# Phase context (if available)
|
|
384
|
+
if [ -n "$CURRENT_PHASE_CONTEXT" ]; then
|
|
385
|
+
echo "$CURRENT_PHASE_CONTEXT" | head -5 | while IFS= read -r line; do
|
|
386
|
+
printf "${C_DIM} %s${C_RESET}\n" "$line"
|
|
387
|
+
done
|
|
388
|
+
printf "\n"
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
printf "${C_DIM}───────────────────────────────────────────────────────────────${C_RESET}\n"
|
|
392
|
+
printf "\n"
|
|
393
|
+
|
|
394
|
+
# Completed stages
|
|
395
|
+
if [ -f "$DISPLAY_STATE_DIR/completed_stages" ]; then
|
|
396
|
+
while IFS= read -r stage_entry; do
|
|
397
|
+
[ -z "$stage_entry" ] && continue
|
|
398
|
+
local stage_name="${stage_entry%%:*}"
|
|
399
|
+
local stage_time="${stage_entry##*:}"
|
|
400
|
+
printf " ${C_DIM}%-12s%47s${C_RESET}\n" "$stage_name" "done $stage_time"
|
|
401
|
+
done < "$DISPLAY_STATE_DIR/completed_stages"
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
# Current stage
|
|
405
|
+
if [ -n "$current_stage" ]; then
|
|
406
|
+
local elapsed=""
|
|
407
|
+
if [ -n "$stage_start" ] && [ "$stage_start" != "0" ]; then
|
|
408
|
+
elapsed=$(elapsed_since "$stage_start")
|
|
409
|
+
fi
|
|
410
|
+
printf " ${C_WHITE}${C_BOLD}%-12s${C_RESET}%47s\n" "$current_stage" "$elapsed"
|
|
411
|
+
|
|
412
|
+
if [ -n "$stage_desc" ]; then
|
|
413
|
+
# Truncate description if needed
|
|
414
|
+
local desc="$stage_desc"
|
|
415
|
+
if [ ${#desc} -gt 55 ]; then
|
|
416
|
+
desc="${desc:0:52}..."
|
|
417
|
+
fi
|
|
418
|
+
printf "\n"
|
|
419
|
+
printf "${C_DIM} %s${C_RESET}\n" "$desc"
|
|
420
|
+
fi
|
|
421
|
+
fi
|
|
422
|
+
|
|
423
|
+
printf "\n"
|
|
424
|
+
printf "${C_DIM}───────────────────────────────────────────────────────────────${C_RESET}\n"
|
|
425
|
+
printf "\n"
|
|
426
|
+
|
|
427
|
+
# Activity feed
|
|
428
|
+
printf " ${C_DIM}Activity:${C_RESET}\n"
|
|
429
|
+
printf "\n"
|
|
430
|
+
|
|
431
|
+
local activity_count=0
|
|
432
|
+
if [ -f "$DISPLAY_STATE_DIR/activity" ] && [ -s "$DISPLAY_STATE_DIR/activity" ]; then
|
|
433
|
+
while IFS= read -r line; do
|
|
434
|
+
[ -z "$line" ] && continue
|
|
435
|
+
printf "${C_DIM} %s${C_RESET}\n" "$line"
|
|
436
|
+
((activity_count++))
|
|
437
|
+
done < "$DISPLAY_STATE_DIR/activity"
|
|
438
|
+
fi
|
|
439
|
+
|
|
440
|
+
if [ $activity_count -eq 0 ]; then
|
|
441
|
+
printf "${C_DIM} waiting...${C_RESET}\n"
|
|
442
|
+
activity_count=1
|
|
443
|
+
fi
|
|
444
|
+
|
|
445
|
+
# Pad to consistent height
|
|
446
|
+
local pad_lines=$((MAX_ACTIVITY_LINES - activity_count))
|
|
447
|
+
for ((i=0; i<pad_lines; i++)); do
|
|
448
|
+
printf "\n"
|
|
449
|
+
done
|
|
450
|
+
|
|
451
|
+
printf "\n"
|
|
452
|
+
printf "${C_DIM}───────────────────────────────────────────────────────────────${C_RESET}\n"
|
|
453
|
+
printf "\n"
|
|
454
|
+
|
|
455
|
+
# Progress bar
|
|
456
|
+
local completed=$current_idx
|
|
457
|
+
local bar_width=50
|
|
458
|
+
local filled=$((completed * bar_width / total_phases))
|
|
459
|
+
local empty=$((bar_width - filled))
|
|
460
|
+
|
|
461
|
+
printf " Progress ["
|
|
462
|
+
printf "${C_CYAN}"
|
|
463
|
+
for ((i=0; i<filled; i++)); do printf "="; done
|
|
464
|
+
for ((i=0; i<empty; i++)); do printf " "; done
|
|
465
|
+
printf "${C_RESET}"
|
|
466
|
+
printf "] %d/%d phases\n" "$completed" "$total_phases"
|
|
467
|
+
|
|
468
|
+
printf "\n"
|
|
469
|
+
printf "${C_DIM}───────────────────────────────────────────────────────────────${C_RESET}\n"
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
473
|
+
# Activity Pipe Reader (runs in background)
|
|
474
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
475
|
+
|
|
476
|
+
READER_PID=""
|
|
477
|
+
DISPLAY_PID=""
|
|
478
|
+
|
|
479
|
+
start_activity_reader() {
|
|
480
|
+
local total_phases=$1
|
|
481
|
+
local phase_idx=$2
|
|
482
|
+
|
|
483
|
+
# Background process to read from pipe and update state
|
|
484
|
+
(
|
|
485
|
+
while true; do
|
|
486
|
+
if read -r line < "$ACTIVITY_PIPE" 2>/dev/null; then
|
|
487
|
+
case "$line" in
|
|
488
|
+
STAGE:*)
|
|
489
|
+
local type=$(echo "$line" | cut -d: -f2)
|
|
490
|
+
local desc=$(echo "$line" | cut -d: -f3-)
|
|
491
|
+
set_stage "$type" "$desc"
|
|
492
|
+
;;
|
|
493
|
+
FILE:*)
|
|
494
|
+
local op=$(echo "$line" | cut -d: -f2)
|
|
495
|
+
local file=$(echo "$line" | cut -d: -f3-)
|
|
496
|
+
add_activity "$op" "$file"
|
|
497
|
+
;;
|
|
498
|
+
COMMIT:*)
|
|
499
|
+
local msg=$(echo "$line" | cut -d: -f2-)
|
|
500
|
+
add_activity "commit" "$msg"
|
|
501
|
+
;;
|
|
502
|
+
TEST:*)
|
|
503
|
+
add_activity "test" "running tests"
|
|
504
|
+
;;
|
|
505
|
+
TODO:*)
|
|
506
|
+
local task=$(echo "$line" | cut -d: -f2-)
|
|
507
|
+
echo "$task" > "$DISPLAY_STATE_DIR/stage_desc"
|
|
508
|
+
;;
|
|
509
|
+
esac
|
|
510
|
+
fi
|
|
511
|
+
done
|
|
512
|
+
) &
|
|
513
|
+
READER_PID=$!
|
|
514
|
+
|
|
515
|
+
# Background process to refresh display (only if Ink TUI not available)
|
|
516
|
+
if [ -t 1 ] && [ "$INK_TUI_AVAILABLE" = false ]; then
|
|
517
|
+
(
|
|
518
|
+
while true; do
|
|
519
|
+
printf "${CURSOR_HOME}${CURSOR_CLEAR}"
|
|
520
|
+
render_display "$total_phases" "$phase_idx"
|
|
521
|
+
sleep 0.5
|
|
522
|
+
done
|
|
523
|
+
) &
|
|
524
|
+
DISPLAY_PID=$!
|
|
525
|
+
fi
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
stop_activity_reader() {
|
|
529
|
+
if [ -n "$READER_PID" ]; then
|
|
530
|
+
kill $READER_PID 2>/dev/null || true
|
|
531
|
+
wait $READER_PID 2>/dev/null || true
|
|
532
|
+
READER_PID=""
|
|
533
|
+
fi
|
|
534
|
+
if [ -n "$DISPLAY_PID" ]; then
|
|
535
|
+
kill $DISPLAY_PID 2>/dev/null || true
|
|
536
|
+
wait $DISPLAY_PID 2>/dev/null || true
|
|
537
|
+
DISPLAY_PID=""
|
|
538
|
+
fi
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
542
|
+
# Logging & Notifications
|
|
543
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
544
|
+
|
|
545
|
+
log() {
|
|
546
|
+
local level="$1"
|
|
547
|
+
local message="$2"
|
|
548
|
+
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
549
|
+
|
|
550
|
+
# Always write to log file
|
|
551
|
+
echo "[$timestamp] [$level] $message" >> "$LOG_DIR/autopilot.log"
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
notify() {
|
|
555
|
+
local message="$1"
|
|
556
|
+
local status="${2:-info}"
|
|
557
|
+
|
|
558
|
+
log "NOTIFY" "$message"
|
|
559
|
+
|
|
560
|
+
# Terminal bell
|
|
561
|
+
printf "\a"
|
|
562
|
+
|
|
563
|
+
# Webhook if configured
|
|
564
|
+
if [ -n "$WEBHOOK_URL" ]; then
|
|
565
|
+
curl -s -X POST "$WEBHOOK_URL" \
|
|
566
|
+
-H "Content-Type: application/json" \
|
|
567
|
+
-d "{\"text\": \"GSD Autopilot [$PROJECT_NAME]: $message\", \"status\": \"$status\"}" \
|
|
568
|
+
> /dev/null 2>&1 || true
|
|
569
|
+
fi
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
573
|
+
# Git Safety
|
|
574
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
575
|
+
|
|
576
|
+
check_uncommitted_files() {
|
|
577
|
+
local context="$1"
|
|
578
|
+
|
|
579
|
+
# Check for uncommitted changes
|
|
580
|
+
if ! git diff --quiet HEAD 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
|
|
581
|
+
local uncommitted=$(git status --short 2>/dev/null)
|
|
582
|
+
|
|
583
|
+
log "WARN" "Uncommitted files detected ($context)"
|
|
584
|
+
log "WARN" "$uncommitted"
|
|
585
|
+
|
|
586
|
+
# Create safety commit
|
|
587
|
+
git add -A 2>/dev/null
|
|
588
|
+
git commit -m "wip(autopilot): uncommitted files from $context
|
|
589
|
+
|
|
590
|
+
Autopilot detected uncommitted files that would otherwise be lost.
|
|
591
|
+
Review and squash/revert as appropriate.
|
|
592
|
+
" 2>/dev/null || true
|
|
593
|
+
|
|
594
|
+
log "INFO" "Created safety commit for orphaned files"
|
|
595
|
+
add_activity "commit" "wip: safety commit"
|
|
596
|
+
return 1
|
|
597
|
+
fi
|
|
598
|
+
return 0
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
ensure_clean_working_tree() {
|
|
602
|
+
local context="$1"
|
|
603
|
+
check_uncommitted_files "$context" || true
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
607
|
+
# State Management
|
|
608
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
609
|
+
|
|
610
|
+
update_autopilot_state() {
|
|
611
|
+
local mode="$1"
|
|
612
|
+
local phase="$2"
|
|
613
|
+
local remaining="$3"
|
|
614
|
+
local error="${4:-none}"
|
|
615
|
+
|
|
616
|
+
if grep -q "## Autopilot" "$STATE_FILE" 2>/dev/null; then
|
|
617
|
+
awk -v mode="$mode" -v phase="$phase" -v remaining="$remaining" -v error="$error" -v ts="$(iso_timestamp)" '
|
|
618
|
+
/^## Autopilot/,/^## / {
|
|
619
|
+
if (/^- \*\*Mode:\*\*/) { print "- **Mode:** " mode; next }
|
|
620
|
+
if (/^- \*\*Current Phase:\*\*/) { print "- **Current Phase:** " phase; next }
|
|
621
|
+
if (/^- \*\*Phases Remaining:\*\*/) { print "- **Phases Remaining:** " remaining; next }
|
|
622
|
+
if (/^- \*\*Last Error:\*\*/) { print "- **Last Error:** " error; next }
|
|
623
|
+
if (/^- \*\*Updated:\*\*/) { print "- **Updated:** " ts; next }
|
|
624
|
+
}
|
|
625
|
+
{ print }
|
|
626
|
+
' "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
|
|
627
|
+
else
|
|
628
|
+
cat >> "$STATE_FILE" << EOF
|
|
629
|
+
|
|
630
|
+
## Autopilot
|
|
631
|
+
|
|
632
|
+
- **Mode:** $mode
|
|
633
|
+
- **Started:** $(iso_timestamp)
|
|
634
|
+
- **Current Phase:** $phase
|
|
635
|
+
- **Phases Remaining:** $remaining
|
|
636
|
+
- **Checkpoints Pending:** (none)
|
|
637
|
+
- **Last Error:** $error
|
|
638
|
+
- **Updated:** $(iso_timestamp)
|
|
639
|
+
EOF
|
|
640
|
+
fi
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
644
|
+
# Cost Tracking
|
|
645
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
646
|
+
|
|
647
|
+
TOTAL_TOKENS=0
|
|
648
|
+
TOTAL_COST_CENTS=0
|
|
649
|
+
|
|
650
|
+
track_cost() {
|
|
651
|
+
local log_file="$1"
|
|
652
|
+
local phase="$2"
|
|
653
|
+
|
|
654
|
+
local tokens=$(grep -o 'tokens[: ]*[0-9,]*' "$log_file" 2>/dev/null | tail -1 | grep -o '[0-9]*' | tr -d ',' || echo "0")
|
|
655
|
+
|
|
656
|
+
if [ "$tokens" -gt 0 ]; then
|
|
657
|
+
TOTAL_TOKENS=$((TOTAL_TOKENS + tokens))
|
|
658
|
+
|
|
659
|
+
local cost_cents=$((tokens / 100))
|
|
660
|
+
TOTAL_COST_CENTS=$((TOTAL_COST_CENTS + cost_cents))
|
|
661
|
+
|
|
662
|
+
local total_dollars=$((TOTAL_COST_CENTS / 100))
|
|
663
|
+
local total_remainder=$((TOTAL_COST_CENTS % 100))
|
|
664
|
+
local total_cost=$(printf "%d.%02d" $total_dollars $total_remainder)
|
|
665
|
+
|
|
666
|
+
log "COST" "Phase $phase: ${tokens} tokens (~\$${total_cost} total)"
|
|
667
|
+
fi
|
|
668
|
+
|
|
669
|
+
# Budget check
|
|
670
|
+
if [ "$BUDGET_LIMIT" -gt 0 ]; then
|
|
671
|
+
local budget_cents=$((BUDGET_LIMIT * 100))
|
|
672
|
+
if [ "$TOTAL_COST_CENTS" -gt "$budget_cents" ]; then
|
|
673
|
+
local total_dollars=$((TOTAL_COST_CENTS / 100))
|
|
674
|
+
local total_remainder=$((TOTAL_COST_CENTS % 100))
|
|
675
|
+
local total_cost=$(printf "%d.%02d" $total_dollars $total_remainder)
|
|
676
|
+
notify "Budget exceeded: \$${total_cost} / \$${BUDGET_LIMIT}" "error"
|
|
677
|
+
update_autopilot_state "paused" "$phase" "${PHASES[*]}" "budget_exceeded"
|
|
678
|
+
exit 0
|
|
679
|
+
fi
|
|
680
|
+
|
|
681
|
+
local warning_threshold=$((budget_cents * 80 / 100))
|
|
682
|
+
if [ "$TOTAL_COST_CENTS" -gt "$warning_threshold" ]; then
|
|
683
|
+
notify "Budget warning: 80% used" "warning"
|
|
684
|
+
fi
|
|
685
|
+
fi
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
689
|
+
# Model Selection & CCR Integration
|
|
690
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
691
|
+
|
|
692
|
+
CLAUDE_CMD="claude"
|
|
693
|
+
CCR_ACTIVATED=false
|
|
694
|
+
|
|
695
|
+
# Load phase models configuration
|
|
696
|
+
load_phase_models() {
|
|
697
|
+
if [ ! -f "$PHASE_MODELS_CONFIG" ]; then
|
|
698
|
+
log "INFO" "No phase models config found at $PHASE_MODELS_CONFIG"
|
|
699
|
+
return 1
|
|
700
|
+
fi
|
|
701
|
+
|
|
702
|
+
# Parse JSON to extract model for a phase
|
|
703
|
+
get_model_for_phase() {
|
|
704
|
+
local phase="$1"
|
|
705
|
+
local context="${2:-execution}"
|
|
706
|
+
|
|
707
|
+
# Try to get model from phases section
|
|
708
|
+
local model=$(grep -o "\"$phase\"[[:space:]]*:[[:space:]]*{" "$PHASE_MODELS_CONFIG" -A 5 2>/dev/null | grep -o '"model"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/' || echo "")
|
|
709
|
+
|
|
710
|
+
if [ -z "$model" ]; then
|
|
711
|
+
# Fall back to context-specific model
|
|
712
|
+
model=$(grep -o "\"$context\"[[:space:]]*:[[:space:]]*{" "$PHASE_MODELS_CONFIG" -A 5 2>/dev/null | grep -o '"model"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/' || echo "")
|
|
713
|
+
fi
|
|
714
|
+
|
|
715
|
+
if [ -z "$model" ]; then
|
|
716
|
+
# Final fallback to default model
|
|
717
|
+
model=$(grep -o '"default_model"[[:space:]]*:[[:space:]]*"[^"]*"' "$PHASE_MODELS_CONFIG" | sed 's/.*: *"\([^"]*\)".*/\1/' || echo "claude-3-5-sonnet-latest")
|
|
718
|
+
fi
|
|
719
|
+
|
|
720
|
+
echo "$model"
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
# Setup CCR environment for a specific model
|
|
724
|
+
setup_model_for_phase() {
|
|
725
|
+
local model="$1"
|
|
726
|
+
|
|
727
|
+
# Check if CCR is available
|
|
728
|
+
if ! command -v ccr &> /dev/null; then
|
|
729
|
+
CLAUDE_CMD="claude"
|
|
730
|
+
log "WARN" "CCR not found, using default claude command"
|
|
731
|
+
return 1
|
|
732
|
+
fi
|
|
733
|
+
|
|
734
|
+
# Check if CCR is configured
|
|
735
|
+
if [ ! -f "$HOME/.claude-code-router/config.json" ]; then
|
|
736
|
+
CLAUDE_CMD="claude"
|
|
737
|
+
log "WARN" "CCR config not found at ~/.claude-code-router/config.json"
|
|
738
|
+
return 1
|
|
739
|
+
fi
|
|
740
|
+
|
|
741
|
+
# Parse provider routing from config
|
|
742
|
+
local provider_info=$(grep -A 20 "\"provider_routing\"" "$PHASE_MODELS_CONFIG" 2>/dev/null | grep -A 5 "\"$model\"" | head -6 || echo "")
|
|
743
|
+
|
|
744
|
+
if [ -n "$provider_info" ]; then
|
|
745
|
+
local provider=$(echo "$provider_info" | grep -o '"provider"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"\([^"]*\)".*/\1/' || echo "anthropic")
|
|
746
|
+
local base_url=$(echo "$provider_info" | grep -o '"base_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"\([^"]*\)".*/\1/' || echo "https://api.anthropic.com")
|
|
747
|
+
|
|
748
|
+
# Export CCR environment variables
|
|
749
|
+
export ANTHROPIC_BASE_URL="$base_url"
|
|
750
|
+
export NO_PROXY="127.0.0.1"
|
|
751
|
+
|
|
752
|
+
# Use ccr code wrapper
|
|
753
|
+
CLAUDE_CMD="ccr code --model $model"
|
|
754
|
+
CCR_ACTIVATED=true
|
|
755
|
+
|
|
756
|
+
log "INFO" "Configured CCR for model: $model via $provider"
|
|
757
|
+
return 0
|
|
758
|
+
else
|
|
759
|
+
# Model not in provider routing, try direct CCR routing
|
|
760
|
+
CLAUDE_CMD="ccr code --model $model"
|
|
761
|
+
CCR_ACTIVATED=true
|
|
762
|
+
log "INFO" "Using CCR for model: $model"
|
|
763
|
+
return 0
|
|
764
|
+
fi
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
# Execute claude command with model routing
|
|
769
|
+
execute_claude() {
|
|
770
|
+
local model="$1"
|
|
771
|
+
local prompt="$2"
|
|
772
|
+
shift 2
|
|
773
|
+
|
|
774
|
+
# Setup model-specific environment
|
|
775
|
+
setup_model_for_phase "$model"
|
|
776
|
+
|
|
777
|
+
# Execute with appropriate command
|
|
778
|
+
if [ "$CCR_ACTIVATED" = true ]; then
|
|
779
|
+
echo "$prompt" | $CLAUDE_CMD -p "$@" 2>&1
|
|
780
|
+
else
|
|
781
|
+
echo "$prompt" | $CLAUDE_CMD -p --model "$model" "$@" 2>&1
|
|
782
|
+
fi
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
786
|
+
# Checkpoint Handling
|
|
787
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
788
|
+
|
|
789
|
+
queue_checkpoint() {
|
|
790
|
+
local phase="$1"
|
|
791
|
+
local plan="$2"
|
|
792
|
+
local checkpoint_data="$3"
|
|
793
|
+
|
|
794
|
+
local checkpoint_file="$CHECKPOINT_DIR/pending/phase-${phase}-plan-${plan}.json"
|
|
795
|
+
echo "$checkpoint_data" > "$checkpoint_file"
|
|
796
|
+
|
|
797
|
+
log "CHECKPOINT" "Queued: $checkpoint_file"
|
|
798
|
+
notify "Checkpoint queued: Phase $phase, Plan $plan" "checkpoint"
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
process_approved_checkpoints() {
|
|
802
|
+
mkdir -p "$CHECKPOINT_DIR/processed"
|
|
803
|
+
|
|
804
|
+
for approval in "$CHECKPOINT_DIR/approved/"*.json; do
|
|
805
|
+
[ -f "$approval" ] || continue
|
|
806
|
+
|
|
807
|
+
if grep -q '"approved": false' "$approval" 2>/dev/null; then
|
|
808
|
+
log "INFO" "Checkpoint rejected, skipping: $approval"
|
|
809
|
+
mv "$approval" "$CHECKPOINT_DIR/processed/"
|
|
810
|
+
continue
|
|
811
|
+
fi
|
|
812
|
+
|
|
813
|
+
local basename=$(basename "$approval" .json)
|
|
814
|
+
local phase=$(echo "$basename" | sed -n 's/phase-\([0-9]*\)-.*/\1/p')
|
|
815
|
+
local plan=$(echo "$basename" | sed -n 's/.*plan-\([0-9]*\)/\1/p')
|
|
816
|
+
|
|
817
|
+
if [ -z "$phase" ] || [ -z "$plan" ]; then
|
|
818
|
+
log "WARN" "Could not parse phase/plan from: $approval"
|
|
819
|
+
mv "$approval" "$CHECKPOINT_DIR/processed/"
|
|
820
|
+
continue
|
|
821
|
+
fi
|
|
822
|
+
|
|
823
|
+
log "INFO" "Processing approved checkpoint: Phase $phase, Plan $plan"
|
|
824
|
+
|
|
825
|
+
local user_response=$(grep -o '"response"[[:space:]]*:[[:space:]]*"[^"]*"' "$approval" | sed 's/.*: *"//' | sed 's/"$//' || echo "")
|
|
826
|
+
local continuation_log="$LOG_DIR/continuation-phase${phase}-plan${plan}-$(date +%Y%m%d-%H%M%S).log"
|
|
827
|
+
|
|
828
|
+
add_activity "commit" "continuing from checkpoint"
|
|
829
|
+
|
|
830
|
+
# Get model for checkpoint continuation
|
|
831
|
+
local model=$(get_model_for_phase "$phase" "continuation")
|
|
832
|
+
load_phase_models 2>/dev/null || true
|
|
833
|
+
|
|
834
|
+
execute_claude "$model" "/gsd:execute-plan $phase $plan --continue --checkpoint-response \"$user_response\"" \
|
|
835
|
+
--allowedTools "Read,Write,Edit,Glob,Grep,Bash,Task,TodoWrite,AskUserQuestion" \
|
|
836
|
+
> "$continuation_log"
|
|
837
|
+
|
|
838
|
+
if [ ${PIPESTATUS[1]} -ne 0 ]; then
|
|
839
|
+
log "ERROR" "Continuation failed"
|
|
840
|
+
else
|
|
841
|
+
mv "$approval" "$CHECKPOINT_DIR/processed/"
|
|
842
|
+
track_cost "$continuation_log" "$phase"
|
|
843
|
+
fi
|
|
844
|
+
done
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
848
|
+
# Phase Execution
|
|
849
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
850
|
+
|
|
851
|
+
is_phase_complete() {
|
|
852
|
+
local phase="$1"
|
|
853
|
+
grep -qE "^- \[x\] \*\*Phase $phase" .planning/ROADMAP.md 2>/dev/null
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
execute_phase() {
|
|
857
|
+
local phase="$1"
|
|
858
|
+
local phase_idx="$2"
|
|
859
|
+
local total_phases="$3"
|
|
860
|
+
local attempt=1
|
|
861
|
+
local phase_log="$LOG_DIR/phase-${phase}-$(date +%Y%m%d-%H%M%S).log"
|
|
862
|
+
|
|
863
|
+
# Safety check before starting
|
|
864
|
+
ensure_clean_working_tree "before phase $phase"
|
|
865
|
+
|
|
866
|
+
# Skip completed phases
|
|
867
|
+
if is_phase_complete "$phase"; then
|
|
868
|
+
log "INFO" "Phase $phase already complete, skipping"
|
|
869
|
+
return 0
|
|
870
|
+
fi
|
|
871
|
+
|
|
872
|
+
# Load phase context
|
|
873
|
+
CURRENT_PHASE="$phase"
|
|
874
|
+
load_phase_context "$phase"
|
|
875
|
+
reset_stages
|
|
876
|
+
|
|
877
|
+
# Start activity reader and display
|
|
878
|
+
start_activity_reader "$total_phases" "$phase_idx"
|
|
879
|
+
|
|
880
|
+
# Initial render (hide cursor for clean display)
|
|
881
|
+
if [ -t 1 ]; then
|
|
882
|
+
printf "${CURSOR_HIDE}${CURSOR_HOME}${CURSOR_CLEAR}"
|
|
883
|
+
render_display "$total_phases" "$phase_idx"
|
|
884
|
+
fi
|
|
885
|
+
|
|
886
|
+
while [ $attempt -le $MAX_RETRIES ]; do
|
|
887
|
+
if [ $attempt -gt 1 ]; then
|
|
888
|
+
log "INFO" "Retry $attempt/$MAX_RETRIES for phase $phase"
|
|
889
|
+
add_activity "retry" "attempt $attempt of $MAX_RETRIES"
|
|
890
|
+
fi
|
|
891
|
+
|
|
892
|
+
# Load phase models configuration
|
|
893
|
+
load_phase_models 2>/dev/null || true
|
|
894
|
+
|
|
895
|
+
# Check if phase needs planning
|
|
896
|
+
local phase_dir=$(ls -d .planning/phases/$(printf "%02d" "$phase" 2>/dev/null || echo "$phase")-* 2>/dev/null | head -1)
|
|
897
|
+
|
|
898
|
+
if [ -z "$phase_dir" ] || [ $(ls "$phase_dir"/*-PLAN.md 2>/dev/null | wc -l) -eq 0 ]; then
|
|
899
|
+
log "INFO" "Planning phase $phase"
|
|
900
|
+
|
|
901
|
+
# Get model for phase planning
|
|
902
|
+
local model=$(get_model_for_phase "$phase" "planning")
|
|
903
|
+
|
|
904
|
+
execute_claude "$model" "/gsd:plan-phase $phase" \
|
|
905
|
+
--allowedTools "Read,Write,Edit,Glob,Grep,Bash,Task,TodoWrite,AskUserQuestion" \
|
|
906
|
+
>> "$phase_log"
|
|
907
|
+
|
|
908
|
+
if [ ${PIPESTATUS[0]} -ne 0 ]; then
|
|
909
|
+
log "ERROR" "Planning failed for phase $phase"
|
|
910
|
+
((attempt++))
|
|
911
|
+
sleep 5
|
|
912
|
+
continue
|
|
913
|
+
fi
|
|
914
|
+
|
|
915
|
+
phase_dir=$(ls -d .planning/phases/$(printf "%02d" "$phase" 2>/dev/null || echo "$phase")-* 2>/dev/null | head -1)
|
|
916
|
+
fi
|
|
917
|
+
|
|
918
|
+
# Execution
|
|
919
|
+
log "INFO" "Executing phase $phase"
|
|
920
|
+
|
|
921
|
+
# Get model for phase execution
|
|
922
|
+
local model=$(get_model_for_phase "$phase" "execution")
|
|
923
|
+
|
|
924
|
+
execute_claude "$model" "/gsd:execute-phase $phase" \
|
|
925
|
+
--allowedTools "Read,Write,Edit,Glob,Grep,Bash,Task,TodoWrite,AskUserQuestion" \
|
|
926
|
+
>> "$phase_log"
|
|
927
|
+
|
|
928
|
+
if [ ${PIPESTATUS[1]} -ne 0 ]; then
|
|
929
|
+
log "ERROR" "Execution failed for phase $phase"
|
|
930
|
+
((attempt++))
|
|
931
|
+
sleep 5
|
|
932
|
+
continue
|
|
933
|
+
fi
|
|
934
|
+
|
|
935
|
+
track_cost "$phase_log" "$phase"
|
|
936
|
+
|
|
937
|
+
# Check verification status
|
|
938
|
+
local verification_file=$(ls "$phase_dir"/*-VERIFICATION.md 2>/dev/null | head -1)
|
|
939
|
+
local status="passed"
|
|
940
|
+
|
|
941
|
+
if [ -f "$verification_file" ]; then
|
|
942
|
+
status=$(grep "^status:" "$verification_file" | head -1 | cut -d: -f2 | tr -d ' ')
|
|
943
|
+
fi
|
|
944
|
+
|
|
945
|
+
case "$status" in
|
|
946
|
+
"passed")
|
|
947
|
+
complete_current_stage
|
|
948
|
+
stop_activity_reader
|
|
949
|
+
ensure_clean_working_tree "after phase $phase"
|
|
950
|
+
notify "Phase $phase complete" "success"
|
|
951
|
+
return 0
|
|
952
|
+
;;
|
|
953
|
+
|
|
954
|
+
"gaps_found")
|
|
955
|
+
log "INFO" "Gaps found in phase $phase, planning closure"
|
|
956
|
+
|
|
957
|
+
# Get model for gap closure
|
|
958
|
+
local model=$(get_model_for_phase "$phase" "gaps")
|
|
959
|
+
|
|
960
|
+
execute_claude "$model" "/gsd:plan-phase $phase --gaps" \
|
|
961
|
+
--allowedTools "Read,Write,Edit,Glob,Grep,Bash,Task,TodoWrite,AskUserQuestion" \
|
|
962
|
+
>> "$phase_log"
|
|
963
|
+
|
|
964
|
+
if [ ${PIPESTATUS[0]} -ne 0 ]; then
|
|
965
|
+
((attempt++))
|
|
966
|
+
continue
|
|
967
|
+
fi
|
|
968
|
+
|
|
969
|
+
execute_claude "$model" "/gsd:execute-phase $phase --gaps-only" \
|
|
970
|
+
--allowedTools "Read,Write,Edit,Glob,Grep,Bash,Task,TodoWrite,AskUserQuestion" \
|
|
971
|
+
>> "$phase_log"
|
|
972
|
+
|
|
973
|
+
if [ ${PIPESTATUS[1]} -ne 0 ]; then
|
|
974
|
+
((attempt++))
|
|
975
|
+
continue
|
|
976
|
+
fi
|
|
977
|
+
|
|
978
|
+
track_cost "$phase_log" "$phase"
|
|
979
|
+
|
|
980
|
+
status=$(grep "^status:" "$verification_file" 2>/dev/null | tail -1 | cut -d: -f2 | tr -d ' ')
|
|
981
|
+
|
|
982
|
+
if [ "$status" = "passed" ]; then
|
|
983
|
+
complete_current_stage
|
|
984
|
+
stop_activity_reader
|
|
985
|
+
ensure_clean_working_tree "after phase $phase gap closure"
|
|
986
|
+
notify "Phase $phase complete (after gap closure)" "success"
|
|
987
|
+
return 0
|
|
988
|
+
else
|
|
989
|
+
((attempt++))
|
|
990
|
+
continue
|
|
991
|
+
fi
|
|
992
|
+
;;
|
|
993
|
+
|
|
994
|
+
"human_needed")
|
|
995
|
+
if [ "$CHECKPOINT_MODE" = "queue" ]; then
|
|
996
|
+
queue_checkpoint "$phase" "verification" "{\"type\": \"human_verification\", \"phase\": \"$phase\"}"
|
|
997
|
+
fi
|
|
998
|
+
complete_current_stage
|
|
999
|
+
stop_activity_reader
|
|
1000
|
+
ensure_clean_working_tree "after phase $phase (human verification queued)"
|
|
1001
|
+
return 0
|
|
1002
|
+
;;
|
|
1003
|
+
|
|
1004
|
+
*)
|
|
1005
|
+
complete_current_stage
|
|
1006
|
+
stop_activity_reader
|
|
1007
|
+
ensure_clean_working_tree "after phase $phase"
|
|
1008
|
+
return 0
|
|
1009
|
+
;;
|
|
1010
|
+
esac
|
|
1011
|
+
done
|
|
1012
|
+
|
|
1013
|
+
# All retries exhausted
|
|
1014
|
+
stop_activity_reader
|
|
1015
|
+
ensure_clean_working_tree "after phase $phase failure"
|
|
1016
|
+
notify "Phase $phase FAILED after $MAX_RETRIES attempts" "error"
|
|
1017
|
+
return 1
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
1021
|
+
# Main
|
|
1022
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
1023
|
+
|
|
1024
|
+
main() {
|
|
1025
|
+
local total_phases=${#PHASES[@]}
|
|
1026
|
+
local start_time=$(date +%s)
|
|
1027
|
+
|
|
1028
|
+
# Startup banner
|
|
1029
|
+
clear 2>/dev/null || true
|
|
1030
|
+
|
|
1031
|
+
printf "\n"
|
|
1032
|
+
printf "${C_BOLD}${C_CYAN}"
|
|
1033
|
+
printf " ██████╗ ███████╗██████╗ \n"
|
|
1034
|
+
printf " ██╔════╝ ██╔════╝██╔══██╗\n"
|
|
1035
|
+
printf " ██║ ███╗███████╗██║ ██║\n"
|
|
1036
|
+
printf " ██║ ██║╚════██║██║ ██║\n"
|
|
1037
|
+
printf " ╚██████╔╝███████║██████╔╝\n"
|
|
1038
|
+
printf " ╚═════╝ ╚══════╝╚═════╝ \n"
|
|
1039
|
+
printf "${C_RESET}\n"
|
|
1040
|
+
printf "${C_BOLD}${C_WHITE} AUTOPILOT${C_RESET}\n"
|
|
1041
|
+
printf "${C_DIM} %s${C_RESET}\n" "$PROJECT_NAME"
|
|
1042
|
+
printf "\n"
|
|
1043
|
+
printf "${C_DIM} Phases: %s${C_RESET}\n" "${PHASES[*]}"
|
|
1044
|
+
printf "${C_DIM} Retries: %s per phase${C_RESET}\n" "$MAX_RETRIES"
|
|
1045
|
+
printf "${C_DIM} Budget: \$%s${C_RESET}\n" "$BUDGET_LIMIT"
|
|
1046
|
+
printf "${C_DIM} Checkpoints: %s${C_RESET}\n" "$CHECKPOINT_MODE"
|
|
1047
|
+
printf "\n"
|
|
1048
|
+
printf "${C_DIM} Starting in 3 seconds...${C_RESET}\n"
|
|
1049
|
+
|
|
1050
|
+
sleep 3
|
|
1051
|
+
|
|
1052
|
+
log "INFO" "Autopilot started for $PROJECT_NAME"
|
|
1053
|
+
notify "Autopilot started" "info"
|
|
1054
|
+
|
|
1055
|
+
local remaining_phases=("${PHASES[@]}")
|
|
1056
|
+
local phase_idx=0
|
|
1057
|
+
|
|
1058
|
+
for phase in "${PHASES[@]}"; do
|
|
1059
|
+
process_approved_checkpoints
|
|
1060
|
+
|
|
1061
|
+
remaining_phases=("${remaining_phases[@]:1}")
|
|
1062
|
+
local remaining_str="${remaining_phases[*]:-none}"
|
|
1063
|
+
|
|
1064
|
+
update_autopilot_state "running" "$phase" "$remaining_str"
|
|
1065
|
+
|
|
1066
|
+
if ! execute_phase "$phase" "$phase_idx" "$total_phases"; then
|
|
1067
|
+
update_autopilot_state "failed" "$phase" "$remaining_str" "phase_${phase}_failed"
|
|
1068
|
+
|
|
1069
|
+
if [ -t 1 ]; then
|
|
1070
|
+
printf "${CURSOR_SHOW}"
|
|
1071
|
+
printf "\n${C_RED}${C_BOLD}Autopilot stopped at phase $phase${C_RESET}\n"
|
|
1072
|
+
fi
|
|
1073
|
+
|
|
1074
|
+
notify "Autopilot STOPPED at phase $phase" "error"
|
|
1075
|
+
exit 1
|
|
1076
|
+
fi
|
|
1077
|
+
|
|
1078
|
+
((phase_idx++))
|
|
1079
|
+
done
|
|
1080
|
+
|
|
1081
|
+
# Final checkpoint processing
|
|
1082
|
+
process_approved_checkpoints
|
|
1083
|
+
|
|
1084
|
+
# Final safety check
|
|
1085
|
+
ensure_clean_working_tree "autopilot completion"
|
|
1086
|
+
|
|
1087
|
+
# Completion
|
|
1088
|
+
local total_time=$(($(date +%s) - start_time))
|
|
1089
|
+
local total_min=$((total_time / 60))
|
|
1090
|
+
local total_sec=$((total_time % 60))
|
|
1091
|
+
|
|
1092
|
+
local total_dollars=$((TOTAL_COST_CENTS / 100))
|
|
1093
|
+
local total_remainder=$((TOTAL_COST_CENTS % 100))
|
|
1094
|
+
local total_cost=$(printf "%d.%02d" $total_dollars $total_remainder)
|
|
1095
|
+
|
|
1096
|
+
update_autopilot_state "completed" "all" "none"
|
|
1097
|
+
|
|
1098
|
+
if [ -t 1 ]; then
|
|
1099
|
+
printf "${CURSOR_SHOW}"
|
|
1100
|
+
clear
|
|
1101
|
+
|
|
1102
|
+
printf "\n"
|
|
1103
|
+
printf "${C_BOLD}${C_GREEN}"
|
|
1104
|
+
printf " ╔═══════════════════════════════════════════════════╗\n"
|
|
1105
|
+
printf " ║ MILESTONE COMPLETE ║\n"
|
|
1106
|
+
printf " ╚═══════════════════════════════════════════════════╝\n"
|
|
1107
|
+
printf "${C_RESET}\n"
|
|
1108
|
+
|
|
1109
|
+
printf "${C_WHITE} Phases:${C_RESET} %d completed\n" "$total_phases"
|
|
1110
|
+
printf "${C_WHITE} Time:${C_RESET} %dm %ds\n" "$total_min" "$total_sec"
|
|
1111
|
+
printf "${C_WHITE} Tokens:${C_RESET} %s\n" "$TOTAL_TOKENS"
|
|
1112
|
+
printf "${C_WHITE} Cost:${C_RESET} \$%s\n" "$total_cost"
|
|
1113
|
+
printf "\n"
|
|
1114
|
+
fi
|
|
1115
|
+
|
|
1116
|
+
log "SUCCESS" "Milestone complete: $total_phases phases, ${total_min}m ${total_sec}s, \$$total_cost"
|
|
1117
|
+
|
|
1118
|
+
# Complete milestone
|
|
1119
|
+
echo "/gsd:complete-milestone" | claude -p \
|
|
1120
|
+
--allowedTools "Read,Write,Edit,Glob,Grep,Bash,AskUserQuestion" \
|
|
1121
|
+
2>&1 | tee -a "$LOG_DIR/milestone-complete.log"
|
|
1122
|
+
|
|
1123
|
+
notify "Milestone COMPLETE! $total_phases phases, \$$total_cost" "success"
|
|
1124
|
+
|
|
1125
|
+
# Check for pending checkpoints
|
|
1126
|
+
local pending_count=$(ls "$CHECKPOINT_DIR/pending/"*.json 2>/dev/null | wc -l | tr -d ' ')
|
|
1127
|
+
if [ "$pending_count" -gt 0 ]; then
|
|
1128
|
+
printf "\n"
|
|
1129
|
+
printf "${C_YELLOW} Pending checkpoints: %d${C_RESET}\n" "$pending_count"
|
|
1130
|
+
printf "${C_DIM} Run: /gsd:checkpoints${C_RESET}\n"
|
|
1131
|
+
fi
|
|
1132
|
+
|
|
1133
|
+
printf "\n"
|
|
1134
|
+
printf "${C_DIM} Logs: %s/${C_RESET}\n" "$LOG_DIR"
|
|
1135
|
+
printf "\n"
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
1139
|
+
# Run
|
|
1140
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
1141
|
+
|
|
1142
|
+
main "$@"
|