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.
Files changed (89) hide show
  1. package/bundled/VERSION.json +5 -0
  2. package/bundled/adapters/claude/agent-adapter.js +108 -0
  3. package/bundled/adapters/claude/command-adapter.js +104 -0
  4. package/bundled/adapters/claude/paths.js +35 -0
  5. package/bundled/adapters/claude/rules-adapter.js +77 -0
  6. package/bundled/adapters/claude/settings-adapter.js +73 -0
  7. package/bundled/adapters/claude/team-adapter.js +183 -0
  8. package/bundled/adapters/codebuddy/agent-adapter.js +43 -0
  9. package/bundled/adapters/codebuddy/paths.js +29 -0
  10. package/bundled/adapters/codebuddy/settings-adapter.js +47 -0
  11. package/bundled/adapters/codebuddy/skill-adapter.js +68 -0
  12. package/bundled/adapters/codebuddy/team-adapter.js +46 -0
  13. package/bundled/adapters/shared/frontmatter.js +77 -0
  14. package/bundled/agents/prizm-dev-team-coordinator.md +142 -0
  15. package/bundled/agents/prizm-dev-team-dev.md +99 -0
  16. package/bundled/agents/prizm-dev-team-pm.md +114 -0
  17. package/bundled/agents/prizm-dev-team-reviewer.md +119 -0
  18. package/bundled/dev-pipeline/README.md +482 -0
  19. package/bundled/dev-pipeline/assets/feature-list-example.json +147 -0
  20. package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +138 -0
  21. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +425 -0
  22. package/bundled/dev-pipeline/launch-daemon.sh +549 -0
  23. package/bundled/dev-pipeline/reset-feature.sh +209 -0
  24. package/bundled/dev-pipeline/retry-bug.sh +344 -0
  25. package/bundled/dev-pipeline/retry-feature.sh +338 -0
  26. package/bundled/dev-pipeline/run-bugfix.sh +638 -0
  27. package/bundled/dev-pipeline/run.sh +845 -0
  28. package/bundled/dev-pipeline/scripts/check-session-status.py +158 -0
  29. package/bundled/dev-pipeline/scripts/detect-stuck.py +385 -0
  30. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +598 -0
  31. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +402 -0
  32. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +294 -0
  33. package/bundled/dev-pipeline/scripts/init-dev-team.py +134 -0
  34. package/bundled/dev-pipeline/scripts/init-pipeline.py +335 -0
  35. package/bundled/dev-pipeline/scripts/update-bug-status.py +748 -0
  36. package/bundled/dev-pipeline/scripts/update-feature-status.py +1076 -0
  37. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +262 -0
  38. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +159 -0
  39. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +291 -0
  40. package/bundled/dev-pipeline/templates/feature-list-schema.json +112 -0
  41. package/bundled/dev-pipeline/templates/session-status-schema.json +77 -0
  42. package/bundled/skills/_metadata.json +267 -0
  43. package/bundled/skills/app-planner/SKILL.md +580 -0
  44. package/bundled/skills/app-planner/assets/planning-guide.md +313 -0
  45. package/bundled/skills/app-planner/scripts/validate-and-generate.py +758 -0
  46. package/bundled/skills/bug-planner/SKILL.md +235 -0
  47. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +252 -0
  48. package/bundled/skills/dev-pipeline-launcher/SKILL.md +223 -0
  49. package/bundled/skills/prizm-kit/SKILL.md +151 -0
  50. package/bundled/skills/prizm-kit/assets/claude-md-template.md +38 -0
  51. package/bundled/skills/prizm-kit/assets/codebuddy-md-template.md +35 -0
  52. package/bundled/skills/prizm-kit/assets/hooks/prizm-commit-hook.json +15 -0
  53. package/bundled/skills/prizmkit-adr-manager/SKILL.md +68 -0
  54. package/bundled/skills/prizmkit-adr-manager/assets/adr-template.md +26 -0
  55. package/bundled/skills/prizmkit-analyze/SKILL.md +194 -0
  56. package/bundled/skills/prizmkit-api-doc-generator/SKILL.md +56 -0
  57. package/bundled/skills/prizmkit-bug-fix-workflow/SKILL.md +351 -0
  58. package/bundled/skills/prizmkit-bug-reproducer/SKILL.md +62 -0
  59. package/bundled/skills/prizmkit-ci-cd-generator/SKILL.md +54 -0
  60. package/bundled/skills/prizmkit-clarify/SKILL.md +52 -0
  61. package/bundled/skills/prizmkit-code-review/SKILL.md +70 -0
  62. package/bundled/skills/prizmkit-committer/SKILL.md +117 -0
  63. package/bundled/skills/prizmkit-db-migration/SKILL.md +65 -0
  64. package/bundled/skills/prizmkit-dependency-health/SKILL.md +123 -0
  65. package/bundled/skills/prizmkit-deployment-strategy/SKILL.md +58 -0
  66. package/bundled/skills/prizmkit-error-triage/SKILL.md +55 -0
  67. package/bundled/skills/prizmkit-implement/SKILL.md +47 -0
  68. package/bundled/skills/prizmkit-init/SKILL.md +156 -0
  69. package/bundled/skills/prizmkit-log-analyzer/SKILL.md +55 -0
  70. package/bundled/skills/prizmkit-monitoring-setup/SKILL.md +75 -0
  71. package/bundled/skills/prizmkit-onboarding-generator/SKILL.md +70 -0
  72. package/bundled/skills/prizmkit-perf-profiler/SKILL.md +55 -0
  73. package/bundled/skills/prizmkit-plan/SKILL.md +54 -0
  74. package/bundled/skills/prizmkit-plan/assets/plan-template.md +37 -0
  75. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +140 -0
  76. package/bundled/skills/prizmkit-prizm-docs/assets/PRIZM-SPEC.md +943 -0
  77. package/bundled/skills/prizmkit-retrospective/SKILL.md +79 -0
  78. package/bundled/skills/prizmkit-security-audit/SKILL.md +130 -0
  79. package/bundled/skills/prizmkit-specify/SKILL.md +52 -0
  80. package/bundled/skills/prizmkit-specify/assets/spec-template.md +37 -0
  81. package/bundled/skills/prizmkit-summarize/SKILL.md +51 -0
  82. package/bundled/skills/prizmkit-summarize/assets/registry-template.md +18 -0
  83. package/bundled/skills/prizmkit-tasks/SKILL.md +50 -0
  84. package/bundled/skills/prizmkit-tasks/assets/tasks-template.md +21 -0
  85. package/bundled/skills/prizmkit-tech-debt-tracker/SKILL.md +139 -0
  86. package/bundled/team/prizm-dev-team.json +47 -0
  87. package/bundled/templates/claude-md-template.md +38 -0
  88. package/bundled/templates/codebuddy-md-template.md +35 -0
  89. 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