prizmkit 1.0.0 → 1.0.2

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 (90) 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
  90. package/src/scaffold.js +1 -1
@@ -0,0 +1,549 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # ============================================================
5
+ # dev-pipeline/launch-daemon.sh - Daemon wrapper for run.sh
6
+ #
7
+ # Manages run.sh as a background daemon process with PID tracking,
8
+ # log consolidation, and lifecycle commands.
9
+ #
10
+ # Usage:
11
+ # ./launch-daemon.sh start [feature-list.json] [--env "KEY=VAL ..."]
12
+ # ./launch-daemon.sh stop
13
+ # ./launch-daemon.sh status
14
+ # ./launch-daemon.sh logs [--lines N] [--follow]
15
+ # ./launch-daemon.sh restart [feature-list.json] [--env "KEY=VAL ..."]
16
+ #
17
+ # Files managed:
18
+ # state/.pipeline.pid - PID of the background run.sh process
19
+ # state/pipeline-daemon.log - Consolidated stdout+stderr from run.sh
20
+ # ============================================================
21
+
22
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ STATE_DIR="$SCRIPT_DIR/state"
24
+ PID_FILE="$STATE_DIR/.pipeline.pid"
25
+ LOG_FILE="$STATE_DIR/pipeline-daemon.log"
26
+ RUN_SCRIPT="$SCRIPT_DIR/run.sh"
27
+
28
+ # Colors
29
+ RED='\033[0;31m'
30
+ GREEN='\033[0;32m'
31
+ YELLOW='\033[1;33m'
32
+ BLUE='\033[0;34m'
33
+ BOLD='\033[1m'
34
+ NC='\033[0m'
35
+
36
+ log_info() { echo -e "${BLUE}[INFO]${NC} $*" >&2; }
37
+ log_warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
38
+ log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
39
+ log_success() { echo -e "${GREEN}[OK]${NC} $*" >&2; }
40
+
41
+ # ============================================================
42
+ # Helpers
43
+ # ============================================================
44
+
45
+ # Check if pipeline process is alive
46
+ # Returns 0 if alive, 1 if dead/no PID file
47
+ is_running() {
48
+ if [[ ! -f "$PID_FILE" ]]; then
49
+ return 1
50
+ fi
51
+ local pid
52
+ pid=$(cat "$PID_FILE" 2>/dev/null) || return 1
53
+ if [[ -z "$pid" ]]; then
54
+ return 1
55
+ fi
56
+ if kill -0 "$pid" 2>/dev/null; then
57
+ return 0
58
+ else
59
+ return 1
60
+ fi
61
+ }
62
+
63
+ # Get PID from file (or empty string)
64
+ get_pid() {
65
+ if [[ -f "$PID_FILE" ]]; then
66
+ cat "$PID_FILE" 2>/dev/null || echo ""
67
+ else
68
+ echo ""
69
+ fi
70
+ }
71
+
72
+ # Clean stale PID file
73
+ clean_stale_pid() {
74
+ if [[ -f "$PID_FILE" ]]; then
75
+ local pid
76
+ pid=$(get_pid)
77
+ if [[ -n "$pid" ]] && ! kill -0 "$pid" 2>/dev/null; then
78
+ rm -f "$PID_FILE"
79
+ log_warn "Cleaned stale PID file (process $pid no longer running)"
80
+ fi
81
+ fi
82
+ }
83
+
84
+ # ============================================================
85
+ # start: Launch run.sh in background
86
+ # ============================================================
87
+
88
+ cmd_start() {
89
+ local feature_list=""
90
+ local env_overrides=""
91
+
92
+ # Parse arguments
93
+ while [[ $# -gt 0 ]]; do
94
+ case "$1" in
95
+ --env)
96
+ shift
97
+ if [[ $# -eq 0 ]]; then
98
+ log_error "--env requires a value (e.g. --env \"MAX_RETRIES=5 SESSION_TIMEOUT=3600\")"
99
+ exit 1
100
+ fi
101
+ env_overrides="$1"
102
+ shift
103
+ ;;
104
+ *)
105
+ feature_list="$1"
106
+ shift
107
+ ;;
108
+ esac
109
+ done
110
+
111
+ # Default feature list
112
+ if [[ -z "$feature_list" ]]; then
113
+ feature_list="feature-list.json"
114
+ fi
115
+
116
+ # Resolve to absolute path
117
+ if [[ ! "$feature_list" = /* ]]; then
118
+ feature_list="$(cd "$(dirname "$feature_list")" 2>/dev/null && pwd)/$(basename "$feature_list")"
119
+ fi
120
+
121
+ # Validate feature list
122
+ if [[ ! -f "$feature_list" ]]; then
123
+ log_error "Feature list not found: $feature_list"
124
+ log_error "Run the app-planner skill first to generate feature-list.json"
125
+ exit 2
126
+ fi
127
+
128
+ # Validate run.sh exists
129
+ if [[ ! -x "$RUN_SCRIPT" ]]; then
130
+ log_error "run.sh not found or not executable: $RUN_SCRIPT"
131
+ exit 2
132
+ fi
133
+
134
+ # Clean stale PID if needed
135
+ clean_stale_pid
136
+
137
+ # Check if already running
138
+ if is_running; then
139
+ local pid
140
+ pid=$(get_pid)
141
+ log_error "Pipeline is already running (PID: $pid)"
142
+ log_error "Use './launch-daemon.sh stop' first, or './launch-daemon.sh restart'"
143
+ exit 1
144
+ fi
145
+
146
+ # Ensure state directory exists
147
+ mkdir -p "$STATE_DIR"
148
+
149
+ # Build environment prefix
150
+ local env_cmd=""
151
+ if [[ -n "$env_overrides" ]]; then
152
+ env_cmd="env $env_overrides"
153
+ fi
154
+
155
+ # Record start time
156
+ local start_time
157
+ start_time=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
158
+
159
+ # Launch run.sh in background, fully detached
160
+ log_info "Launching pipeline..."
161
+ log_info "Feature list: $feature_list"
162
+ log_info "Log file: $LOG_FILE"
163
+ if [[ -n "$env_overrides" ]]; then
164
+ log_info "Environment overrides: $env_overrides"
165
+ fi
166
+
167
+ # Write a separator to the log file
168
+ {
169
+ echo ""
170
+ echo "================================================================"
171
+ echo " Pipeline Daemon Started: $start_time"
172
+ echo " Feature list: $feature_list"
173
+ if [[ -n "$env_overrides" ]]; then
174
+ echo " Environment: $env_overrides"
175
+ fi
176
+ echo "================================================================"
177
+ echo ""
178
+ } >> "$LOG_FILE"
179
+
180
+ # Launch with nohup + disown for full detachment
181
+ if [[ -n "$env_cmd" ]]; then
182
+ nohup $env_cmd "$RUN_SCRIPT" run "$feature_list" >> "$LOG_FILE" 2>&1 &
183
+ else
184
+ nohup "$RUN_SCRIPT" run "$feature_list" >> "$LOG_FILE" 2>&1 &
185
+ fi
186
+ local pipeline_pid=$!
187
+ disown "$pipeline_pid" 2>/dev/null || true
188
+
189
+ # Write PID file
190
+ echo "$pipeline_pid" > "$PID_FILE"
191
+
192
+ # Write start metadata
193
+ python3 -c "
194
+ import json
195
+ from datetime import datetime
196
+ data = {
197
+ 'pid': $pipeline_pid,
198
+ 'started_at': '$start_time',
199
+ 'feature_list': '$feature_list',
200
+ 'env_overrides': '$env_overrides',
201
+ 'log_file': '$LOG_FILE'
202
+ }
203
+ with open('$STATE_DIR/.pipeline-meta.json', 'w') as f:
204
+ json.dump(data, f, indent=2)
205
+ " 2>/dev/null || true
206
+
207
+ # Wait briefly and verify
208
+ sleep 2
209
+ if is_running; then
210
+ log_success "Pipeline started successfully (PID: $pipeline_pid)"
211
+ log_info "Monitor logs: ./launch-daemon.sh logs --follow"
212
+ log_info "Check status: ./launch-daemon.sh status"
213
+
214
+ # Output JSON on stdout for programmatic consumption
215
+ echo "{\"success\": true, \"pid\": $pipeline_pid, \"log_file\": \"$LOG_FILE\", \"started_at\": \"$start_time\"}"
216
+ else
217
+ log_error "Pipeline process died immediately after launch"
218
+ log_error "Check log for errors: tail -20 $LOG_FILE"
219
+ rm -f "$PID_FILE"
220
+ exit 1
221
+ fi
222
+ }
223
+
224
+ # ============================================================
225
+ # stop: Gracefully stop the pipeline
226
+ # ============================================================
227
+
228
+ cmd_stop() {
229
+ if [[ ! -f "$PID_FILE" ]]; then
230
+ log_info "Pipeline is not running (no PID file)"
231
+ echo '{"success": true, "message": "not running"}'
232
+ return 0
233
+ fi
234
+
235
+ local pid
236
+ pid=$(get_pid)
237
+
238
+ if [[ -z "$pid" ]]; then
239
+ log_info "Pipeline is not running (empty PID file)"
240
+ rm -f "$PID_FILE"
241
+ echo '{"success": true, "message": "not running"}'
242
+ return 0
243
+ fi
244
+
245
+ if ! kill -0 "$pid" 2>/dev/null; then
246
+ log_info "Pipeline is not running (process $pid already exited)"
247
+ rm -f "$PID_FILE"
248
+ echo '{"success": true, "message": "already exited"}'
249
+ return 0
250
+ fi
251
+
252
+ log_info "Stopping pipeline (PID: $pid)..."
253
+
254
+ # Send SIGTERM for graceful shutdown (triggers run.sh cleanup trap)
255
+ kill -TERM "$pid" 2>/dev/null || true
256
+
257
+ # Wait up to 30 seconds for graceful exit
258
+ local waited=0
259
+ while [[ $waited -lt 30 ]]; do
260
+ if ! kill -0 "$pid" 2>/dev/null; then
261
+ break
262
+ fi
263
+ sleep 1
264
+ waited=$((waited + 1))
265
+ done
266
+
267
+ # Force kill if still alive
268
+ if kill -0 "$pid" 2>/dev/null; then
269
+ log_warn "Process did not exit after 30s, sending SIGKILL..."
270
+ kill -9 "$pid" 2>/dev/null || true
271
+ sleep 1
272
+ fi
273
+
274
+ rm -f "$PID_FILE"
275
+
276
+ if ! kill -0 "$pid" 2>/dev/null; then
277
+ log_success "Pipeline stopped"
278
+ echo "{\"success\": true, \"pid\": $pid, \"message\": \"stopped\"}"
279
+ else
280
+ log_error "Failed to stop pipeline (PID: $pid)"
281
+ echo "{\"success\": false, \"pid\": $pid, \"message\": \"failed to stop\"}"
282
+ exit 1
283
+ fi
284
+ }
285
+
286
+ # ============================================================
287
+ # status: Check pipeline status
288
+ # ============================================================
289
+
290
+ cmd_status() {
291
+ clean_stale_pid
292
+
293
+ if ! is_running; then
294
+ log_info "Pipeline is not running"
295
+
296
+ # Check if log file exists for last run info
297
+ if [[ -f "$LOG_FILE" ]]; then
298
+ local log_size
299
+ log_size=$(wc -c < "$LOG_FILE" 2>/dev/null | tr -d ' ')
300
+ log_info "Last log: $LOG_FILE ($((log_size / 1024))KB)"
301
+ fi
302
+
303
+ # Show feature-level progress from last run if metadata exists
304
+ if [[ -f "$STATE_DIR/.pipeline-meta.json" ]]; then
305
+ local last_feature_list
306
+ last_feature_list=$(python3 -c "
307
+ import json
308
+ with open('$STATE_DIR/.pipeline-meta.json') as f:
309
+ print(json.load(f).get('feature_list', ''))
310
+ " 2>/dev/null || echo "")
311
+
312
+ if [[ -n "$last_feature_list" && -f "$last_feature_list" ]]; then
313
+ echo "" >&2
314
+ log_info "Last run feature progress:"
315
+ python3 "$SCRIPT_DIR/scripts/update-feature-status.py" \
316
+ --feature-list "$last_feature_list" \
317
+ --state-dir "$STATE_DIR" \
318
+ --action status >&2 2>/dev/null || true
319
+ echo "" >&2
320
+ fi
321
+ fi
322
+
323
+ echo '{"running": false}'
324
+ return 1
325
+ fi
326
+
327
+ local pid
328
+ pid=$(get_pid)
329
+
330
+ # Gather metadata
331
+ local log_size_kb=0
332
+ if [[ -f "$LOG_FILE" ]]; then
333
+ local log_size
334
+ log_size=$(wc -c < "$LOG_FILE" 2>/dev/null | tr -d ' ')
335
+ log_size_kb=$((log_size / 1024))
336
+ fi
337
+
338
+ local started_at=""
339
+ local feature_list_path=""
340
+ if [[ -f "$STATE_DIR/.pipeline-meta.json" ]]; then
341
+ started_at=$(python3 -c "
342
+ import json
343
+ with open('$STATE_DIR/.pipeline-meta.json') as f:
344
+ print(json.load(f).get('started_at', ''))
345
+ " 2>/dev/null || echo "")
346
+ feature_list_path=$(python3 -c "
347
+ import json
348
+ with open('$STATE_DIR/.pipeline-meta.json') as f:
349
+ print(json.load(f).get('feature_list', ''))
350
+ " 2>/dev/null || echo "")
351
+ fi
352
+
353
+ log_success "Pipeline is running (PID: $pid)"
354
+ if [[ -n "$started_at" ]]; then
355
+ log_info "Started at: $started_at"
356
+ fi
357
+ if [[ -n "$feature_list_path" ]]; then
358
+ log_info "Feature list: $feature_list_path"
359
+ fi
360
+ log_info "Log file: $LOG_FILE (${log_size_kb}KB)"
361
+
362
+ # Show feature-level progress if feature list is available
363
+ if [[ -n "$feature_list_path" && -f "$feature_list_path" ]]; then
364
+ echo "" >&2
365
+ python3 "$SCRIPT_DIR/scripts/update-feature-status.py" \
366
+ --feature-list "$feature_list_path" \
367
+ --state-dir "$STATE_DIR" \
368
+ --action status >&2 2>/dev/null || true
369
+ echo "" >&2
370
+ fi
371
+
372
+ # Show last few log lines
373
+ if [[ -f "$LOG_FILE" ]]; then
374
+ log_info "--- Last 5 log lines ---"
375
+ tail -5 "$LOG_FILE" >&2 || true
376
+ echo "" >&2
377
+ fi
378
+
379
+ # JSON output on stdout (enhanced with progress info)
380
+ local progress_json=""
381
+ if [[ -n "$feature_list_path" && -f "$feature_list_path" ]]; then
382
+ progress_json=$(python3 -c "
383
+ import json, sys, os
384
+ sys.path.insert(0, '$SCRIPT_DIR/scripts')
385
+ from datetime import datetime
386
+
387
+ def load_json(p):
388
+ with open(p, 'r') as f:
389
+ return json.load(f)
390
+
391
+ fl = load_json('$feature_list_path')
392
+ features = fl.get('features', [])
393
+ total = len(features)
394
+ counts = {'completed': 0, 'in_progress': 0, 'failed': 0, 'pending': 0, 'skipped': 0}
395
+ for feat in features:
396
+ fid = feat.get('id', '')
397
+ sp = os.path.join('$STATE_DIR', 'features', fid, 'status.json')
398
+ if os.path.isfile(sp):
399
+ fs = load_json(sp)
400
+ st = fs.get('status', 'pending')
401
+ else:
402
+ st = 'pending'
403
+ if st in counts:
404
+ counts[st] += 1
405
+ else:
406
+ counts['pending'] += 1
407
+
408
+ pct = round(counts['completed'] / total * 100, 1) if total > 0 else 0
409
+ print(json.dumps({
410
+ 'total': total,
411
+ 'completed': counts['completed'],
412
+ 'in_progress': counts['in_progress'],
413
+ 'failed': counts['failed'],
414
+ 'pending': counts['pending'],
415
+ 'percent': pct
416
+ }))
417
+ " 2>/dev/null || echo "")
418
+ fi
419
+
420
+ if [[ -n "$progress_json" ]]; then
421
+ # Merge progress into the main JSON output
422
+ cat <<EOF
423
+ {"running": true, "pid": $pid, "log_file": "$LOG_FILE", "log_size_kb": $log_size_kb, "started_at": "$started_at", "feature_list": "$feature_list_path", "progress": $progress_json}
424
+ EOF
425
+ else
426
+ cat <<EOF
427
+ {"running": true, "pid": $pid, "log_file": "$LOG_FILE", "log_size_kb": $log_size_kb, "started_at": "$started_at", "feature_list": "$feature_list_path"}
428
+ EOF
429
+ fi
430
+ }
431
+
432
+ # ============================================================
433
+ # logs: View or follow pipeline logs
434
+ # ============================================================
435
+
436
+ cmd_logs() {
437
+ local lines=50
438
+ local follow=false
439
+
440
+ while [[ $# -gt 0 ]]; do
441
+ case "$1" in
442
+ --lines|-n)
443
+ shift
444
+ if [[ $# -eq 0 ]]; then
445
+ log_error "--lines requires a number"
446
+ exit 1
447
+ fi
448
+ lines="$1"
449
+ shift
450
+ ;;
451
+ --follow|-f)
452
+ follow=true
453
+ shift
454
+ ;;
455
+ *)
456
+ log_error "Unknown option: $1"
457
+ exit 1
458
+ ;;
459
+ esac
460
+ done
461
+
462
+ if [[ ! -f "$LOG_FILE" ]]; then
463
+ log_info "No log file found at $LOG_FILE"
464
+ log_info "Pipeline has not been started yet"
465
+ exit 0
466
+ fi
467
+
468
+ if [[ "$follow" == true ]]; then
469
+ log_info "Following $LOG_FILE (Ctrl+C to stop)..."
470
+ echo "" >&2
471
+ tail -f "$LOG_FILE"
472
+ else
473
+ tail -"$lines" "$LOG_FILE"
474
+ fi
475
+ }
476
+
477
+ # ============================================================
478
+ # restart: Stop + Start
479
+ # ============================================================
480
+
481
+ cmd_restart() {
482
+ cmd_stop 2>/dev/null || true
483
+ sleep 1
484
+ cmd_start "$@"
485
+ }
486
+
487
+ # ============================================================
488
+ # Entry point
489
+ # ============================================================
490
+
491
+ show_help() {
492
+ cat <<'HELP'
493
+ Usage: launch-daemon.sh <command> [options]
494
+
495
+ Commands:
496
+ start [feature-list.json] [--env "K=V ..."] Start pipeline in background
497
+ stop Gracefully stop pipeline
498
+ status Check if pipeline is running
499
+ logs [--lines N] [--follow] View pipeline logs
500
+ restart [feature-list.json] [--env "K=V ..."] Stop + start pipeline
501
+ help Show this help
502
+
503
+ Examples:
504
+ ./launch-daemon.sh start # Start with default feature-list.json
505
+ ./launch-daemon.sh start my-features.json # Start with custom feature list
506
+ ./launch-daemon.sh start --env "MAX_RETRIES=5 SESSION_TIMEOUT=7200"
507
+ ./launch-daemon.sh status # Check if running (JSON on stdout)
508
+ ./launch-daemon.sh logs --follow # Live log tailing
509
+ ./launch-daemon.sh logs --lines 100 # Last 100 lines
510
+ ./launch-daemon.sh stop # Graceful shutdown
511
+ ./launch-daemon.sh restart # Stop + start
512
+
513
+ Environment Variables (pass via --env):
514
+ MAX_RETRIES Max retries per feature (default: 3)
515
+ SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
516
+ VERBOSE Set to 1 for verbose AI CLI output
517
+ HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
518
+ HELP
519
+ }
520
+
521
+ case "${1:-help}" in
522
+ start)
523
+ shift
524
+ cmd_start "$@"
525
+ ;;
526
+ stop)
527
+ cmd_stop
528
+ ;;
529
+ status)
530
+ cmd_status
531
+ ;;
532
+ logs|log)
533
+ shift
534
+ cmd_logs "$@"
535
+ ;;
536
+ restart)
537
+ shift
538
+ cmd_restart "$@"
539
+ ;;
540
+ help|--help|-h)
541
+ show_help
542
+ ;;
543
+ *)
544
+ log_error "Unknown command: $1"
545
+ echo "" >&2
546
+ show_help
547
+ exit 1
548
+ ;;
549
+ esac