forge-pipeline 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # Forge
2
+
3
+ Autonomous multi-agent coding pipeline. Give it a task, walk away, come back to finished code.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Clone and set up
9
+ git clone <repo-url> ~/forge
10
+ chmod +x ~/forge/forge
11
+ export PATH="$HOME/forge:$PATH" # Add to your shell profile
12
+
13
+ # Initialize a new project
14
+ forge init my-project
15
+ cd my-project
16
+ forge start "Build a REST API with user authentication"
17
+
18
+ # Or use with an existing project
19
+ cd my-existing-project
20
+ forge init
21
+ forge start "Add rate limiting to all API endpoints"
22
+ ```
23
+
24
+ ## Commands
25
+
26
+ | Command | Description |
27
+ |---------|-------------|
28
+ | `forge init [name]` | Initialize a project (creates git repo if needed) |
29
+ | `forge start "task"` | Run the full autonomous pipeline |
30
+ | `forge run "task"` | Alias for start |
31
+ | `forge status` | Show current phase and active agents |
32
+ | `forge logs [agent]` | View agent logs |
33
+ | `forge abort` | Kill all running agents |
34
+ | `forge clean` | Remove .forge/ directory and temp branches |
35
+
36
+ ## Options
37
+
38
+ | Option | Default | Description |
39
+ |--------|---------|-------------|
40
+ | `--max-workers N` | 4 | Max parallel workers per lead |
41
+ | `--max-leads N` | 4 | Max parallel leads |
42
+ | `--audit-rounds N` | 2 | Max audit-fix cycles |
43
+ | `--timeout N` | 60 | Timeout in minutes |
44
+ | `--dry-run` | - | Only run spec + planning phases |
45
+ | `--verbose` | - | Show real-time output |
46
+
47
+ ## How It Works
48
+
49
+ Forge runs 7 phases automatically:
50
+
51
+ 0. **Spec** — Architect writes spec, Challenger reviews it (up to 3 rounds)
52
+ 1. **Plan** — Coordinator decomposes spec into parallel work units
53
+ 2. **Implement** — Lead agents spawn Worker agents in isolated git worktrees
54
+ 3. **Integrate** — All branches merged, conflicts resolved, tests run
55
+ 4. **Audit** — Senior auditor spawns junior auditors for security, quality, testing review
56
+ 5. **Fix** — Audit findings routed back for fixes (up to 2 rounds)
57
+ 6. **Finalize** — Merge to main, generate summary report
58
+
59
+ ## Requirements
60
+
61
+ - `git` — version control
62
+ - `tmux` — session management for parallel agents
63
+ - `claude` — [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
64
+ - `jq` — JSON processing
65
+
66
+ ## Optional
67
+
68
+ - **Augment codebase-retrieval** — Semantic code search for better results. Auto-detected if configured.
package/forge ADDED
@@ -0,0 +1,593 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ###############################################################################
5
+ # FORGE — Autonomous Coding Pipeline
6
+ # Main CLI entry point
7
+ ###############################################################################
8
+
9
+ # Determine installation directory (resolve symlinks for npm global installs)
10
+ SOURCE="${BASH_SOURCE[0]}"
11
+ while [ -L "$SOURCE" ]; do
12
+ DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
13
+ SOURCE="$(readlink "$SOURCE")"
14
+ [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
15
+ done
16
+ FORGE_DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
17
+
18
+ # Source all libraries
19
+ source "$FORGE_DIR/lib/utils.sh"
20
+ source "$FORGE_DIR/lib/agent.sh"
21
+ source "$FORGE_DIR/lib/worktree.sh"
22
+ source "$FORGE_DIR/lib/prompts.sh"
23
+ source "$FORGE_DIR/lib/phases/00_spec.sh"
24
+ source "$FORGE_DIR/lib/phases/01_plan.sh"
25
+ source "$FORGE_DIR/lib/phases/02_implement.sh"
26
+ source "$FORGE_DIR/lib/phases/03_integrate.sh"
27
+ source "$FORGE_DIR/lib/phases/04_audit.sh"
28
+ source "$FORGE_DIR/lib/phases/05_fix.sh"
29
+ source "$FORGE_DIR/lib/phases/06_finalize.sh"
30
+
31
+ ###############################################################################
32
+ # Default configuration
33
+ ###############################################################################
34
+ MAX_WORKERS=4
35
+ MAX_LEADS=4
36
+ AUDIT_ROUNDS=2
37
+ TIMEOUT_MINUTES=60
38
+ DRY_RUN=false
39
+ VERBOSE=false
40
+ AUTO_APPROVE=false
41
+ FORGE_TIMEOUT=$((TIMEOUT_MINUTES * 60))
42
+ FORGE_START=$(date +%s)
43
+ AGENT_COUNT=0
44
+ MAX_TOTAL_AGENTS=30
45
+
46
+ export MAX_WORKERS MAX_LEADS AUDIT_ROUNDS TIMEOUT_MINUTES DRY_RUN VERBOSE
47
+ export FORGE_TIMEOUT FORGE_START AGENT_COUNT MAX_TOTAL_AGENTS FORGE_DIR
48
+
49
+ ###############################################################################
50
+ # Usage
51
+ ###############################################################################
52
+ show_usage() {
53
+ cat <<'EOF'
54
+ Usage:
55
+ forge init [project-name] Initialize a project for forge
56
+ forge start <task description> Run the full pipeline
57
+ forge run <task description> Alias for start
58
+ forge status Show current phase and active agents
59
+ forge logs [agent-name] Tail an agent's log
60
+ forge abort Kill all agents and cleanup
61
+ forge clean Remove .forge/ directory and branches
62
+
63
+ Options:
64
+ --auto Skip approval gate, fully autonomous
65
+ --max-workers N Max parallel workers per lead (default: 4)
66
+ --max-leads N Max parallel leads (default: 4)
67
+ --audit-rounds N Max audit-fix cycles (default: 2)
68
+ --timeout MINUTES Kill everything after this long (default: 60)
69
+ --dry-run Only run spec + planning phases
70
+ --verbose Show real-time agent output
71
+ --help, -h Show this help
72
+
73
+ Examples:
74
+ forge init my-app
75
+ forge start Add user authentication with JWT
76
+ forge start Refactor the payment module to use Stripe
77
+ forge --auto start Build a REST API with CRUD endpoints
78
+ EOF
79
+ }
80
+
81
+ ###############################################################################
82
+ # Preflight check
83
+ ###############################################################################
84
+ preflight_check() {
85
+ local skip_git_check="${1:-false}"
86
+ local missing=()
87
+
88
+ # Check required tools
89
+ command -v git >/dev/null 2>&1 || missing+=("git")
90
+ command -v tmux >/dev/null 2>&1 || missing+=("tmux")
91
+ command -v claude >/dev/null 2>&1 || missing+=("claude")
92
+ command -v jq >/dev/null 2>&1 || missing+=("jq")
93
+
94
+ if [ ${#missing[@]} -gt 0 ]; then
95
+ log_error "Missing required tools: ${missing[*]}"
96
+ log_error "Please install them before running forge."
97
+ exit 1
98
+ fi
99
+
100
+ # Check we're in a git repo (unless caller says to skip)
101
+ if [ "$skip_git_check" != "true" ]; then
102
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
103
+ log_error "Not inside a git repository. Run 'forge init' first."
104
+ exit 1
105
+ fi
106
+ fi
107
+
108
+ # Detect Augment MCP tool
109
+ AUGMENT_AVAILABLE=false
110
+ AUGMENT_TOOL_NAME=""
111
+
112
+ local augment_line
113
+ augment_line="$(claude mcp list 2>/dev/null | grep -i "augment\|auggie\|codebase-retrieval" | head -1 || true)"
114
+
115
+ if [ -n "$augment_line" ]; then
116
+ AUGMENT_AVAILABLE=true
117
+ AUGMENT_TOOL_NAME="$(echo "$augment_line" | awk '{print $1}')"
118
+ fi
119
+
120
+ export AUGMENT_AVAILABLE AUGMENT_TOOL_NAME
121
+
122
+ # Ensure .forge directory exists
123
+ local forge_root
124
+ forge_root="$(get_forge_root)"
125
+ mkdir -p "$forge_root/.forge"
126
+
127
+ # Save config
128
+ cat > "$forge_root/.forge/config.json" <<CONF
129
+ {
130
+ "max_workers": $MAX_WORKERS,
131
+ "max_leads": $MAX_LEADS,
132
+ "audit_rounds": $AUDIT_ROUNDS,
133
+ "timeout_minutes": $TIMEOUT_MINUTES,
134
+ "dry_run": $DRY_RUN,
135
+ "verbose": $VERBOSE,
136
+ "auto_approve": $AUTO_APPROVE,
137
+ "augment_available": $AUGMENT_AVAILABLE,
138
+ "augment_tool_name": "$AUGMENT_TOOL_NAME",
139
+ "max_total_agents": $MAX_TOTAL_AGENTS,
140
+ "started_at": $FORGE_START
141
+ }
142
+ CONF
143
+
144
+ log_info "Preflight check passed."
145
+ }
146
+
147
+ ###############################################################################
148
+ # Global timeout checker
149
+ ###############################################################################
150
+ check_global_timeout() {
151
+ local now elapsed_minutes
152
+ now=$(date +%s)
153
+ elapsed_minutes=$(( (now - FORGE_START) / 60 ))
154
+
155
+ if [ "$elapsed_minutes" -ge "$TIMEOUT_MINUTES" ]; then
156
+ log_error "Global timeout reached (${TIMEOUT_MINUTES} minutes). Aborting all agents."
157
+ forge_abort
158
+ exit 1
159
+ fi
160
+ }
161
+
162
+ ###############################################################################
163
+ # Init command (idempotent)
164
+ ###############################################################################
165
+ forge_init() {
166
+ local project_name="${1:-}"
167
+
168
+ # If project name given, create directory and cd into it
169
+ if [ -n "$project_name" ]; then
170
+ mkdir -p "$project_name"
171
+ cd "$project_name"
172
+ fi
173
+
174
+ # If not already a git repo, initialize one
175
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
176
+ git init
177
+ git commit --allow-empty -m "Initial commit"
178
+ log_info "Initialized git repository."
179
+ else
180
+ log_info "Git repository already exists."
181
+ fi
182
+
183
+ # Add .forge/ to .gitignore (idempotent — skip if already present)
184
+ if [ -f .gitignore ] && grep -qxF '.forge/' .gitignore 2>/dev/null; then
185
+ log_info ".forge/ already in .gitignore."
186
+ else
187
+ echo '.forge/' >> .gitignore
188
+ git add .gitignore
189
+ if ! git diff --cached --quiet 2>/dev/null; then
190
+ git commit -m "Update gitignore"
191
+ fi
192
+ log_info "Added .forge/ to .gitignore."
193
+ fi
194
+
195
+ # Run preflight (skip git check since we just ensured it)
196
+ preflight_check "true"
197
+
198
+ log_success "Forge initialized. Run: forge start <your task>"
199
+ }
200
+
201
+ ###############################################################################
202
+ # Approval gate — shows plan summary and asks user to confirm
203
+ ###############################################################################
204
+ show_plan_summary() {
205
+ local forge_root="$1"
206
+
207
+ echo ""
208
+ printf "${BOLD}${CYAN}═══════════════════════════════════════${RESET}\n"
209
+ printf "${BOLD}${CYAN} PLAN REVIEW${RESET}\n"
210
+ printf "${BOLD}${CYAN}═══════════════════════════════════════${RESET}\n"
211
+ echo ""
212
+
213
+ # Show leads and workers summary from plan.json
214
+ local plan_file="$forge_root/.forge/plan.json"
215
+ local total_leads
216
+ total_leads="$(jq -r '.leads | length' "$plan_file" 2>/dev/null || echo 0)"
217
+
218
+ echo "Leads: $total_leads"
219
+ echo ""
220
+
221
+ local i=0
222
+ while [ "$i" -lt "$total_leads" ]; do
223
+ local lead_name lead_module workers_count
224
+ lead_name="$(jq -r ".leads[$i].name" "$plan_file")"
225
+ lead_module="$(jq -r ".leads[$i].module // .leads[$i].name" "$plan_file")"
226
+ workers_count="$(jq -r ".leads[$i].workers | length" "$plan_file")"
227
+
228
+ printf " ${BOLD}%s${RESET} (%s workers)\n" "$lead_module" "$workers_count"
229
+
230
+ local j=0
231
+ while [ "$j" -lt "$workers_count" ]; do
232
+ local worker_name worker_task
233
+ worker_name="$(jq -r ".leads[$i].workers[$j].name // .leads[$i].workers[$j].id" "$plan_file")"
234
+ worker_task="$(jq -r ".leads[$i].workers[$j].task // .leads[$i].workers[$j].task_description" "$plan_file" | head -c 80)"
235
+ printf " - %s: %s\n" "$worker_name" "$worker_task"
236
+ j=$((j + 1))
237
+ done
238
+ echo ""
239
+ i=$((i + 1))
240
+ done
241
+
242
+ echo "Full spec: .forge/spec/spec.md"
243
+ echo "Full plan: .forge/plan.json"
244
+ echo ""
245
+ }
246
+
247
+ wait_for_approval() {
248
+ printf "${BOLD}${YELLOW}Approve this plan and start implementation? [Y/n] ${RESET}"
249
+ local answer
250
+ read -r answer </dev/tty
251
+ case "$answer" in
252
+ n|N|no|No|NO)
253
+ log_info "Aborted. Edit .forge/spec/spec.md or .forge/plan.json, then run 'forge start' again."
254
+ exit 0
255
+ ;;
256
+ *)
257
+ log_success "Plan approved. Starting autonomous execution..."
258
+ echo ""
259
+ ;;
260
+ esac
261
+ }
262
+
263
+ ###############################################################################
264
+ # Status command
265
+ ###############################################################################
266
+ forge_status() {
267
+ local forge_root
268
+ forge_root="$(get_forge_root)"
269
+
270
+ echo ""
271
+ echo "=== Forge Status ==="
272
+ echo ""
273
+
274
+ # Determine current phase
275
+ if [ -f "$forge_root/.forge/status/finalize.done" ]; then
276
+ echo "Phase: COMPLETE"
277
+ elif [ -f "$forge_root/.forge/status/fix.done" ]; then
278
+ echo "Phase: Post-fix (audit/fix cycles)"
279
+ elif [ -f "$forge_root/.forge/status/audit.done" ]; then
280
+ echo "Phase: Audit complete"
281
+ elif [ -f "$forge_root/.forge/status/integrate.done" ]; then
282
+ echo "Phase: Integration complete"
283
+ elif [ -f "$forge_root/.forge/status/implement.done" ]; then
284
+ echo "Phase: Implementation complete"
285
+ elif [ -f "$forge_root/.forge/status/plan.done" ]; then
286
+ echo "Phase: Planning complete"
287
+ elif [ -f "$forge_root/.forge/status/spec.done" ]; then
288
+ echo "Phase: Spec complete"
289
+ else
290
+ echo "Phase: Not started or in progress"
291
+ fi
292
+
293
+ echo ""
294
+
295
+ # List active tmux sessions
296
+ echo "Active forge sessions:"
297
+ local sessions
298
+ sessions="$(tmux list-sessions 2>/dev/null | grep "^forge-" || true)"
299
+ if [ -n "$sessions" ]; then
300
+ echo "$sessions"
301
+ else
302
+ echo " (none)"
303
+ fi
304
+
305
+ echo ""
306
+
307
+ # Show agent counts
308
+ local expected=0 done=0
309
+ if [ -d "$forge_root/.forge/status" ]; then
310
+ expected=$(find "$forge_root/.forge/status" -name "*.expected" 2>/dev/null | wc -l | tr -d ' ')
311
+ done=$(find "$forge_root/.forge/status" -name "*.done" 2>/dev/null | wc -l | tr -d ' ')
312
+ fi
313
+ echo "Agents: $done / $expected completed"
314
+ echo ""
315
+ }
316
+
317
+ ###############################################################################
318
+ # Logs command
319
+ ###############################################################################
320
+ forge_logs() {
321
+ local agent_name="${1:-}"
322
+ local forge_root
323
+ forge_root="$(get_forge_root)"
324
+ local log_dir="$forge_root/.forge/logs"
325
+
326
+ if [ ! -d "$log_dir" ]; then
327
+ log_warn "No log directory found at $log_dir"
328
+ return 0
329
+ fi
330
+
331
+ if [ -n "$agent_name" ]; then
332
+ local log_file="$log_dir/${agent_name}.log"
333
+ if [ -f "$log_file" ]; then
334
+ tail -f "$log_file"
335
+ else
336
+ log_error "Log file not found: $log_file"
337
+ echo "Available logs:"
338
+ ls -1 "$log_dir"/*.log 2>/dev/null || echo " (none)"
339
+ fi
340
+ else
341
+ echo "Available log files:"
342
+ ls -1 "$log_dir"/*.log 2>/dev/null || echo " (none)"
343
+ fi
344
+ }
345
+
346
+ ###############################################################################
347
+ # Abort command
348
+ ###############################################################################
349
+ forge_abort() {
350
+ log_warn "Aborting all forge agents..."
351
+
352
+ local sessions
353
+ sessions="$(tmux list-sessions 2>/dev/null | grep "^forge-" | cut -d: -f1 || true)"
354
+ if [ -n "$sessions" ]; then
355
+ while IFS= read -r session; do
356
+ tmux kill-session -t "$session" 2>/dev/null || true
357
+ log_info "Killed session: $session"
358
+ done <<< "$sessions"
359
+ else
360
+ log_info "No active forge sessions found."
361
+ fi
362
+
363
+ log_warn "All forge agents aborted."
364
+ }
365
+
366
+ ###############################################################################
367
+ # Clean command
368
+ ###############################################################################
369
+ forge_clean() {
370
+ local forge_root
371
+ forge_root="$(get_forge_root)"
372
+
373
+ forge_abort
374
+
375
+ if [ -d "$forge_root/.forge" ]; then
376
+ rm -rf "$forge_root/.forge"
377
+ log_info "Removed .forge directory."
378
+ fi
379
+
380
+ local worktrees
381
+ worktrees="$(git worktree list --porcelain 2>/dev/null | grep "^worktree " | grep "forge" | sed 's/^worktree //' || true)"
382
+ if [ -n "$worktrees" ]; then
383
+ while IFS= read -r wt; do
384
+ git worktree remove --force "$wt" 2>/dev/null || true
385
+ done <<< "$worktrees"
386
+ fi
387
+
388
+ git worktree prune 2>/dev/null || true
389
+ log_success "Forge workspace cleaned."
390
+ }
391
+
392
+ ###############################################################################
393
+ # Argument parsing
394
+ #
395
+ # Handles three styles:
396
+ # forge start Add user auth with JWT (all args after "start" = prompt)
397
+ # forge Add user auth with JWT (all non-option args = prompt)
398
+ # forge --auto start Build me an app (options before subcommand)
399
+ ###############################################################################
400
+ USER_PROMPT=""
401
+ SUBCOMMAND=""
402
+ INIT_PROJECT=""
403
+ LOGS_AGENT=""
404
+ PROMPT_ARGS=()
405
+
406
+ while [[ $# -gt 0 ]]; do
407
+ case "$1" in
408
+ --max-workers)
409
+ MAX_WORKERS="$2"; shift 2 ;;
410
+ --max-leads)
411
+ MAX_LEADS="$2"; shift 2 ;;
412
+ --audit-rounds)
413
+ AUDIT_ROUNDS="$2"; shift 2 ;;
414
+ --timeout)
415
+ TIMEOUT_MINUTES="$2"
416
+ FORGE_TIMEOUT=$((TIMEOUT_MINUTES * 60))
417
+ shift 2 ;;
418
+ --dry-run)
419
+ DRY_RUN=true; shift ;;
420
+ --verbose)
421
+ VERBOSE=true; shift ;;
422
+ --auto)
423
+ AUTO_APPROVE=true; shift ;;
424
+ --help|-h)
425
+ show_usage; exit 0 ;;
426
+ init)
427
+ SUBCOMMAND="init"
428
+ shift
429
+ # Everything after init is the project name (just first arg)
430
+ INIT_PROJECT="${1:-}"
431
+ [ -n "$INIT_PROJECT" ] && shift
432
+ break
433
+ ;;
434
+ start|run)
435
+ SUBCOMMAND="$1"
436
+ shift
437
+ # Everything remaining is the prompt
438
+ PROMPT_ARGS+=("$@")
439
+ break
440
+ ;;
441
+ status)
442
+ SUBCOMMAND="status"; shift; break ;;
443
+ logs)
444
+ SUBCOMMAND="logs"; shift
445
+ LOGS_AGENT="${1:-}"
446
+ [ -n "$LOGS_AGENT" ] && shift
447
+ break
448
+ ;;
449
+ abort)
450
+ SUBCOMMAND="abort"; shift; break ;;
451
+ clean)
452
+ SUBCOMMAND="clean"; shift; break ;;
453
+ -*)
454
+ log_error "Unknown option: $1"
455
+ show_usage; exit 1 ;;
456
+ *)
457
+ # No subcommand — collect everything as prompt args
458
+ PROMPT_ARGS+=("$1")
459
+ shift
460
+ ;;
461
+ esac
462
+ done
463
+
464
+ # Join prompt args into a single string
465
+ if [ ${#PROMPT_ARGS[@]} -gt 0 ]; then
466
+ USER_PROMPT="${PROMPT_ARGS[*]}"
467
+ fi
468
+
469
+ # Re-export updated values after argument parsing
470
+ export MAX_WORKERS MAX_LEADS AUDIT_ROUNDS TIMEOUT_MINUTES DRY_RUN VERBOSE
471
+ export FORGE_TIMEOUT AUTO_APPROVE
472
+
473
+ ###############################################################################
474
+ # Main pipeline
475
+ ###############################################################################
476
+ run_pipeline() {
477
+ local user_prompt="$1"
478
+ local truncated_prompt="${user_prompt:0:72}"
479
+
480
+ echo ""
481
+ printf "${BOLD}═══════════════════════════════════════${RESET}\n"
482
+ printf "${BOLD} FORGE${RESET}\n"
483
+ printf " ${truncated_prompt}\n"
484
+ printf "${BOLD}═══════════════════════════════════════${RESET}\n"
485
+ echo ""
486
+
487
+ # Initialize
488
+ FORGE_ROOT="$(get_forge_root)"
489
+ export FORGE_ROOT
490
+
491
+ mkdir -p "$FORGE_ROOT/.forge/status"
492
+ mkdir -p "$FORGE_ROOT/.forge/logs"
493
+
494
+ # Preflight
495
+ preflight_check
496
+
497
+ # Phase 0: Spec Creation
498
+ run_phase_spec "$user_prompt" || { log_error "Phase 0 failed"; exit 1; }
499
+ check_global_timeout
500
+
501
+ # Phase 1: Planning
502
+ run_phase_plan || { log_error "Phase 1 failed"; exit 1; }
503
+ check_global_timeout
504
+
505
+ # Dry run stops here
506
+ if [ "$DRY_RUN" = true ]; then
507
+ log_success "Dry run complete. Check .forge/spec/spec.md and .forge/plan.json"
508
+ exit 0
509
+ fi
510
+
511
+ # ── Approval gate ─────────────────────────────────────────────
512
+ if [ "$AUTO_APPROVE" = true ]; then
513
+ log_info "Auto-approve enabled, skipping review."
514
+ else
515
+ show_plan_summary "$FORGE_ROOT"
516
+ wait_for_approval
517
+ fi
518
+
519
+ # ── From here on, fully autonomous ────────────────────────────
520
+
521
+ # Phase 2: Implementation
522
+ run_phase_implement || { log_error "Phase 2 failed"; exit 1; }
523
+ check_global_timeout
524
+
525
+ # Phase 3: Integration
526
+ run_phase_integrate || { log_error "Phase 3 failed"; exit 1; }
527
+ check_global_timeout
528
+
529
+ # Phase 4-5: Audit + Fix cycles
530
+ for round in $(seq 1 "$AUDIT_ROUNDS"); do
531
+ run_phase_audit "$round" || { log_warn "Audit round $round had issues"; }
532
+ check_global_timeout
533
+
534
+ local critical_count warning_count
535
+ critical_count=$(jq '[.findings[] | select(.severity == "critical")] | length' \
536
+ "$FORGE_ROOT/.forge/audit/consolidated-audit.json" 2>/dev/null || echo "0")
537
+ warning_count=$(jq '[.findings[] | select(.severity == "warning")] | length' \
538
+ "$FORGE_ROOT/.forge/audit/consolidated-audit.json" 2>/dev/null || echo "0")
539
+
540
+ if [ "$critical_count" -eq 0 ] && [ "$warning_count" -eq 0 ]; then
541
+ log_success "Clean audit in round $round"
542
+ break
543
+ fi
544
+
545
+ run_phase_fix "$round" || { log_warn "Fix round $round had issues"; }
546
+ check_global_timeout
547
+ done
548
+
549
+ # Phase 6: Finalize
550
+ run_phase_finalize "$user_prompt" || { log_error "Phase 6 failed"; exit 1; }
551
+
552
+ log_success "Done. Read .forge/DONE.md"
553
+ }
554
+
555
+ ###############################################################################
556
+ # Entry point
557
+ ###############################################################################
558
+
559
+ # No arguments at all → show help
560
+ if [ -z "${SUBCOMMAND:-}" ] && [ -z "${USER_PROMPT:-}" ]; then
561
+ show_usage
562
+ exit 0
563
+ fi
564
+
565
+ # Dispatch subcommands
566
+ case "${SUBCOMMAND:-}" in
567
+ init)
568
+ forge_init "${INIT_PROJECT:-}"
569
+ ;;
570
+ status)
571
+ forge_status
572
+ ;;
573
+ logs)
574
+ forge_logs "${LOGS_AGENT:-}"
575
+ ;;
576
+ abort)
577
+ forge_abort
578
+ ;;
579
+ clean)
580
+ forge_clean
581
+ ;;
582
+ start|run)
583
+ if [ -z "${USER_PROMPT:-}" ]; then
584
+ log_error "No task provided. Usage: forge start <your task description>"
585
+ exit 1
586
+ fi
587
+ run_pipeline "$USER_PROMPT"
588
+ ;;
589
+ "")
590
+ # No subcommand — bare prompt
591
+ run_pipeline "$USER_PROMPT"
592
+ ;;
593
+ esac