prizmkit 1.0.0 → 1.0.1
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/bundled/VERSION.json +5 -0
- package/bundled/adapters/claude/agent-adapter.js +108 -0
- package/bundled/adapters/claude/command-adapter.js +104 -0
- package/bundled/adapters/claude/paths.js +35 -0
- package/bundled/adapters/claude/rules-adapter.js +77 -0
- package/bundled/adapters/claude/settings-adapter.js +73 -0
- package/bundled/adapters/claude/team-adapter.js +183 -0
- package/bundled/adapters/codebuddy/agent-adapter.js +43 -0
- package/bundled/adapters/codebuddy/paths.js +29 -0
- package/bundled/adapters/codebuddy/settings-adapter.js +47 -0
- package/bundled/adapters/codebuddy/skill-adapter.js +68 -0
- package/bundled/adapters/codebuddy/team-adapter.js +46 -0
- package/bundled/adapters/shared/frontmatter.js +77 -0
- package/bundled/agents/prizm-dev-team-coordinator.md +142 -0
- package/bundled/agents/prizm-dev-team-dev.md +99 -0
- package/bundled/agents/prizm-dev-team-pm.md +114 -0
- package/bundled/agents/prizm-dev-team-reviewer.md +119 -0
- package/bundled/dev-pipeline/README.md +482 -0
- package/bundled/dev-pipeline/assets/feature-list-example.json +147 -0
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +138 -0
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +425 -0
- package/bundled/dev-pipeline/launch-daemon.sh +549 -0
- package/bundled/dev-pipeline/reset-feature.sh +209 -0
- package/bundled/dev-pipeline/retry-bug.sh +344 -0
- package/bundled/dev-pipeline/retry-feature.sh +338 -0
- package/bundled/dev-pipeline/run-bugfix.sh +638 -0
- package/bundled/dev-pipeline/run.sh +845 -0
- package/bundled/dev-pipeline/scripts/check-session-status.py +158 -0
- package/bundled/dev-pipeline/scripts/detect-stuck.py +385 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +598 -0
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +402 -0
- package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +294 -0
- package/bundled/dev-pipeline/scripts/init-dev-team.py +134 -0
- package/bundled/dev-pipeline/scripts/init-pipeline.py +335 -0
- package/bundled/dev-pipeline/scripts/update-bug-status.py +748 -0
- package/bundled/dev-pipeline/scripts/update-feature-status.py +1076 -0
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +262 -0
- package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +159 -0
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +291 -0
- package/bundled/dev-pipeline/templates/feature-list-schema.json +112 -0
- package/bundled/dev-pipeline/templates/session-status-schema.json +77 -0
- package/bundled/skills/_metadata.json +267 -0
- package/bundled/skills/app-planner/SKILL.md +580 -0
- package/bundled/skills/app-planner/assets/planning-guide.md +313 -0
- package/bundled/skills/app-planner/scripts/validate-and-generate.py +758 -0
- package/bundled/skills/bug-planner/SKILL.md +235 -0
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +252 -0
- package/bundled/skills/dev-pipeline-launcher/SKILL.md +223 -0
- package/bundled/skills/prizm-kit/SKILL.md +151 -0
- package/bundled/skills/prizm-kit/assets/claude-md-template.md +38 -0
- package/bundled/skills/prizm-kit/assets/codebuddy-md-template.md +35 -0
- package/bundled/skills/prizm-kit/assets/hooks/prizm-commit-hook.json +15 -0
- package/bundled/skills/prizmkit-adr-manager/SKILL.md +68 -0
- package/bundled/skills/prizmkit-adr-manager/assets/adr-template.md +26 -0
- package/bundled/skills/prizmkit-analyze/SKILL.md +194 -0
- package/bundled/skills/prizmkit-api-doc-generator/SKILL.md +56 -0
- package/bundled/skills/prizmkit-bug-fix-workflow/SKILL.md +351 -0
- package/bundled/skills/prizmkit-bug-reproducer/SKILL.md +62 -0
- package/bundled/skills/prizmkit-ci-cd-generator/SKILL.md +54 -0
- package/bundled/skills/prizmkit-clarify/SKILL.md +52 -0
- package/bundled/skills/prizmkit-code-review/SKILL.md +70 -0
- package/bundled/skills/prizmkit-committer/SKILL.md +117 -0
- package/bundled/skills/prizmkit-db-migration/SKILL.md +65 -0
- package/bundled/skills/prizmkit-dependency-health/SKILL.md +123 -0
- package/bundled/skills/prizmkit-deployment-strategy/SKILL.md +58 -0
- package/bundled/skills/prizmkit-error-triage/SKILL.md +55 -0
- package/bundled/skills/prizmkit-implement/SKILL.md +47 -0
- package/bundled/skills/prizmkit-init/SKILL.md +156 -0
- package/bundled/skills/prizmkit-log-analyzer/SKILL.md +55 -0
- package/bundled/skills/prizmkit-monitoring-setup/SKILL.md +75 -0
- package/bundled/skills/prizmkit-onboarding-generator/SKILL.md +70 -0
- package/bundled/skills/prizmkit-perf-profiler/SKILL.md +55 -0
- package/bundled/skills/prizmkit-plan/SKILL.md +54 -0
- package/bundled/skills/prizmkit-plan/assets/plan-template.md +37 -0
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +140 -0
- package/bundled/skills/prizmkit-prizm-docs/assets/PRIZM-SPEC.md +943 -0
- package/bundled/skills/prizmkit-retrospective/SKILL.md +79 -0
- package/bundled/skills/prizmkit-security-audit/SKILL.md +130 -0
- package/bundled/skills/prizmkit-specify/SKILL.md +52 -0
- package/bundled/skills/prizmkit-specify/assets/spec-template.md +37 -0
- package/bundled/skills/prizmkit-summarize/SKILL.md +51 -0
- package/bundled/skills/prizmkit-summarize/assets/registry-template.md +18 -0
- package/bundled/skills/prizmkit-tasks/SKILL.md +50 -0
- package/bundled/skills/prizmkit-tasks/assets/tasks-template.md +21 -0
- package/bundled/skills/prizmkit-tech-debt-tracker/SKILL.md +139 -0
- package/bundled/team/prizm-dev-team.json +47 -0
- package/bundled/templates/claude-md-template.md +38 -0
- package/bundled/templates/codebuddy-md-template.md +35 -0
- package/package.json +2 -1
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# ============================================================
|
|
5
|
+
# dev-pipeline/run.sh - Autonomous Dev Pipeline Runner
|
|
6
|
+
#
|
|
7
|
+
# Drives the prizm-dev-team multi-agent team through iterative
|
|
8
|
+
# AI CLI sessions (CodeBuddy or Claude Code) to build a complete app
|
|
9
|
+
# from a feature list.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# ./run.sh run [feature-list.json] Run all features
|
|
13
|
+
# ./run.sh run <feature-id> [options] Run a single feature
|
|
14
|
+
# ./run.sh status [feature-list.json] Show pipeline status
|
|
15
|
+
# ./run.sh reset Clear all state
|
|
16
|
+
#
|
|
17
|
+
# Environment Variables:
|
|
18
|
+
# MAX_RETRIES Max retries per feature (default: 3)
|
|
19
|
+
# SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
|
|
20
|
+
# AI_CLI AI CLI command name (auto-detected: cbc or claude)
|
|
21
|
+
# CODEBUDDY_CLI Legacy alias for AI_CLI (deprecated, use AI_CLI instead)
|
|
22
|
+
# PRIZMKIT_PLATFORM Force platform: 'codebuddy' or 'claude' (auto-detected)
|
|
23
|
+
# VERBOSE Set to 1 to enable --verbose on AI CLI (shows subagent output)
|
|
24
|
+
# HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
|
|
25
|
+
# HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)
|
|
26
|
+
# ============================================================
|
|
27
|
+
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
STATE_DIR="$SCRIPT_DIR/state"
|
|
30
|
+
SCRIPTS_DIR="$SCRIPT_DIR/scripts"
|
|
31
|
+
|
|
32
|
+
# Configuration (override via environment variables)
|
|
33
|
+
MAX_RETRIES=${MAX_RETRIES:-3}
|
|
34
|
+
SESSION_TIMEOUT=${SESSION_TIMEOUT:-0}
|
|
35
|
+
HEARTBEAT_STALE_THRESHOLD=${HEARTBEAT_STALE_THRESHOLD:-600}
|
|
36
|
+
HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
|
|
37
|
+
VERBOSE=${VERBOSE:-0}
|
|
38
|
+
|
|
39
|
+
# AI CLI detection: AI_CLI > CODEBUDDY_CLI > auto-detect > error
|
|
40
|
+
if [[ -n "${AI_CLI:-}" ]]; then
|
|
41
|
+
CLI_CMD="$AI_CLI"
|
|
42
|
+
elif [[ -n "${CODEBUDDY_CLI:-}" ]]; then
|
|
43
|
+
CLI_CMD="$CODEBUDDY_CLI"
|
|
44
|
+
elif command -v cbc &>/dev/null; then
|
|
45
|
+
CLI_CMD="cbc"
|
|
46
|
+
elif command -v claude &>/dev/null; then
|
|
47
|
+
CLI_CMD="claude"
|
|
48
|
+
else
|
|
49
|
+
echo "ERROR: No AI CLI found. Install CodeBuddy (cbc) or Claude Code (claude)." >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Platform detection
|
|
54
|
+
if [[ -n "${PRIZMKIT_PLATFORM:-}" ]]; then
|
|
55
|
+
PLATFORM="$PRIZMKIT_PLATFORM"
|
|
56
|
+
elif [[ "$CLI_CMD" == *"claude"* ]]; then
|
|
57
|
+
PLATFORM="claude"
|
|
58
|
+
else
|
|
59
|
+
PLATFORM="codebuddy"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
export PRIZMKIT_PLATFORM="$PLATFORM"
|
|
63
|
+
|
|
64
|
+
# Feature list path (set in main, used by cleanup trap)
|
|
65
|
+
FEATURE_LIST=""
|
|
66
|
+
|
|
67
|
+
# Colors for output
|
|
68
|
+
RED='\033[0;31m'
|
|
69
|
+
GREEN='\033[0;32m'
|
|
70
|
+
YELLOW='\033[1;33m'
|
|
71
|
+
BLUE='\033[0;34m'
|
|
72
|
+
BOLD='\033[1m'
|
|
73
|
+
NC='\033[0m'
|
|
74
|
+
|
|
75
|
+
log_info() { echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
76
|
+
log_warn() { echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
77
|
+
log_error() { echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
78
|
+
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
79
|
+
|
|
80
|
+
# ============================================================
|
|
81
|
+
# Shared: Spawn an AI CLI session and wait for result
|
|
82
|
+
# ============================================================
|
|
83
|
+
|
|
84
|
+
# Spawns an AI CLI session with heartbeat + timeout, waits for completion,
|
|
85
|
+
# checks session status, and updates feature status.
|
|
86
|
+
#
|
|
87
|
+
# Arguments:
|
|
88
|
+
# $1 - feature_id
|
|
89
|
+
# $2 - feature_list (absolute path)
|
|
90
|
+
# $3 - session_id
|
|
91
|
+
# $4 - bootstrap_prompt (path)
|
|
92
|
+
# $5 - session_dir
|
|
93
|
+
# $6 - max_retries (for status update)
|
|
94
|
+
spawn_and_wait_session() {
|
|
95
|
+
local feature_id="$1"
|
|
96
|
+
local feature_list="$2"
|
|
97
|
+
local session_id="$3"
|
|
98
|
+
local bootstrap_prompt="$4"
|
|
99
|
+
local session_dir="$5"
|
|
100
|
+
local max_retries="$6"
|
|
101
|
+
|
|
102
|
+
local session_log="$session_dir/logs/session.log"
|
|
103
|
+
|
|
104
|
+
# Spawn AI CLI session
|
|
105
|
+
local verbose_flag=""
|
|
106
|
+
if [[ "$VERBOSE" == "1" ]]; then
|
|
107
|
+
verbose_flag="--verbose"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
case "$CLI_CMD" in
|
|
111
|
+
*claude*)
|
|
112
|
+
# Claude Code: prompt via -p argument, --yes for auto-accept
|
|
113
|
+
"$CLI_CMD" \
|
|
114
|
+
--print \
|
|
115
|
+
-p "$(cat "$bootstrap_prompt")" \
|
|
116
|
+
--yes \
|
|
117
|
+
$verbose_flag \
|
|
118
|
+
> "$session_log" 2>&1 &
|
|
119
|
+
;;
|
|
120
|
+
*)
|
|
121
|
+
# CodeBuddy (cbc) and others: prompt via stdin
|
|
122
|
+
"$CLI_CMD" \
|
|
123
|
+
--print \
|
|
124
|
+
-y \
|
|
125
|
+
$verbose_flag \
|
|
126
|
+
< "$bootstrap_prompt" \
|
|
127
|
+
> "$session_log" 2>&1 &
|
|
128
|
+
;;
|
|
129
|
+
esac
|
|
130
|
+
local cbc_pid=$!
|
|
131
|
+
|
|
132
|
+
# Timeout watchdog (only if SESSION_TIMEOUT > 0)
|
|
133
|
+
local watcher_pid=""
|
|
134
|
+
if [[ $SESSION_TIMEOUT -gt 0 ]]; then
|
|
135
|
+
( sleep "$SESSION_TIMEOUT" && kill -TERM "$cbc_pid" 2>/dev/null ) &
|
|
136
|
+
watcher_pid=$!
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Heartbeat monitor
|
|
140
|
+
local heartbeat_interval=$HEARTBEAT_INTERVAL
|
|
141
|
+
(
|
|
142
|
+
local elapsed=0
|
|
143
|
+
local prev_size=0
|
|
144
|
+
while kill -0 "$cbc_pid" 2>/dev/null; do
|
|
145
|
+
sleep "$heartbeat_interval"
|
|
146
|
+
elapsed=$((elapsed + heartbeat_interval))
|
|
147
|
+
kill -0 "$cbc_pid" 2>/dev/null || break
|
|
148
|
+
|
|
149
|
+
local cur_size=0
|
|
150
|
+
if [[ -f "$session_log" ]]; then
|
|
151
|
+
cur_size=$(wc -c < "$session_log" 2>/dev/null || echo 0)
|
|
152
|
+
cur_size=$(echo "$cur_size" | tr -d ' ')
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
local growth=$((cur_size - prev_size))
|
|
156
|
+
prev_size=$cur_size
|
|
157
|
+
|
|
158
|
+
local size_display
|
|
159
|
+
if [[ $cur_size -gt 1048576 ]]; then
|
|
160
|
+
size_display="$((cur_size / 1048576))MB"
|
|
161
|
+
elif [[ $cur_size -gt 1024 ]]; then
|
|
162
|
+
size_display="$((cur_size / 1024))KB"
|
|
163
|
+
else
|
|
164
|
+
size_display="${cur_size}B"
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
local mins=$((elapsed / 60))
|
|
168
|
+
local secs=$((elapsed % 60))
|
|
169
|
+
|
|
170
|
+
local last_activity=""
|
|
171
|
+
if [[ -f "$session_log" ]]; then
|
|
172
|
+
last_activity=$(tail -20 "$session_log" 2>/dev/null | grep -v '^$' | tail -1 | cut -c1-80 || echo "")
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
local status_icon
|
|
176
|
+
if [[ $growth -gt 0 ]]; then
|
|
177
|
+
status_icon="${GREEN}▶${NC}"
|
|
178
|
+
else
|
|
179
|
+
status_icon="${YELLOW}⏸${NC}"
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
echo -e " ${status_icon} ${BLUE}[HEARTBEAT]${NC} ${mins}m${secs}s elapsed | log: ${size_display} (+${growth}B) | ${last_activity}"
|
|
183
|
+
done
|
|
184
|
+
) &
|
|
185
|
+
local heartbeat_pid=$!
|
|
186
|
+
|
|
187
|
+
# Wait for AI CLI to finish
|
|
188
|
+
local exit_code=0
|
|
189
|
+
if wait "$cbc_pid" 2>/dev/null; then
|
|
190
|
+
exit_code=0
|
|
191
|
+
else
|
|
192
|
+
exit_code=$?
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# Clean up watcher and heartbeat
|
|
196
|
+
[[ -n "$watcher_pid" ]] && kill "$watcher_pid" 2>/dev/null || true
|
|
197
|
+
kill "$heartbeat_pid" 2>/dev/null || true
|
|
198
|
+
[[ -n "$watcher_pid" ]] && wait "$watcher_pid" 2>/dev/null || true
|
|
199
|
+
wait "$heartbeat_pid" 2>/dev/null || true
|
|
200
|
+
|
|
201
|
+
# Map SIGTERM (143) to timeout code 124
|
|
202
|
+
if [[ $exit_code -eq 143 ]]; then
|
|
203
|
+
exit_code=124
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# Show final session summary
|
|
207
|
+
if [[ -f "$session_log" ]]; then
|
|
208
|
+
local final_size=$(wc -c < "$session_log" 2>/dev/null | tr -d ' ')
|
|
209
|
+
local final_lines=$(wc -l < "$session_log" 2>/dev/null | tr -d ' ')
|
|
210
|
+
log_info "Session log: $final_lines lines, $((final_size / 1024))KB"
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Check session outcome
|
|
214
|
+
local session_status_file="$session_dir/session-status.json"
|
|
215
|
+
local session_status
|
|
216
|
+
|
|
217
|
+
if [[ $exit_code -eq 124 ]]; then
|
|
218
|
+
log_warn "Session timed out after ${SESSION_TIMEOUT}s"
|
|
219
|
+
session_status="timed_out"
|
|
220
|
+
elif [[ -f "$session_status_file" ]]; then
|
|
221
|
+
session_status=$(python3 "$SCRIPTS_DIR/check-session-status.py" \
|
|
222
|
+
--status-file "$session_status_file" 2>/dev/null) || session_status="crashed"
|
|
223
|
+
else
|
|
224
|
+
log_warn "Session ended without status file — treating as crashed"
|
|
225
|
+
session_status="crashed"
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
log_info "Session result: $session_status"
|
|
229
|
+
|
|
230
|
+
# Update feature status
|
|
231
|
+
python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
232
|
+
--feature-list "$feature_list" \
|
|
233
|
+
--state-dir "$STATE_DIR" \
|
|
234
|
+
--feature-id "$feature_id" \
|
|
235
|
+
--session-status "$session_status" \
|
|
236
|
+
--session-id "$session_id" \
|
|
237
|
+
--max-retries "$max_retries" \
|
|
238
|
+
--action update >/dev/null 2>&1 || true
|
|
239
|
+
|
|
240
|
+
# Return status via global variable (avoids $() swallowing stdout)
|
|
241
|
+
_SPAWN_RESULT="$session_status"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
# ============================================================
|
|
245
|
+
# Graceful Shutdown
|
|
246
|
+
# ============================================================
|
|
247
|
+
|
|
248
|
+
cleanup() {
|
|
249
|
+
echo ""
|
|
250
|
+
log_warn "Received interrupt signal. Saving state..."
|
|
251
|
+
|
|
252
|
+
if [[ -n "$FEATURE_LIST" && -f "$FEATURE_LIST" ]]; then
|
|
253
|
+
python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
254
|
+
--feature-list "$FEATURE_LIST" \
|
|
255
|
+
--state-dir "$STATE_DIR" \
|
|
256
|
+
--action pause 2>/dev/null || true
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
log_info "Pipeline paused. Run './run.sh run' to resume."
|
|
260
|
+
exit 130
|
|
261
|
+
}
|
|
262
|
+
trap cleanup SIGINT SIGTERM
|
|
263
|
+
|
|
264
|
+
# ============================================================
|
|
265
|
+
# Dependency Check
|
|
266
|
+
# ============================================================
|
|
267
|
+
|
|
268
|
+
check_dependencies() {
|
|
269
|
+
# Check for jq
|
|
270
|
+
if ! command -v jq &>/dev/null; then
|
|
271
|
+
log_error "jq is required but not installed. Install with: brew install jq"
|
|
272
|
+
exit 1
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
# Check for python3
|
|
276
|
+
if ! command -v python3 &>/dev/null; then
|
|
277
|
+
log_error "python3 is required but not installed."
|
|
278
|
+
exit 1
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
# Check for AI CLI
|
|
282
|
+
if ! command -v "$CLI_CMD" &>/dev/null; then
|
|
283
|
+
log_warn "AI CLI '$CLI_CMD' not found in PATH."
|
|
284
|
+
log_warn "Set AI_CLI environment variable to the correct command."
|
|
285
|
+
log_warn "Continuing anyway (will fail when spawning sessions)..."
|
|
286
|
+
fi
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# ============================================================
|
|
290
|
+
# run-one: Run a single feature with full control
|
|
291
|
+
# ============================================================
|
|
292
|
+
|
|
293
|
+
run_one() {
|
|
294
|
+
local feature_id=""
|
|
295
|
+
local feature_list=""
|
|
296
|
+
local dry_run=false
|
|
297
|
+
local resume_phase=""
|
|
298
|
+
local mode_override=""
|
|
299
|
+
local do_clean=false
|
|
300
|
+
local no_reset=false
|
|
301
|
+
|
|
302
|
+
# Parse arguments
|
|
303
|
+
while [[ $# -gt 0 ]]; do
|
|
304
|
+
case "$1" in
|
|
305
|
+
--dry-run)
|
|
306
|
+
dry_run=true
|
|
307
|
+
shift
|
|
308
|
+
;;
|
|
309
|
+
--resume-phase)
|
|
310
|
+
shift
|
|
311
|
+
if [[ $# -eq 0 ]]; then
|
|
312
|
+
log_error "--resume-phase requires a value"
|
|
313
|
+
exit 1
|
|
314
|
+
fi
|
|
315
|
+
resume_phase="$1"
|
|
316
|
+
shift
|
|
317
|
+
;;
|
|
318
|
+
--mode)
|
|
319
|
+
shift
|
|
320
|
+
if [[ $# -eq 0 ]]; then
|
|
321
|
+
log_error "--mode requires a value (lite|standard|full)"
|
|
322
|
+
exit 1
|
|
323
|
+
fi
|
|
324
|
+
case "$1" in
|
|
325
|
+
lite|standard|full)
|
|
326
|
+
mode_override="$1"
|
|
327
|
+
;;
|
|
328
|
+
*)
|
|
329
|
+
log_error "Invalid mode: $1 (must be lite, standard, or full)"
|
|
330
|
+
exit 1
|
|
331
|
+
;;
|
|
332
|
+
esac
|
|
333
|
+
shift
|
|
334
|
+
;;
|
|
335
|
+
--clean)
|
|
336
|
+
do_clean=true
|
|
337
|
+
shift
|
|
338
|
+
;;
|
|
339
|
+
--no-reset)
|
|
340
|
+
no_reset=true
|
|
341
|
+
shift
|
|
342
|
+
;;
|
|
343
|
+
--timeout)
|
|
344
|
+
shift
|
|
345
|
+
if [[ $# -eq 0 ]]; then
|
|
346
|
+
log_error "--timeout requires a value in seconds"
|
|
347
|
+
exit 1
|
|
348
|
+
fi
|
|
349
|
+
SESSION_TIMEOUT="$1"
|
|
350
|
+
shift
|
|
351
|
+
;;
|
|
352
|
+
F-*|f-*)
|
|
353
|
+
feature_id="$1"
|
|
354
|
+
shift
|
|
355
|
+
;;
|
|
356
|
+
*)
|
|
357
|
+
feature_list="$1"
|
|
358
|
+
shift
|
|
359
|
+
;;
|
|
360
|
+
esac
|
|
361
|
+
done
|
|
362
|
+
|
|
363
|
+
# Validate required args
|
|
364
|
+
if [[ -z "$feature_id" ]]; then
|
|
365
|
+
log_error "Feature ID is required (e.g. F-007)"
|
|
366
|
+
echo ""
|
|
367
|
+
show_help
|
|
368
|
+
exit 1
|
|
369
|
+
fi
|
|
370
|
+
|
|
371
|
+
# Default feature list
|
|
372
|
+
if [[ -z "$feature_list" ]]; then
|
|
373
|
+
feature_list="feature-list.json"
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
# Resolve to absolute path
|
|
377
|
+
if [[ ! "$feature_list" = /* ]]; then
|
|
378
|
+
feature_list="$(pwd)/$feature_list"
|
|
379
|
+
fi
|
|
380
|
+
|
|
381
|
+
FEATURE_LIST="$feature_list"
|
|
382
|
+
|
|
383
|
+
# Default resume phase
|
|
384
|
+
if [[ -z "$resume_phase" ]]; then
|
|
385
|
+
resume_phase="null"
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
# Validation
|
|
389
|
+
if [[ ! -f "$feature_list" ]]; then
|
|
390
|
+
log_error "Feature list not found: $feature_list"
|
|
391
|
+
exit 1
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
|
|
395
|
+
log_error "No pipeline state found. Run './run.sh run' first to initialize."
|
|
396
|
+
exit 1
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
check_dependencies
|
|
400
|
+
|
|
401
|
+
# Verify feature exists
|
|
402
|
+
local feature_title
|
|
403
|
+
feature_title=$(python3 -c "
|
|
404
|
+
import json, sys
|
|
405
|
+
with open('$feature_list') as f:
|
|
406
|
+
data = json.load(f)
|
|
407
|
+
for feat in data.get('features', []):
|
|
408
|
+
if feat.get('id') == '$feature_id':
|
|
409
|
+
print(feat.get('title', ''))
|
|
410
|
+
sys.exit(0)
|
|
411
|
+
sys.exit(1)
|
|
412
|
+
" 2>/dev/null) || {
|
|
413
|
+
log_error "Feature $feature_id not found in $feature_list"
|
|
414
|
+
exit 1
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
# Optional Clean
|
|
418
|
+
if [[ "$do_clean" == true ]]; then
|
|
419
|
+
if [[ "$dry_run" == true ]]; then
|
|
420
|
+
log_warn "Dry-run mode: --clean ignored (no artifacts will be deleted)"
|
|
421
|
+
else
|
|
422
|
+
log_info "Cleaning artifacts for $feature_id..."
|
|
423
|
+
|
|
424
|
+
local feature_slug
|
|
425
|
+
feature_slug=$(python3 -c "
|
|
426
|
+
import json, re, sys
|
|
427
|
+
with open('$feature_list') as f:
|
|
428
|
+
data = json.load(f)
|
|
429
|
+
for feat in data.get('features', []):
|
|
430
|
+
if feat.get('id') == '$feature_id':
|
|
431
|
+
fid = feat['id'].replace('F-', '').replace('f-', '').zfill(3)
|
|
432
|
+
title = feat.get('title', '').lower()
|
|
433
|
+
title = re.sub(r'[^a-z0-9\s-]', '', title)
|
|
434
|
+
title = re.sub(r'[\s]+', '-', title.strip())
|
|
435
|
+
title = re.sub(r'-+', '-', title).strip('-')
|
|
436
|
+
print(f'{fid}-{title}')
|
|
437
|
+
sys.exit(0)
|
|
438
|
+
sys.exit(1)
|
|
439
|
+
" 2>/dev/null) || {
|
|
440
|
+
log_warn "Could not determine feature slug for cleanup"
|
|
441
|
+
feature_slug=""
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
local project_root
|
|
445
|
+
project_root="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
446
|
+
|
|
447
|
+
if [[ -n "$feature_slug" ]]; then
|
|
448
|
+
local specs_dir="$project_root/.prizmkit/specs/$feature_slug"
|
|
449
|
+
if [[ -d "$specs_dir" ]]; then
|
|
450
|
+
rm -rf "$specs_dir"
|
|
451
|
+
log_info "Removed $specs_dir"
|
|
452
|
+
fi
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
local dev_team_dir="$project_root/.dev-team"
|
|
456
|
+
if [[ -d "$dev_team_dir" ]]; then
|
|
457
|
+
rm -rf "$dev_team_dir"
|
|
458
|
+
log_info "Removed $dev_team_dir"
|
|
459
|
+
fi
|
|
460
|
+
|
|
461
|
+
local feature_state_dir="$STATE_DIR/features/$feature_id"
|
|
462
|
+
if [[ -d "$feature_state_dir" ]]; then
|
|
463
|
+
rm -rf "$feature_state_dir"
|
|
464
|
+
log_info "Removed $feature_state_dir"
|
|
465
|
+
fi
|
|
466
|
+
fi
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
# Reset Status
|
|
470
|
+
if [[ "$no_reset" == false && "$dry_run" == false ]]; then
|
|
471
|
+
log_info "Resetting $feature_id status..."
|
|
472
|
+
python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
473
|
+
--feature-list "$feature_list" \
|
|
474
|
+
--state-dir "$STATE_DIR" \
|
|
475
|
+
--feature-id "$feature_id" \
|
|
476
|
+
--action reset >/dev/null 2>&1 || {
|
|
477
|
+
log_warn "Failed to reset feature status (may already be pending)"
|
|
478
|
+
}
|
|
479
|
+
elif [[ "$dry_run" == true && "$no_reset" == false ]]; then
|
|
480
|
+
log_info "Dry-run mode: skipping status reset"
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
# Generate Bootstrap Prompt
|
|
484
|
+
local run_id session_id session_dir bootstrap_prompt
|
|
485
|
+
run_id=$(jq -r '.run_id' "$STATE_DIR/pipeline.json")
|
|
486
|
+
session_id="${feature_id}-$(date +%Y%m%d%H%M%S)"
|
|
487
|
+
session_dir="$STATE_DIR/features/$feature_id/sessions/$session_id"
|
|
488
|
+
mkdir -p "$session_dir/logs"
|
|
489
|
+
|
|
490
|
+
bootstrap_prompt="$session_dir/bootstrap-prompt.md"
|
|
491
|
+
|
|
492
|
+
local prompt_args=(
|
|
493
|
+
--feature-list "$feature_list"
|
|
494
|
+
--feature-id "$feature_id"
|
|
495
|
+
--session-id "$session_id"
|
|
496
|
+
--run-id "$run_id"
|
|
497
|
+
--retry-count 0
|
|
498
|
+
--resume-phase "$resume_phase"
|
|
499
|
+
--state-dir "$STATE_DIR"
|
|
500
|
+
--output "$bootstrap_prompt"
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
if [[ -n "$mode_override" ]]; then
|
|
504
|
+
prompt_args+=(--mode "$mode_override")
|
|
505
|
+
fi
|
|
506
|
+
|
|
507
|
+
log_info "Generating bootstrap prompt..."
|
|
508
|
+
python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" "${prompt_args[@]}" >/dev/null 2>&1
|
|
509
|
+
|
|
510
|
+
# Dry-Run: Print info and exit
|
|
511
|
+
if [[ "$dry_run" == true ]]; then
|
|
512
|
+
echo ""
|
|
513
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
514
|
+
echo -e "${BOLD} Dry Run: $feature_id — $feature_title${NC}"
|
|
515
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
516
|
+
echo ""
|
|
517
|
+
log_info "Session ID: $session_id"
|
|
518
|
+
log_info "Resume Phase: $resume_phase"
|
|
519
|
+
if [[ -n "$mode_override" ]]; then
|
|
520
|
+
log_info "Mode Override: $mode_override"
|
|
521
|
+
else
|
|
522
|
+
log_info "Mode: auto-detect (from complexity)"
|
|
523
|
+
fi
|
|
524
|
+
echo ""
|
|
525
|
+
log_info "Bootstrap prompt written to:"
|
|
526
|
+
echo " $bootstrap_prompt"
|
|
527
|
+
echo ""
|
|
528
|
+
|
|
529
|
+
local prompt_lines
|
|
530
|
+
prompt_lines=$(wc -l < "$bootstrap_prompt" | tr -d ' ')
|
|
531
|
+
log_info "Prompt: $prompt_lines lines"
|
|
532
|
+
echo ""
|
|
533
|
+
echo -e "${BOLD}--- Session Context (from prompt) ---${NC}"
|
|
534
|
+
sed -n '/^## Session Context/,/^##[^#]/p' "$bootstrap_prompt" | head -20
|
|
535
|
+
echo -e "${BOLD}--- end ---${NC}"
|
|
536
|
+
echo ""
|
|
537
|
+
|
|
538
|
+
log_success "Dry run complete. Inspect full prompt with:"
|
|
539
|
+
echo " cat $bootstrap_prompt"
|
|
540
|
+
return 0
|
|
541
|
+
fi
|
|
542
|
+
|
|
543
|
+
# Spawn AI CLI Session
|
|
544
|
+
echo ""
|
|
545
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
546
|
+
echo -e "${BOLD} Run: $feature_id — $feature_title${NC}"
|
|
547
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
548
|
+
log_info "Session ID: $session_id"
|
|
549
|
+
log_info "Resume Phase: $resume_phase"
|
|
550
|
+
if [[ -n "$mode_override" ]]; then
|
|
551
|
+
log_info "Mode Override: $mode_override"
|
|
552
|
+
fi
|
|
553
|
+
if [[ $SESSION_TIMEOUT -gt 0 ]]; then
|
|
554
|
+
log_info "Session timeout: ${SESSION_TIMEOUT}s"
|
|
555
|
+
else
|
|
556
|
+
log_info "Session timeout: none"
|
|
557
|
+
fi
|
|
558
|
+
log_info "Prompt: $bootstrap_prompt"
|
|
559
|
+
log_info "Log: $session_dir/logs/session.log"
|
|
560
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
561
|
+
echo ""
|
|
562
|
+
|
|
563
|
+
# Override cleanup trap for single-feature mode
|
|
564
|
+
cleanup() {
|
|
565
|
+
echo ""
|
|
566
|
+
log_warn "Interrupted. Killing session..."
|
|
567
|
+
# Kill all child processes
|
|
568
|
+
kill 0 2>/dev/null || true
|
|
569
|
+
log_info "Session log: $session_dir/logs/session.log"
|
|
570
|
+
exit 130
|
|
571
|
+
}
|
|
572
|
+
trap cleanup SIGINT SIGTERM
|
|
573
|
+
|
|
574
|
+
_SPAWN_RESULT=""
|
|
575
|
+
spawn_and_wait_session \
|
|
576
|
+
"$feature_id" "$feature_list" "$session_id" \
|
|
577
|
+
"$bootstrap_prompt" "$session_dir" 999
|
|
578
|
+
local session_status="$_SPAWN_RESULT"
|
|
579
|
+
|
|
580
|
+
echo ""
|
|
581
|
+
if [[ "$session_status" == "success" ]]; then
|
|
582
|
+
log_success "════════════════════════════════════════════════════"
|
|
583
|
+
log_success " $feature_id completed successfully!"
|
|
584
|
+
log_success "════════════════════════════════════════════════════"
|
|
585
|
+
else
|
|
586
|
+
log_error "════════════════════════════════════════════════════"
|
|
587
|
+
log_error " $feature_id result: $session_status"
|
|
588
|
+
log_error " Review log: $session_dir/logs/session.log"
|
|
589
|
+
log_error "════════════════════════════════════════════════════"
|
|
590
|
+
fi
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
# ============================================================
|
|
594
|
+
# Main Loop: Run all features
|
|
595
|
+
# ============================================================
|
|
596
|
+
|
|
597
|
+
main() {
|
|
598
|
+
local feature_list="${1:-feature-list.json}"
|
|
599
|
+
|
|
600
|
+
# Resolve to absolute path
|
|
601
|
+
if [[ ! "$feature_list" = /* ]]; then
|
|
602
|
+
feature_list="$(pwd)/$feature_list"
|
|
603
|
+
fi
|
|
604
|
+
|
|
605
|
+
FEATURE_LIST="$feature_list"
|
|
606
|
+
|
|
607
|
+
# Validate feature list exists
|
|
608
|
+
if [[ ! -f "$feature_list" ]]; then
|
|
609
|
+
log_error "Feature list not found: $feature_list"
|
|
610
|
+
log_info "Create a feature list first using the app-planner skill,"
|
|
611
|
+
log_info "or provide a path: ./run.sh run <path-to-feature-list.json>"
|
|
612
|
+
exit 1
|
|
613
|
+
fi
|
|
614
|
+
|
|
615
|
+
check_dependencies
|
|
616
|
+
|
|
617
|
+
# Initialize pipeline state if needed
|
|
618
|
+
if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
|
|
619
|
+
log_info "Initializing pipeline state..."
|
|
620
|
+
local init_result
|
|
621
|
+
init_result=$(python3 "$SCRIPTS_DIR/init-pipeline.py" \
|
|
622
|
+
--feature-list "$feature_list" \
|
|
623
|
+
--state-dir "$STATE_DIR" 2>&1)
|
|
624
|
+
|
|
625
|
+
local init_valid
|
|
626
|
+
init_valid=$(echo "$init_result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('valid', False))" 2>/dev/null || echo "False")
|
|
627
|
+
|
|
628
|
+
if [[ "$init_valid" != "True" ]]; then
|
|
629
|
+
log_error "Pipeline initialization failed:"
|
|
630
|
+
echo "$init_result"
|
|
631
|
+
exit 1
|
|
632
|
+
fi
|
|
633
|
+
|
|
634
|
+
local features_count
|
|
635
|
+
features_count=$(echo "$init_result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('features_count', 0))" 2>/dev/null || echo "0")
|
|
636
|
+
log_success "Pipeline initialized with $features_count features"
|
|
637
|
+
else
|
|
638
|
+
log_info "Resuming existing pipeline..."
|
|
639
|
+
fi
|
|
640
|
+
|
|
641
|
+
# Print header
|
|
642
|
+
echo ""
|
|
643
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
644
|
+
echo -e "${BOLD} Dev-Pipeline Runner Started${NC}"
|
|
645
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
646
|
+
log_info "Feature list: $feature_list"
|
|
647
|
+
log_info "Max retries per feature: $MAX_RETRIES"
|
|
648
|
+
if [[ $SESSION_TIMEOUT -gt 0 ]]; then
|
|
649
|
+
log_info "Session timeout: ${SESSION_TIMEOUT}s"
|
|
650
|
+
else
|
|
651
|
+
log_info "Session timeout: none"
|
|
652
|
+
fi
|
|
653
|
+
log_info "AI CLI: $CLI_CMD (platform: $PLATFORM)"
|
|
654
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
655
|
+
echo ""
|
|
656
|
+
|
|
657
|
+
# Main processing loop
|
|
658
|
+
local session_count=0
|
|
659
|
+
|
|
660
|
+
while true; do
|
|
661
|
+
# Check for stuck features
|
|
662
|
+
local stuck_result
|
|
663
|
+
stuck_result=$(python3 "$SCRIPTS_DIR/detect-stuck.py" \
|
|
664
|
+
--state-dir "$STATE_DIR" \
|
|
665
|
+
--feature-list "$FEATURE_LIST" \
|
|
666
|
+
--max-retries "$MAX_RETRIES" \
|
|
667
|
+
--stale-threshold "$HEARTBEAT_STALE_THRESHOLD" 2>/dev/null || echo '{"stuck_count": 0}')
|
|
668
|
+
|
|
669
|
+
local stuck_count
|
|
670
|
+
stuck_count=$(echo "$stuck_result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('stuck_count', 0))" 2>/dev/null || echo "0")
|
|
671
|
+
|
|
672
|
+
if [[ "$stuck_count" -gt 0 ]]; then
|
|
673
|
+
log_warn "Detected $stuck_count stuck feature(s):"
|
|
674
|
+
echo "$stuck_result" | python3 -c "
|
|
675
|
+
import sys, json
|
|
676
|
+
data = json.load(sys.stdin)
|
|
677
|
+
for f in data.get('stuck_features', []):
|
|
678
|
+
print(f' - {f[\"feature_id\"]}: {f[\"reason\"]} — {f[\"suggestion\"]}')
|
|
679
|
+
" 2>/dev/null || true
|
|
680
|
+
fi
|
|
681
|
+
|
|
682
|
+
# Find next feature to process
|
|
683
|
+
local next_feature
|
|
684
|
+
next_feature=$(python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
685
|
+
--feature-list "$feature_list" \
|
|
686
|
+
--state-dir "$STATE_DIR" \
|
|
687
|
+
--max-retries "$MAX_RETRIES" \
|
|
688
|
+
--action get_next 2>/dev/null) || true
|
|
689
|
+
|
|
690
|
+
if [[ "$next_feature" == "PIPELINE_COMPLETE" ]]; then
|
|
691
|
+
echo ""
|
|
692
|
+
log_success "════════════════════════════════════════════════════"
|
|
693
|
+
log_success " All features completed! Pipeline finished."
|
|
694
|
+
log_success " Total sessions: $session_count"
|
|
695
|
+
log_success "════════════════════════════════════════════════════"
|
|
696
|
+
break
|
|
697
|
+
fi
|
|
698
|
+
|
|
699
|
+
if [[ "$next_feature" == "PIPELINE_BLOCKED" ]]; then
|
|
700
|
+
log_warn "All remaining features are blocked by dependencies or failed."
|
|
701
|
+
log_warn "Run './run.sh status' to see details."
|
|
702
|
+
log_warn "Waiting 60s before re-checking... (Ctrl+C to stop)"
|
|
703
|
+
sleep 60
|
|
704
|
+
continue
|
|
705
|
+
fi
|
|
706
|
+
|
|
707
|
+
# Parse feature info
|
|
708
|
+
local feature_id feature_title retry_count resume_phase
|
|
709
|
+
feature_id=$(echo "$next_feature" | jq -r '.feature_id')
|
|
710
|
+
feature_title=$(echo "$next_feature" | jq -r '.title')
|
|
711
|
+
retry_count=$(echo "$next_feature" | jq -r '.retry_count // 0')
|
|
712
|
+
resume_phase=$(echo "$next_feature" | jq -r '.resume_from_phase // "null"')
|
|
713
|
+
|
|
714
|
+
echo ""
|
|
715
|
+
echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
|
|
716
|
+
log_info "Feature: ${BOLD}$feature_id${NC} — $feature_title"
|
|
717
|
+
log_info "Retry: $retry_count / $MAX_RETRIES"
|
|
718
|
+
if [[ "$resume_phase" != "null" ]]; then
|
|
719
|
+
log_info "Resuming from Phase $resume_phase"
|
|
720
|
+
fi
|
|
721
|
+
echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
|
|
722
|
+
|
|
723
|
+
# Generate session ID and bootstrap prompt
|
|
724
|
+
local session_id run_id
|
|
725
|
+
run_id=$(jq -r '.run_id' "$STATE_DIR/pipeline.json")
|
|
726
|
+
session_id="${feature_id}-$(date +%Y%m%d%H%M%S)"
|
|
727
|
+
|
|
728
|
+
local session_dir="$STATE_DIR/features/$feature_id/sessions/$session_id"
|
|
729
|
+
mkdir -p "$session_dir/logs"
|
|
730
|
+
|
|
731
|
+
local bootstrap_prompt="$session_dir/bootstrap-prompt.md"
|
|
732
|
+
python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" \
|
|
733
|
+
--feature-list "$feature_list" \
|
|
734
|
+
--feature-id "$feature_id" \
|
|
735
|
+
--session-id "$session_id" \
|
|
736
|
+
--run-id "$run_id" \
|
|
737
|
+
--retry-count "$retry_count" \
|
|
738
|
+
--resume-phase "$resume_phase" \
|
|
739
|
+
--state-dir "$STATE_DIR" \
|
|
740
|
+
--output "$bootstrap_prompt" >/dev/null 2>&1
|
|
741
|
+
|
|
742
|
+
# Update current session tracking
|
|
743
|
+
python3 -c "
|
|
744
|
+
import json, sys
|
|
745
|
+
from datetime import datetime
|
|
746
|
+
data = {
|
|
747
|
+
'feature_id': '$feature_id',
|
|
748
|
+
'session_id': '$session_id',
|
|
749
|
+
'started_at': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
750
|
+
}
|
|
751
|
+
with open('$STATE_DIR/current-session.json', 'w') as f:
|
|
752
|
+
json.dump(data, f, indent=2)
|
|
753
|
+
"
|
|
754
|
+
|
|
755
|
+
# Spawn session and wait
|
|
756
|
+
log_info "Spawning AI CLI session: $session_id"
|
|
757
|
+
_SPAWN_RESULT=""
|
|
758
|
+
spawn_and_wait_session \
|
|
759
|
+
"$feature_id" "$feature_list" "$session_id" \
|
|
760
|
+
"$bootstrap_prompt" "$session_dir" "$MAX_RETRIES"
|
|
761
|
+
local session_status="$_SPAWN_RESULT"
|
|
762
|
+
|
|
763
|
+
session_count=$((session_count + 1))
|
|
764
|
+
|
|
765
|
+
# Brief pause before next iteration
|
|
766
|
+
log_info "Pausing 5s before next feature..."
|
|
767
|
+
sleep 5
|
|
768
|
+
done
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
# ============================================================
|
|
772
|
+
# Entry Point
|
|
773
|
+
# ============================================================
|
|
774
|
+
|
|
775
|
+
show_help() {
|
|
776
|
+
echo "Usage: $0 <command> [options]"
|
|
777
|
+
echo ""
|
|
778
|
+
echo "Commands:"
|
|
779
|
+
echo " run [feature-list.json] Run all features sequentially"
|
|
780
|
+
echo " run <feature-id> [options] Run a single feature"
|
|
781
|
+
echo " status [feature-list.json] Show pipeline status"
|
|
782
|
+
echo " reset Clear all state and start fresh"
|
|
783
|
+
echo " help Show this help message"
|
|
784
|
+
echo ""
|
|
785
|
+
echo "Single Feature Options (run <feature-id>):"
|
|
786
|
+
echo " --dry-run Generate bootstrap prompt only, don't spawn session"
|
|
787
|
+
echo " --resume-phase N Override resume phase (default: auto-detect)"
|
|
788
|
+
echo " --mode <lite|standard|full> Override pipeline mode (bypasses estimated_complexity)"
|
|
789
|
+
echo " --clean Delete artifacts and reset before running"
|
|
790
|
+
echo " --no-reset Skip feature status reset step"
|
|
791
|
+
echo " --timeout N Session timeout in seconds (default: 0 = no limit)"
|
|
792
|
+
echo ""
|
|
793
|
+
echo "Environment Variables:"
|
|
794
|
+
echo " MAX_RETRIES Max retries per feature (default: 3)"
|
|
795
|
+
echo " SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)"
|
|
796
|
+
echo " AI_CLI AI CLI command name (auto-detected: cbc or claude)"
|
|
797
|
+
echo " HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)"
|
|
798
|
+
echo " HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)"
|
|
799
|
+
echo ""
|
|
800
|
+
echo "Examples:"
|
|
801
|
+
echo " ./run.sh run # Run all features"
|
|
802
|
+
echo " ./run.sh run F-007 --dry-run # Inspect generated prompt"
|
|
803
|
+
echo " ./run.sh run F-007 --dry-run --mode lite # Test lite mode"
|
|
804
|
+
echo " ./run.sh run F-007 --resume-phase 6 # Skip to implementation"
|
|
805
|
+
echo " ./run.sh run F-007 --mode full --timeout 3600 # Full mode, 1h timeout"
|
|
806
|
+
echo " ./run.sh run F-007 --clean --mode standard # Clean + run standard"
|
|
807
|
+
echo " ./run.sh status # Show pipeline status"
|
|
808
|
+
echo " MAX_RETRIES=5 SESSION_TIMEOUT=7200 ./run.sh run # Custom config"
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
case "${1:-run}" in
|
|
812
|
+
run|resume)
|
|
813
|
+
shift || true
|
|
814
|
+
# Check if first arg is a feature ID (F-xxx pattern)
|
|
815
|
+
if [[ "${1:-}" =~ ^[Ff]-[0-9]+ ]]; then
|
|
816
|
+
run_one "$@"
|
|
817
|
+
else
|
|
818
|
+
main "${1:-feature-list.json}"
|
|
819
|
+
fi
|
|
820
|
+
;;
|
|
821
|
+
status)
|
|
822
|
+
check_dependencies
|
|
823
|
+
if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
|
|
824
|
+
log_error "No pipeline state found. Run './run.sh run' first."
|
|
825
|
+
exit 1
|
|
826
|
+
fi
|
|
827
|
+
python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
828
|
+
--feature-list "${2:-feature-list.json}" \
|
|
829
|
+
--state-dir "$STATE_DIR" \
|
|
830
|
+
--action status
|
|
831
|
+
;;
|
|
832
|
+
reset)
|
|
833
|
+
log_warn "Resetting pipeline state..."
|
|
834
|
+
rm -rf "$STATE_DIR"
|
|
835
|
+
log_success "State cleared. Run './run.sh run' to start fresh."
|
|
836
|
+
;;
|
|
837
|
+
help|--help|-h)
|
|
838
|
+
show_help
|
|
839
|
+
;;
|
|
840
|
+
*)
|
|
841
|
+
log_error "Unknown command: $1"
|
|
842
|
+
show_help
|
|
843
|
+
exit 1
|
|
844
|
+
;;
|
|
845
|
+
esac
|