@cyperx/clawforge 1.2.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +312 -0
  3. package/VERSION +1 -0
  4. package/bin/attach.sh +98 -0
  5. package/bin/check-agents.sh +343 -0
  6. package/bin/clawforge +257 -0
  7. package/bin/clawforge-dashboard +0 -0
  8. package/bin/clean.sh +257 -0
  9. package/bin/config.sh +111 -0
  10. package/bin/conflicts.sh +224 -0
  11. package/bin/cost.sh +273 -0
  12. package/bin/dashboard.sh +557 -0
  13. package/bin/diff.sh +109 -0
  14. package/bin/doctor.sh +196 -0
  15. package/bin/eval.sh +217 -0
  16. package/bin/history.sh +91 -0
  17. package/bin/init.sh +182 -0
  18. package/bin/learn.sh +230 -0
  19. package/bin/logs.sh +126 -0
  20. package/bin/memory.sh +207 -0
  21. package/bin/merge-helper.sh +174 -0
  22. package/bin/multi-review.sh +215 -0
  23. package/bin/notify.sh +93 -0
  24. package/bin/on-complete.sh +149 -0
  25. package/bin/parse-cost.sh +205 -0
  26. package/bin/pr.sh +167 -0
  27. package/bin/resume.sh +183 -0
  28. package/bin/review-mode.sh +163 -0
  29. package/bin/review-pr.sh +145 -0
  30. package/bin/routing.sh +88 -0
  31. package/bin/scope-task.sh +169 -0
  32. package/bin/spawn-agent.sh +190 -0
  33. package/bin/sprint.sh +320 -0
  34. package/bin/steer.sh +107 -0
  35. package/bin/stop.sh +136 -0
  36. package/bin/summary.sh +182 -0
  37. package/bin/swarm.sh +525 -0
  38. package/bin/templates.sh +244 -0
  39. package/lib/common.sh +302 -0
  40. package/lib/templates/bugfix.json +6 -0
  41. package/lib/templates/migration.json +7 -0
  42. package/lib/templates/refactor.json +6 -0
  43. package/lib/templates/security-audit.json +5 -0
  44. package/lib/templates/test-coverage.json +6 -0
  45. package/package.json +31 -0
  46. package/registry/conflicts.jsonl +0 -0
  47. package/registry/costs.jsonl +0 -0
  48. package/tui/PRD.md +106 -0
  49. package/tui/agent.go +266 -0
  50. package/tui/animation.go +192 -0
  51. package/tui/dashboard.go +219 -0
  52. package/tui/filter.go +68 -0
  53. package/tui/go.mod +25 -0
  54. package/tui/go.sum +46 -0
  55. package/tui/keybindings.go +229 -0
  56. package/tui/main.go +61 -0
  57. package/tui/model.go +166 -0
  58. package/tui/steer.go +69 -0
  59. package/tui/styles.go +69 -0
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env bash
2
+ # check-agents.sh — Module 4: Health check all running agents
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ source "${SCRIPT_DIR}/../lib/common.sh"
7
+
8
+ # ── Help ───────────────────────────────────────────────────────────────
9
+ PID_FILE="${CLAWFORGE_DIR}/watch.pid"
10
+
11
+ usage() {
12
+ cat <<EOF
13
+ Usage: check-agents.sh [options]
14
+
15
+ Options:
16
+ --json Output as JSON
17
+ --dry-run Don't auto-respawn or update registry
18
+ --daemon Run continuously in background (interval: 5 min)
19
+ --interval N Daemon check interval in seconds (default: 300)
20
+ --stop Stop the running daemon
21
+ --help Show this help
22
+ EOF
23
+ }
24
+
25
+ JSON_OUTPUT=false
26
+ DRY_RUN=false
27
+ DAEMON=false
28
+ DAEMON_INTERVAL=300
29
+ STOP_DAEMON=false
30
+
31
+ while [[ $# -gt 0 ]]; do
32
+ case "$1" in
33
+ --json) JSON_OUTPUT=true; shift ;;
34
+ --dry-run) DRY_RUN=true; shift ;;
35
+ --daemon) DAEMON=true; shift ;;
36
+ --interval) DAEMON_INTERVAL="$2"; shift 2 ;;
37
+ --stop) STOP_DAEMON=true; shift ;;
38
+ --help|-h) usage; exit 0 ;;
39
+ *) log_error "Unknown option: $1"; usage; exit 1 ;;
40
+ esac
41
+ done
42
+
43
+ # ── Stop daemon ───────────────────────────────────────────────────────
44
+ if $STOP_DAEMON; then
45
+ if [[ -f "$PID_FILE" ]]; then
46
+ PID=$(cat "$PID_FILE")
47
+ if kill -0 "$PID" 2>/dev/null; then
48
+ kill "$PID"
49
+ rm -f "$PID_FILE"
50
+ echo "Daemon stopped (PID: $PID)"
51
+ else
52
+ rm -f "$PID_FILE"
53
+ echo "Daemon was not running (stale PID file removed)"
54
+ fi
55
+ else
56
+ echo "No daemon running."
57
+ fi
58
+ exit 0
59
+ fi
60
+
61
+ # ── CI auto-feedback loop ────────────────────────────────────────────
62
+ _ci_feedback() {
63
+ local id="$1" repo="$2" pr="$3" tmux_session="$4" branch="$5"
64
+ local ci_retries ci_limit
65
+
66
+ ci_retries=$(registry_get "$id" | jq -r '.ci_retries // 0')
67
+ # Use per-task max_ci_retries if set, otherwise fall back to config
68
+ ci_limit=$(registry_get "$id" | jq -r '.max_ci_retries // empty' 2>/dev/null || true)
69
+ [[ -z "$ci_limit" ]] && ci_limit=$(config_get ci_retry_limit 2)
70
+
71
+ if [[ "$ci_retries" -ge "$ci_limit" ]]; then
72
+ log_warn "CI retry limit reached for $id ($ci_retries/$ci_limit)"
73
+ return 0
74
+ fi
75
+
76
+ # Fetch failed CI log
77
+ local ci_log
78
+ ci_log=$(gh run list --repo "$repo" --branch "$branch" --status failure --limit 1 --json databaseId -q '.[0].databaseId' 2>/dev/null || true)
79
+ if [[ -z "$ci_log" ]]; then
80
+ return 0
81
+ fi
82
+
83
+ local error_output
84
+ error_output=$(gh run view "$ci_log" --repo "$repo" --log-failed 2>/dev/null | tail -50 || true)
85
+ if [[ -z "$error_output" ]]; then
86
+ return 0
87
+ fi
88
+
89
+ # Auto-steer the agent
90
+ local steer_msg="CI failed on your PR. Here is the error output:
91
+
92
+ ${error_output}
93
+
94
+ Fix the issues and push again."
95
+
96
+ if tmux has-session -t "$tmux_session" 2>/dev/null; then
97
+ if [[ ${#steer_msg} -gt 200 ]]; then
98
+ local tmpfile
99
+ tmpfile=$(mktemp)
100
+ echo "$steer_msg" > "$tmpfile"
101
+ tmux load-buffer "$tmpfile"
102
+ tmux paste-buffer -t "$tmux_session"
103
+ tmux send-keys -t "$tmux_session" Enter
104
+ rm -f "$tmpfile"
105
+ else
106
+ tmux send-keys -t "$tmux_session" "$steer_msg" Enter
107
+ fi
108
+
109
+ # Increment ci_retries
110
+ local new_retries=$((ci_retries + 1))
111
+ registry_update "$id" "ci_retries" "$new_retries"
112
+ log_info "CI feedback sent to $id (retry $new_retries/$ci_limit)"
113
+
114
+ # Notify
115
+ "${SCRIPT_DIR}/notify.sh" --type task-failed --description "CI failed for $id (auto-retry $new_retries)" --dry-run 2>/dev/null || true
116
+ fi
117
+ }
118
+
119
+ _ensure_registry
120
+
121
+ # ── Check each task ───────────────────────────────────────────────────
122
+ TASKS=$(jq -c '.tasks[]' "$REGISTRY_FILE" 2>/dev/null || true)
123
+ if [[ -z "$TASKS" ]]; then
124
+ if $JSON_OUTPUT; then
125
+ echo '{"tasks":[],"summary":"No active tasks"}'
126
+ else
127
+ echo "No active tasks in registry."
128
+ fi
129
+ exit 0
130
+ fi
131
+
132
+ RESULTS="[]"
133
+
134
+ while IFS= read -r task; do
135
+ id=$(echo "$task" | jq -r '.id')
136
+ tmux_session=$(echo "$task" | jq -r '.tmuxSession')
137
+ branch=$(echo "$task" | jq -r '.branch')
138
+ repo=$(echo "$task" | jq -r '.repo')
139
+ status=$(echo "$task" | jq -r '.status')
140
+ retries=$(echo "$task" | jq -r '.retries')
141
+ max_retries=$(echo "$task" | jq -r '.maxRetries')
142
+
143
+ new_status="$status"
144
+ pr_number=""
145
+ ci_status=""
146
+
147
+ # Skip completed tasks
148
+ if [[ "$status" == "done" ]]; then
149
+ RESULTS=$(echo "$RESULTS" | jq --argjson t "$task" '. += [$t]')
150
+ continue
151
+ fi
152
+
153
+ # 1. Check tmux session
154
+ tmux_alive=false
155
+ if tmux has-session -t "$tmux_session" 2>/dev/null; then
156
+ tmux_alive=true
157
+ fi
158
+
159
+ # 2. Check for PR
160
+ if [[ -d "$repo" ]]; then
161
+ pr_info=$(gh pr list --repo "$repo" --head "$branch" --json number,state 2>/dev/null || echo "[]")
162
+ pr_number=$(echo "$pr_info" | jq -r '.[0].number // empty' 2>/dev/null || true)
163
+ fi
164
+
165
+ # 3. Determine status
166
+ if [[ -n "$pr_number" ]]; then
167
+ new_status="pr-created"
168
+ if ! $DRY_RUN; then
169
+ registry_update "$id" "pr" "$pr_number"
170
+ fi
171
+
172
+ # Check CI
173
+ ci_result=$(gh pr checks "$pr_number" --repo "$repo" 2>/dev/null || true)
174
+ if echo "$ci_result" | grep -q "pass"; then
175
+ if ! echo "$ci_result" | grep -qE "fail|pending"; then
176
+ new_status="ci-passing"
177
+ fi
178
+ fi
179
+
180
+ # CI auto-feedback loop: if CI failed and agent is alive, steer with error context
181
+ if echo "$ci_result" | grep -q "fail" && $tmux_alive && ! $DRY_RUN; then
182
+ _ci_feedback "$id" "$repo" "$pr_number" "$tmux_session" "$branch"
183
+ fi
184
+ elif ! $tmux_alive; then
185
+ # tmux dead + no PR = failed
186
+ new_status="failed"
187
+ if ! $DRY_RUN && [[ "$retries" -lt "$max_retries" ]]; then
188
+ log_warn "Task $id failed, respawning (retry $((retries + 1))/$max_retries)..."
189
+ new_retries=$((retries + 1))
190
+ registry_update "$id" "retries" "$new_retries"
191
+ registry_update "$id" "status" '"spawned"'
192
+
193
+ # Respawn
194
+ worktree=$(echo "$task" | jq -r '.worktree')
195
+ agent=$(echo "$task" | jq -r '.agent')
196
+ model=$(echo "$task" | jq -r '.model')
197
+ desc=$(echo "$task" | jq -r '.description')
198
+
199
+ FULL_PROMPT="$desc
200
+
201
+ When complete:
202
+ 1. Commit your changes with a descriptive message
203
+ 2. Push the branch: git push origin $branch
204
+ 3. Create a PR: gh pr create --fill --base main"
205
+
206
+ if [[ "$agent" == "claude" ]]; then
207
+ AGENT_CMD="claude --model ${model} --dangerously-skip-permissions -p \"$(echo "$FULL_PROMPT" | sed 's/"/\\"/g')\""
208
+ else
209
+ AGENT_CMD="codex --model ${model} --dangerously-bypass-approvals-and-sandbox \"$(echo "$FULL_PROMPT" | sed 's/"/\\"/g')\""
210
+ fi
211
+
212
+ tmux kill-session -t "$tmux_session" 2>/dev/null || true
213
+ tmux new-session -d -s "$tmux_session" -c "$worktree" "$AGENT_CMD"
214
+ new_status="running"
215
+ log_info "Respawned task $id"
216
+ elif ! $DRY_RUN && [[ "$retries" -ge "$max_retries" ]]; then
217
+ log_error "Task $id failed after $max_retries retries"
218
+ registry_update "$id" "note" '"Max retries exceeded"'
219
+ fi
220
+ elif $tmux_alive; then
221
+ if [[ "$status" == "spawned" ]]; then
222
+ new_status="running"
223
+ fi
224
+ fi
225
+
226
+ # Update status
227
+ if [[ "$new_status" != "$status" ]] && ! $DRY_RUN; then
228
+ registry_update "$id" "status" "\"$new_status\""
229
+ if [[ "$new_status" == "done" || "$new_status" == "failed" || "$new_status" == "timeout" || "$new_status" == "cancelled" ]]; then
230
+ registry_update "$id" "completedAt" "$(epoch_ms)"
231
+ "${SCRIPT_DIR}/on-complete.sh" "$id" 2>/dev/null &
232
+ log_info "Fired on-complete for $id ($new_status)"
233
+ fi
234
+ fi
235
+
236
+ # Build result entry
237
+ result=$(echo "$task" | jq \
238
+ --arg ns "$new_status" \
239
+ --arg ta "$tmux_alive" \
240
+ --arg pr "${pr_number:-null}" \
241
+ '. + {currentStatus: $ns, tmuxAlive: ($ta == "true"), detectedPR: (if $pr == "null" then null else ($pr | tonumber) end)}')
242
+
243
+ RESULTS=$(echo "$RESULTS" | jq --argjson r "$result" '. += [$r]')
244
+ done <<< "$TASKS"
245
+
246
+ # ── Output ─────────────────────────────────────────────────────────────
247
+ # ── Output ─────────────────────────────────────────────────────────────
248
+ _output_results() {
249
+ if $JSON_OUTPUT; then
250
+ echo "$RESULTS" | jq '{tasks: ., summary: {total: length, running: [.[] | select(.currentStatus == "running")] | length, failed: [.[] | select(.currentStatus == "failed")] | length, done: [.[] | select(.currentStatus == "done")] | length}}'
251
+ else
252
+ echo "=== Agent Status Check ==="
253
+ echo ""
254
+ echo "$RESULTS" | jq -r '.[] | " [\(.currentStatus)] \(.id) — \(.agent)/\(.model) (tmux: \(.tmuxAlive), PR: \(.detectedPR // "none"))"'
255
+ echo ""
256
+ total=$(echo "$RESULTS" | jq 'length')
257
+ running=$(echo "$RESULTS" | jq '[.[] | select(.currentStatus == "running")] | length')
258
+ echo "Total: $total | Running: $running"
259
+ fi
260
+ }
261
+
262
+ _output_results
263
+
264
+ # ── Daemon mode ───────────────────────────────────────────────────────
265
+ if $DAEMON; then
266
+ # Write PID file
267
+ echo $$ > "$PID_FILE"
268
+ log_info "Watch daemon started (PID: $$, interval: ${DAEMON_INTERVAL}s)"
269
+ log_info "PID file: $PID_FILE"
270
+ log_info "Stop with: clawforge watch --stop"
271
+
272
+ _daemon_cleanup() {
273
+ rm -f "$PID_FILE"
274
+ log_info "Watch daemon stopped"
275
+ }
276
+ trap _daemon_cleanup EXIT INT TERM
277
+
278
+ while true; do
279
+ sleep "$DAEMON_INTERVAL"
280
+
281
+ # Re-run the check (re-read tasks from registry)
282
+ TASKS=$(jq -c '.tasks[]' "$REGISTRY_FILE" 2>/dev/null || true)
283
+ if [[ -z "$TASKS" ]]; then
284
+ log_debug "Daemon: no active tasks"
285
+ continue
286
+ fi
287
+
288
+ RESULTS="[]"
289
+ while IFS= read -r task; do
290
+ id=$(echo "$task" | jq -r '.id')
291
+ tmux_session=$(echo "$task" | jq -r '.tmuxSession')
292
+ branch=$(echo "$task" | jq -r '.branch')
293
+ repo=$(echo "$task" | jq -r '.repo')
294
+ status=$(echo "$task" | jq -r '.status')
295
+
296
+ if [[ "$status" == "done" || "$status" == "stopped" || "$status" == "archived" ]]; then
297
+ continue
298
+ fi
299
+
300
+ new_status="$status"
301
+ tmux_alive=false
302
+ tmux has-session -t "$tmux_session" 2>/dev/null && tmux_alive=true
303
+
304
+ pr_number=""
305
+ if [[ -d "$repo" ]]; then
306
+ pr_info=$(gh pr list --repo "$repo" --head "$branch" --json number,state 2>/dev/null || echo "[]")
307
+ pr_number=$(echo "$pr_info" | jq -r '.[0].number // empty' 2>/dev/null || true)
308
+ fi
309
+
310
+ if [[ -n "$pr_number" ]]; then
311
+ if [[ "$status" != "pr-created" && "$status" != "ci-passing" && "$status" != "reviewing" ]]; then
312
+ new_status="pr-created"
313
+ registry_update "$id" "pr" "$pr_number"
314
+ "${SCRIPT_DIR}/notify.sh" --type pr-ready --description "$(echo "$task" | jq -r '.description')" --pr "$pr_number" 2>/dev/null || true
315
+ fi
316
+
317
+ ci_result=$(gh pr checks "$pr_number" --repo "$repo" 2>/dev/null || true)
318
+ if echo "$ci_result" | grep -q "pass" && ! echo "$ci_result" | grep -qE "fail|pending"; then
319
+ new_status="ci-passing"
320
+ elif echo "$ci_result" | grep -q "fail" && $tmux_alive; then
321
+ _ci_feedback "$id" "$repo" "$pr_number" "$tmux_session" "$branch"
322
+ fi
323
+ elif ! $tmux_alive && [[ "$status" == "running" || "$status" == "spawned" ]]; then
324
+ new_status="failed"
325
+ "${SCRIPT_DIR}/notify.sh" --type task-failed --description "$(echo "$task" | jq -r '.description')" 2>/dev/null || true
326
+ fi
327
+
328
+ if [[ "$new_status" != "$status" ]]; then
329
+ registry_update "$id" "status" "\"$new_status\""
330
+ log_info "Daemon: $id status changed: $status → $new_status"
331
+
332
+ # Fire completion hooks on terminal states
333
+ case "$new_status" in
334
+ done|failed|timeout|cancelled)
335
+ registry_update "$id" "completedAt" "$(epoch_ms)"
336
+ "${SCRIPT_DIR}/on-complete.sh" "$id" 2>/dev/null &
337
+ log_info "Daemon: fired on-complete for $id ($new_status)"
338
+ ;;
339
+ esac
340
+ fi
341
+ done <<< "$TASKS"
342
+ done
343
+ fi
package/bin/clawforge ADDED
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env bash
2
+ # clawforge — Unified CLI for agent swarm workflow
3
+ # Routes subcommands to individual module scripts.
4
+ set -euo pipefail
5
+
6
+ # Resolve symlinks to find the real install location
7
+ SOURCE="${BASH_SOURCE[0]}"
8
+ while [[ -L "$SOURCE" ]]; do
9
+ DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
10
+ SOURCE="$(readlink "$SOURCE")"
11
+ [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
12
+ done
13
+ CLAWFORGE_DIR="$(cd "$(dirname "$SOURCE")/.." && pwd)"
14
+ BIN_DIR="${CLAWFORGE_DIR}/bin"
15
+
16
+ # ── Global flags ───────────────────────────────────────────────────────
17
+ PASSTHROUGH_ARGS=()
18
+ for arg in "$@"; do
19
+ if [[ "$arg" == "--verbose" ]]; then
20
+ export CLAWFORGE_DEBUG=1
21
+ else
22
+ PASSTHROUGH_ARGS+=("$arg")
23
+ fi
24
+ done
25
+ set -- "${PASSTHROUGH_ARGS[@]+"${PASSTHROUGH_ARGS[@]}"}"
26
+
27
+ # ── Version ────────────────────────────────────────────────────────────
28
+ show_version() {
29
+ local ver
30
+ ver=$(cat "${CLAWFORGE_DIR}/VERSION" 2>/dev/null || echo "unknown")
31
+ echo "clawforge v${ver}"
32
+ }
33
+
34
+ # ── Help ───────────────────────────────────────────────────────────────
35
+ show_help() {
36
+ local show_all=false
37
+ [[ "${1:-}" == "--all" ]] && show_all=true
38
+
39
+ cat <<EOF
40
+ $(show_version)
41
+ Multi-mode coding workflow CLI — from quick patches to parallel agent orchestration.
42
+
43
+ Usage: clawforge <command> [options]
44
+
45
+ Workflow Modes:
46
+ sprint Single agent, full dev cycle (the workhorse)
47
+ review Quality gate on an existing PR (analysis only)
48
+ swarm Parallel multi-agent orchestration
49
+
50
+ Management:
51
+ status Show all tracked tasks with short IDs
52
+ resume Resume a failed/timeout task from where it left off
53
+ diff Show git diff for a task without attaching
54
+ pr Create a PR from a task branch
55
+ attach Attach to a running agent's tmux session
56
+ steer Send course correction to a running agent
57
+ stop Stop a running agent
58
+ watch Monitor agent health (supports --daemon)
59
+ dashboard Live TUI dashboard with vim keybindings
60
+
61
+ Observability (v0.5):
62
+ cost Token/cost tracking and budget management
63
+ conflicts Swarm conflict detection and resolution
64
+ templates Task template management
65
+
66
+ Fleet Ops (v0.6):
67
+ memory Per-repo agent memory (show, add, search, forget, clear)
68
+ init Scan project and generate initial memory entries
69
+ history Show completed task history
70
+
71
+ Reliability (v0.7):
72
+ doctor Diagnose orphaned sessions, dangling worktrees, stale tasks
73
+
74
+ Observability (v0.9):
75
+ logs Capture agent output from tmux (without attaching)
76
+ on-complete Fire completion hooks for finished tasks
77
+
78
+ Power Features (v1.2):
79
+ config Manage user configuration (~/.clawforge/config.json)
80
+ multi-review Run PR through multiple models and compare feedback
81
+ summary AI-generated summary of what an agent did
82
+ parse-cost Parse real cost/token data from agent output
83
+
84
+ Evaluation (v0.6.2):
85
+ eval Run-eval logging and weekly summaries
86
+
87
+ Lifecycle:
88
+ clean Clean up completed tasks
89
+ learn Record learnings from completed tasks
90
+ EOF
91
+
92
+ if $show_all; then
93
+ cat <<EOF
94
+
95
+ Direct Module Access:
96
+ scope Assemble a prompt with context (Module 1)
97
+ spawn Create worktree + launch coding agent (Module 2)
98
+ notify Send Discord notification (Module 6)
99
+ merge Merge helper with safety checks (Module 7)
100
+ run Scope + spawn in one step (legacy shortcut)
101
+ EOF
102
+ fi
103
+
104
+ cat <<EOF
105
+
106
+ Meta:
107
+ help Show this help (use --all for direct module commands)
108
+ version Show version
109
+
110
+ Global flags:
111
+ --verbose Enable debug logging
112
+
113
+ Examples:
114
+ clawforge sprint "Add JWT authentication middleware"
115
+ clawforge sprint "Fix typo in readme" --quick
116
+ clawforge sprint --template refactor "Refactor auth module"
117
+ clawforge review --pr 42
118
+ clawforge swarm "Migrate all tests from jest to vitest"
119
+ clawforge swarm --template migration "Migrate to TypeScript" --ci-loop
120
+ clawforge swarm --repos ~/api,~/web,~/shared "Upgrade auth library"
121
+ clawforge sprint --routing auto "Refactor auth service"
122
+ clawforge steer 1 "Use bcrypt instead of md5"
123
+ clawforge dashboard
124
+ clawforge cost --summary
125
+ clawforge memory show
126
+ clawforge memory add "Always run tests first"
127
+ clawforge init
128
+ clawforge history --repo api
129
+ clawforge eval log --command sprint --mode quick --repo api --status ok --duration-ms 420000
130
+ clawforge eval weekly
131
+ clawforge templates
132
+ EOF
133
+ }
134
+
135
+ # ── Dashboard (v0.5: routes to TUI, legacy fallback kept as show_dashboard_legacy) ─
136
+
137
+ # ── Run shortcut (scope + spawn) ──────────────────────────────────────
138
+ run_combined() {
139
+ # Parse --repo, --branch, --task from args, pass rest to spawn
140
+ local repo="" branch="" task=""
141
+ local extra_args=()
142
+
143
+ while [[ $# -gt 0 ]]; do
144
+ case "$1" in
145
+ --repo) repo="$2"; shift 2 ;;
146
+ --branch) branch="$2"; shift 2 ;;
147
+ --task) task="$2"; shift 2 ;;
148
+ *) extra_args+=("$1"); shift ;;
149
+ esac
150
+ done
151
+
152
+ if [[ -z "$repo" || -z "$branch" || -z "$task" ]]; then
153
+ echo "Usage: clawforge run --repo <path> --branch <name> --task \"description\"" >&2
154
+ echo "All three flags are required." >&2
155
+ exit 1
156
+ fi
157
+
158
+ echo "── Step 1: Scope ─────────────────────────────────────────────"
159
+ local prompt
160
+ prompt=$("${BIN_DIR}/scope-task.sh" --task "$task" "${extra_args[@]+"${extra_args[@]}"}" 2>/dev/null || echo "$task")
161
+
162
+ echo "── Step 2: Spawn ─────────────────────────────────────────────"
163
+ "${BIN_DIR}/spawn-agent.sh" --repo "$repo" --branch "$branch" --task "$prompt" "${extra_args[@]+"${extra_args[@]}"}"
164
+ }
165
+
166
+ # ── Status (pretty wrapper around registry) ────────────────────────────
167
+ show_status() {
168
+ source "${CLAWFORGE_DIR}/lib/common.sh"
169
+ local tasks
170
+ tasks=$(registry_list "$@" 2>/dev/null || echo "[]")
171
+ local count
172
+ count=$(echo "$tasks" | jq 'length' 2>/dev/null || echo 0)
173
+
174
+ if [[ "$count" == "0" ]]; then
175
+ echo "No active tasks."
176
+ return 0
177
+ fi
178
+
179
+ # Enhanced status with short IDs and mode column
180
+ echo "$tasks" | jq -r '.[] |
181
+ def sid: if .short_id then "#\(.short_id)" else .id end;
182
+ def mode: .mode // "—";
183
+ def elapsed:
184
+ (now * 1000 - (.startedAt // 0)) / 60000 | floor | tostring + "m ago";
185
+ def extra:
186
+ if .mode == "swarm" and .sub_task_count then " (\(.sub_task_count) agents)" else "" end;
187
+ " \(sid) \(mode) \(.status // "?") \(.repo // "" | split("/") | last) \"\(.description // "")[0:50]\"\(extra)"
188
+ ' 2>/dev/null || \
189
+ echo "$tasks" | jq -r '.[] | "[\(.status // "?")] \(.id) \(.agent // "")/\(.model // "") \(.task // .description // "")[0:60]"' 2>/dev/null || echo "$tasks"
190
+ }
191
+
192
+ # ── Route subcommand ───────────────────────────────────────────────────
193
+ COMMAND="${1:-help}"
194
+ shift 2>/dev/null || true
195
+
196
+ case "$COMMAND" in
197
+ # Workflow modes (v0.4)
198
+ sprint) exec "${BIN_DIR}/sprint.sh" "$@" ;;
199
+ review) exec "${BIN_DIR}/review-mode.sh" "$@" ;;
200
+ swarm) exec "${BIN_DIR}/swarm.sh" "$@" ;;
201
+
202
+ # Management commands (v0.4)
203
+ steer) exec "${BIN_DIR}/steer.sh" "$@" ;;
204
+ attach) exec "${BIN_DIR}/attach.sh" "$@" ;;
205
+ stop) exec "${BIN_DIR}/stop.sh" "$@" ;;
206
+
207
+ # Existing commands
208
+ status) show_status "$@" ;;
209
+ watch) exec "${BIN_DIR}/check-agents.sh" "$@" ;;
210
+ dashboard)
211
+ # Prefer Go TUI binary if available, fallback to dashboard.sh
212
+ if [[ -x "${BIN_DIR}/clawforge-dashboard" ]]; then
213
+ exec "${BIN_DIR}/clawforge-dashboard" "$@"
214
+ else
215
+ exec "${BIN_DIR}/dashboard.sh" "$@"
216
+ fi
217
+ ;;
218
+ clean) exec "${BIN_DIR}/clean.sh" "$@" ;;
219
+ learn) exec "${BIN_DIR}/learn.sh" "$@" ;;
220
+
221
+ # v0.5 observability + intelligence
222
+ cost) exec "${BIN_DIR}/cost.sh" "$@" ;;
223
+ conflicts) exec "${BIN_DIR}/conflicts.sh" "$@" ;;
224
+ templates) exec "${BIN_DIR}/templates.sh" "$@" ;;
225
+
226
+ # v0.6 fleet operations
227
+ memory) exec "${BIN_DIR}/memory.sh" "$@" ;;
228
+ init) exec "${BIN_DIR}/init.sh" "$@" ;;
229
+ history) exec "${BIN_DIR}/history.sh" "$@" ;;
230
+ eval) exec "${BIN_DIR}/eval.sh" "$@" ;;
231
+ doctor) exec "${BIN_DIR}/doctor.sh" "$@" ;;
232
+ resume) exec "${BIN_DIR}/resume.sh" "$@" ;;
233
+ diff) exec "${BIN_DIR}/diff.sh" "$@" ;;
234
+ pr) exec "${BIN_DIR}/pr.sh" "$@" ;;
235
+ config) exec "${BIN_DIR}/config.sh" "$@" ;;
236
+ multi-review) exec "${BIN_DIR}/multi-review.sh" "$@" ;;
237
+ summary) exec "${BIN_DIR}/summary.sh" "$@" ;;
238
+ parse-cost) exec "${BIN_DIR}/parse-cost.sh" "$@" ;;
239
+ logs) exec "${BIN_DIR}/logs.sh" "$@" ;;
240
+ on-complete) exec "${BIN_DIR}/on-complete.sh" "$@" ;;
241
+
242
+ # Direct module access (hidden from main help)
243
+ scope) exec "${BIN_DIR}/scope-task.sh" "$@" ;;
244
+ spawn) exec "${BIN_DIR}/spawn-agent.sh" "$@" ;;
245
+ notify) exec "${BIN_DIR}/notify.sh" "$@" ;;
246
+ merge) exec "${BIN_DIR}/merge-helper.sh" "$@" ;;
247
+ run) run_combined "$@" ;;
248
+
249
+ # Meta
250
+ version|-v|--version) show_version ;;
251
+ help|-h|--help) show_help "$@" ;;
252
+ *)
253
+ echo "Unknown command: $COMMAND" >&2
254
+ echo "Run 'clawforge help' for usage." >&2
255
+ exit 1
256
+ ;;
257
+ esac
Binary file