shipwright-cli 1.10.0 → 2.0.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 (108) hide show
  1. package/README.md +114 -36
  2. package/completions/_shipwright +212 -32
  3. package/completions/shipwright.bash +97 -25
  4. package/docs/strategy/01-market-research.md +619 -0
  5. package/docs/strategy/02-mission-and-brand.md +587 -0
  6. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  7. package/docs/strategy/QUICK-START.txt +289 -0
  8. package/docs/strategy/README.md +172 -0
  9. package/package.json +4 -2
  10. package/scripts/sw +208 -1
  11. package/scripts/sw-activity.sh +500 -0
  12. package/scripts/sw-adaptive.sh +925 -0
  13. package/scripts/sw-adversarial.sh +1 -1
  14. package/scripts/sw-architecture-enforcer.sh +1 -1
  15. package/scripts/sw-auth.sh +613 -0
  16. package/scripts/sw-autonomous.sh +664 -0
  17. package/scripts/sw-changelog.sh +704 -0
  18. package/scripts/sw-checkpoint.sh +1 -1
  19. package/scripts/sw-ci.sh +602 -0
  20. package/scripts/sw-cleanup.sh +1 -1
  21. package/scripts/sw-code-review.sh +637 -0
  22. package/scripts/sw-connect.sh +1 -1
  23. package/scripts/sw-context.sh +605 -0
  24. package/scripts/sw-cost.sh +1 -1
  25. package/scripts/sw-daemon.sh +432 -130
  26. package/scripts/sw-dashboard.sh +1 -1
  27. package/scripts/sw-db.sh +540 -0
  28. package/scripts/sw-decompose.sh +539 -0
  29. package/scripts/sw-deps.sh +551 -0
  30. package/scripts/sw-developer-simulation.sh +1 -1
  31. package/scripts/sw-discovery.sh +412 -0
  32. package/scripts/sw-docs-agent.sh +539 -0
  33. package/scripts/sw-docs.sh +1 -1
  34. package/scripts/sw-doctor.sh +59 -1
  35. package/scripts/sw-dora.sh +615 -0
  36. package/scripts/sw-durable.sh +710 -0
  37. package/scripts/sw-e2e-orchestrator.sh +535 -0
  38. package/scripts/sw-eventbus.sh +393 -0
  39. package/scripts/sw-feedback.sh +471 -0
  40. package/scripts/sw-fix.sh +1 -1
  41. package/scripts/sw-fleet-discover.sh +567 -0
  42. package/scripts/sw-fleet-viz.sh +404 -0
  43. package/scripts/sw-fleet.sh +8 -1
  44. package/scripts/sw-github-app.sh +596 -0
  45. package/scripts/sw-github-checks.sh +1 -1
  46. package/scripts/sw-github-deploy.sh +1 -1
  47. package/scripts/sw-github-graphql.sh +1 -1
  48. package/scripts/sw-guild.sh +569 -0
  49. package/scripts/sw-heartbeat.sh +1 -1
  50. package/scripts/sw-hygiene.sh +559 -0
  51. package/scripts/sw-incident.sh +617 -0
  52. package/scripts/sw-init.sh +88 -1
  53. package/scripts/sw-instrument.sh +699 -0
  54. package/scripts/sw-intelligence.sh +1 -1
  55. package/scripts/sw-jira.sh +1 -1
  56. package/scripts/sw-launchd.sh +363 -28
  57. package/scripts/sw-linear.sh +1 -1
  58. package/scripts/sw-logs.sh +1 -1
  59. package/scripts/sw-loop.sh +64 -3
  60. package/scripts/sw-memory.sh +1 -1
  61. package/scripts/sw-mission-control.sh +487 -0
  62. package/scripts/sw-model-router.sh +545 -0
  63. package/scripts/sw-otel.sh +596 -0
  64. package/scripts/sw-oversight.sh +689 -0
  65. package/scripts/sw-pipeline-composer.sh +1 -1
  66. package/scripts/sw-pipeline-vitals.sh +1 -1
  67. package/scripts/sw-pipeline.sh +687 -24
  68. package/scripts/sw-pm.sh +693 -0
  69. package/scripts/sw-pr-lifecycle.sh +522 -0
  70. package/scripts/sw-predictive.sh +1 -1
  71. package/scripts/sw-prep.sh +1 -1
  72. package/scripts/sw-ps.sh +1 -1
  73. package/scripts/sw-public-dashboard.sh +798 -0
  74. package/scripts/sw-quality.sh +595 -0
  75. package/scripts/sw-reaper.sh +1 -1
  76. package/scripts/sw-recruit.sh +573 -0
  77. package/scripts/sw-regression.sh +642 -0
  78. package/scripts/sw-release-manager.sh +736 -0
  79. package/scripts/sw-release.sh +706 -0
  80. package/scripts/sw-remote.sh +1 -1
  81. package/scripts/sw-replay.sh +520 -0
  82. package/scripts/sw-retro.sh +691 -0
  83. package/scripts/sw-scale.sh +444 -0
  84. package/scripts/sw-security-audit.sh +505 -0
  85. package/scripts/sw-self-optimize.sh +1 -1
  86. package/scripts/sw-session.sh +1 -1
  87. package/scripts/sw-setup.sh +1 -1
  88. package/scripts/sw-standup.sh +712 -0
  89. package/scripts/sw-status.sh +1 -1
  90. package/scripts/sw-strategic.sh +658 -0
  91. package/scripts/sw-stream.sh +450 -0
  92. package/scripts/sw-swarm.sh +583 -0
  93. package/scripts/sw-team-stages.sh +511 -0
  94. package/scripts/sw-templates.sh +1 -1
  95. package/scripts/sw-testgen.sh +515 -0
  96. package/scripts/sw-tmux-pipeline.sh +554 -0
  97. package/scripts/sw-tmux.sh +1 -1
  98. package/scripts/sw-trace.sh +485 -0
  99. package/scripts/sw-tracker-github.sh +188 -0
  100. package/scripts/sw-tracker-jira.sh +172 -0
  101. package/scripts/sw-tracker-linear.sh +251 -0
  102. package/scripts/sw-tracker.sh +117 -2
  103. package/scripts/sw-triage.sh +603 -0
  104. package/scripts/sw-upgrade.sh +1 -1
  105. package/scripts/sw-ux.sh +677 -0
  106. package/scripts/sw-webhook.sh +627 -0
  107. package/scripts/sw-widgets.sh +530 -0
  108. package/scripts/sw-worktree.sh +1 -1
@@ -0,0 +1,664 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright autonomous — Master controller for AI-building-AI loop ║
4
+ # ║ Analyze → Create issues → Build → Learn → Repeat ║
5
+ # ║ Closes the loop: PM creates issues, daemon builds, system learns ║
6
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
7
+ set -euo pipefail
8
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
+
10
+ VERSION="2.0.0"
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
+
14
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
15
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
16
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
17
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
18
+ GREEN='\033[38;2;74;222;128m' # success
19
+ YELLOW='\033[38;2;250;204;21m' # warning
20
+ RED='\033[38;2;248;113;113m' # error
21
+ DIM='\033[2m'
22
+ BOLD='\033[1m'
23
+ RESET='\033[0m'
24
+
25
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
26
+ # shellcheck source=lib/compat.sh
27
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
28
+
29
+ # ─── Output Helpers ─────────────────────────────────────────────────────────
30
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
31
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
32
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
33
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
34
+
35
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
36
+ now_epoch() { date +%s; }
37
+
38
+ format_duration() {
39
+ local secs="$1"
40
+ if [[ "$secs" -ge 3600 ]]; then
41
+ printf "%dh %dm %ds" $((secs/3600)) $((secs%3600/60)) $((secs%60))
42
+ elif [[ "$secs" -ge 60 ]]; then
43
+ printf "%dm %ds" $((secs/60)) $((secs%60))
44
+ else
45
+ printf "%ds" "$secs"
46
+ fi
47
+ }
48
+
49
+ # ─── Structured Event Log ──────────────────────────────────────────────────
50
+ EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
51
+
52
+ emit_event() {
53
+ local event_type="$1"
54
+ shift
55
+ local json_fields=""
56
+ for kv in "$@"; do
57
+ local key="${kv%%=*}"
58
+ local val="${kv#*=}"
59
+ if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
60
+ json_fields="${json_fields},\"${key}\":${val}"
61
+ else
62
+ val="${val//\"/\\\"}"
63
+ json_fields="${json_fields},\"${key}\":\"${val}\""
64
+ fi
65
+ done
66
+ mkdir -p "${HOME}/.shipwright"
67
+ echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
68
+ }
69
+
70
+ # ─── State & Config Paths ─────────────────────────────────────────────────
71
+ STATE_DIR="${HOME}/.shipwright/autonomous"
72
+ STATE_FILE="${STATE_DIR}/state.json"
73
+ HISTORY_FILE="${STATE_DIR}/history.jsonl"
74
+ CONFIG_FILE="${STATE_DIR}/config.json"
75
+ CYCLE_COUNTER="${STATE_DIR}/cycle-counter.txt"
76
+
77
+ # Ensure directories exist
78
+ ensure_state_dir() {
79
+ mkdir -p "$STATE_DIR"
80
+ if [[ ! -f "$CONFIG_FILE" ]]; then
81
+ cat > "$CONFIG_FILE" << 'EOF'
82
+ {
83
+ "cycle_interval_minutes": 60,
84
+ "max_issues_per_cycle": 5,
85
+ "max_concurrent_pipelines": 2,
86
+ "enable_human_approval": false,
87
+ "approval_timeout_minutes": 30,
88
+ "rollback_on_failures": true,
89
+ "max_consecutive_failures": 3,
90
+ "learning_enabled": true,
91
+ "self_improvement_enabled": true,
92
+ "shipwright_self_improvement_threshold": 3
93
+ }
94
+ EOF
95
+ info "Created default config at ${CONFIG_FILE}"
96
+ fi
97
+ }
98
+
99
+ # Get config value
100
+ get_config() {
101
+ local key="$1"
102
+ local default="${2:-}"
103
+ jq -r ".${key} // \"${default}\"" "$CONFIG_FILE" 2>/dev/null || echo "$default"
104
+ }
105
+
106
+ # Set config value
107
+ set_config() {
108
+ local key="$1"
109
+ local value="$2"
110
+ local tmp_file
111
+ tmp_file=$(mktemp "${TMPDIR:-/tmp}/sw-autonomous-config.XXXXXX")
112
+ # Try to parse value as JSON first, otherwise treat as string
113
+ local json_value
114
+ if echo "$value" | jq . >/dev/null 2>&1; then
115
+ json_value="$value"
116
+ else
117
+ json_value="\"${value//\"/\\\"}\""
118
+ fi
119
+ jq ".\"$key\" = $json_value" "$CONFIG_FILE" > "$tmp_file" 2>/dev/null && \
120
+ mv "$tmp_file" "$CONFIG_FILE" || rm -f "$tmp_file"
121
+ }
122
+
123
+ # ─── State Management ───────────────────────────────────────────────────────
124
+
125
+ init_state() {
126
+ local tmp_file
127
+ tmp_file=$(mktemp "${TMPDIR:-/tmp}/sw-autonomous-state.XXXXXX")
128
+ jq -n \
129
+ --arg started "$(now_iso)" \
130
+ '{
131
+ status: "idle",
132
+ started: $started,
133
+ last_cycle: null,
134
+ cycles_completed: 0,
135
+ issues_created: 0,
136
+ issues_completed: 0,
137
+ pipelines_succeeded: 0,
138
+ pipelines_failed: 0,
139
+ consecutive_failures: 0,
140
+ paused_at: null
141
+ }' > "$tmp_file" && mv "$tmp_file" "$STATE_FILE" || rm -f "$tmp_file"
142
+ }
143
+
144
+ read_state() {
145
+ ensure_state_dir
146
+ if [[ ! -f "$STATE_FILE" ]]; then
147
+ init_state
148
+ fi
149
+ cat "$STATE_FILE"
150
+ }
151
+
152
+ update_state() {
153
+ local key="$1"
154
+ local value="$2"
155
+ local tmp_file
156
+ tmp_file=$(mktemp "${TMPDIR:-/tmp}/sw-autonomous-state.XXXXXX")
157
+ # Try to parse value as JSON first, otherwise treat as string
158
+ local json_value
159
+ if echo "$value" | jq . >/dev/null 2>&1; then
160
+ json_value="$value"
161
+ else
162
+ json_value="\"${value//\"/\\\"}\""
163
+ fi
164
+ read_state | jq ".\"$key\" = $json_value" > "$tmp_file" && \
165
+ mv "$tmp_file" "$STATE_FILE" || rm -f "$tmp_file"
166
+ }
167
+
168
+ increment_counter() {
169
+ local key="$1"
170
+ local tmp_file
171
+ tmp_file=$(mktemp "${TMPDIR:-/tmp}/sw-autonomous-state.XXXXXX")
172
+ read_state | jq ".\"$key\" += 1" > "$tmp_file" && \
173
+ mv "$tmp_file" "$STATE_FILE" || rm -f "$tmp_file"
174
+ }
175
+
176
+ record_cycle() {
177
+ local issues_found="$1"
178
+ local issues_created="$2"
179
+ local status="$3"
180
+ local tmp_file
181
+ tmp_file=$(mktemp "${TMPDIR:-/tmp}/sw-autonomous-cycle.XXXXXX")
182
+ jq -n \
183
+ --arg ts "$(now_iso)" \
184
+ --argjson found "$issues_found" \
185
+ --argjson created "$issues_created" \
186
+ --arg status "$status" \
187
+ '{ts: $ts, found: $found, created: $created, status: $status}' \
188
+ >> "$HISTORY_FILE"
189
+ }
190
+
191
+ # ─── Analysis Cycle ────────────────────────────────────────────────────────
192
+
193
+ run_analysis_cycle() {
194
+ info "Starting analysis cycle..."
195
+
196
+ ensure_state_dir
197
+ update_state "status" "analyzing"
198
+
199
+ local findings
200
+ findings=$(mktemp "${TMPDIR:-/tmp}/sw-autonomous-findings.XXXXXX")
201
+
202
+ # Use Claude to analyze the codebase
203
+ if command -v claude &>/dev/null; then
204
+ info "Running codebase analysis with Claude..."
205
+
206
+ claude code << 'ANALYSIS_PROMPT'
207
+ You are Shipwright's autonomous PM. Analyze this repository for:
208
+ 1. **Bugs & Issues**: Missing error handling, potential crashes, edge cases
209
+ 2. **Performance**: Bottlenecks, n+1 queries, memory leaks, unnecessary work
210
+ 3. **Missing Tests**: Uncovered code paths, critical scenarios
211
+ 4. **Stale Documentation**: Outdated guides, missing API docs
212
+ 5. **Security**: Input validation, injection risks, credential leaks
213
+ 6. **Code Quality**: Dead code, refactoring opportunities, tech debt
214
+ 7. **Self-Improvement**: How Shipwright itself could be enhanced
215
+
216
+ For each finding, suggest:
217
+ - Priority: critical/high/medium/low
218
+ - Effort estimate: S/M/L (small/medium/large)
219
+ - Labels: e.g. "bug", "performance", "test", "docs", "security", "refactor", "self-improvement"
220
+
221
+ Output as JSON array of findings with fields:
222
+ {
223
+ "title": "...",
224
+ "description": "...",
225
+ "priority": "high",
226
+ "effort": "M",
227
+ "labels": ["..."],
228
+ "category": "bug|performance|test|docs|security|refactor|self-improvement"
229
+ }
230
+ ANALYSIS_PROMPT
231
+
232
+ else
233
+ warn "Claude CLI not available, using static heuristics..."
234
+
235
+ # Static heuristics for analysis
236
+ {
237
+ local has_tests=$(find . -type f -name "*test*" -o -name "*spec*" | wc -l || echo "0")
238
+ local shell_scripts=$(find scripts -type f -name "*.sh" | wc -l || echo "0")
239
+
240
+ jq -n \
241
+ --argjson test_count "$has_tests" \
242
+ --argjson script_count "$shell_scripts" \
243
+ '[
244
+ {
245
+ title: "Add comprehensive test coverage for critical paths",
246
+ description: "Several scripts lack unit test coverage",
247
+ priority: "high",
248
+ effort: "L",
249
+ labels: ["test", "quality"],
250
+ category: "test"
251
+ },
252
+ {
253
+ title: "Simplify error handling in daemon.sh",
254
+ description: "Daemon error handling could be more robust",
255
+ priority: "medium",
256
+ effort: "M",
257
+ labels: ["refactor", "self-improvement"],
258
+ category: "self-improvement"
259
+ }
260
+ ]'
261
+ } > "$findings"
262
+ fi
263
+
264
+ cat "$findings"
265
+ }
266
+
267
+ # ─── Issue Creation ────────────────────────────────────────────────────────
268
+
269
+ create_issue_from_finding() {
270
+ local title="$1"
271
+ local description="$2"
272
+ local priority="$3"
273
+ local effort="$4"
274
+ local labels="$5"
275
+
276
+ if [[ "$NO_GITHUB" == "true" ]]; then
277
+ warn "GitHub disabled, skipping issue creation for: $title"
278
+ return 1
279
+ fi
280
+
281
+ # Check if issue already exists
282
+ local existing
283
+ existing=$(gh issue list --search "$title" --json number -q 'length' 2>/dev/null || echo "0")
284
+ if [[ "${existing:-0}" -gt 0 ]]; then
285
+ warn "Issue already exists: $title"
286
+ return 1
287
+ fi
288
+
289
+ # Add shipwright label to auto-feed daemon
290
+ local all_labels="shipwright,$labels"
291
+
292
+ # Create GitHub issue
293
+ gh issue create \
294
+ --title "$title" \
295
+ --body "$description
296
+
297
+ ---
298
+ **Metadata:**
299
+ - Priority: \`${priority}\`
300
+ - Effort: \`${effort}\`
301
+ - Created: \`$(now_iso)\`
302
+ - By: Autonomous loop (sw-autonomous.sh)
303
+ " \
304
+ --label "$all_labels" 2>/dev/null && \
305
+ success "Created issue: $title" && \
306
+ return 0 || \
307
+ warn "Failed to create issue: $title" && \
308
+ return 1
309
+ }
310
+
311
+ # ─── Issue Processing from Analysis ────────────────────────────────────────
312
+
313
+ process_findings() {
314
+ local findings_file="$1"
315
+ local max_per_cycle
316
+ max_per_cycle=$(get_config "max_issues_per_cycle" "5")
317
+
318
+ info "Processing findings (max ${max_per_cycle} per cycle)..."
319
+
320
+ local created=0
321
+ local total=0
322
+
323
+ # Parse findings and create issues
324
+ while IFS= read -r finding; do
325
+ [[ -z "$finding" ]] && continue
326
+
327
+ total=$((total + 1))
328
+ [[ "$created" -ge "$max_per_cycle" ]] && break
329
+
330
+ local title description priority effort labels category
331
+ title=$(echo "$finding" | jq -r '.title // ""')
332
+ description=$(echo "$finding" | jq -r '.description // ""')
333
+ priority=$(echo "$finding" | jq -r '.priority // "medium"')
334
+ effort=$(echo "$finding" | jq -r '.effort // "M"')
335
+ labels=$(echo "$finding" | jq -r '.labels | join(",") // ""')
336
+ category=$(echo "$finding" | jq -r '.category // ""')
337
+
338
+ if [[ -z "$title" ]]; then
339
+ continue
340
+ fi
341
+
342
+ # Add category to labels if not present
343
+ if [[ "$labels" != *"$category"* ]]; then
344
+ labels="${category}${labels:+,$labels}"
345
+ fi
346
+
347
+ if create_issue_from_finding "$title" "$description" "$priority" "$effort" "$labels"; then
348
+ created=$((created + 1))
349
+ increment_counter "issues_created"
350
+ emit_event "autonomous.issue_created" "title=$title" "priority=$priority" "effort=$effort"
351
+ fi
352
+ done < <(jq -c '.[]' "$findings_file" 2>/dev/null)
353
+
354
+ info "Created $created of $total findings as issues"
355
+ echo "$created"
356
+ }
357
+
358
+ # ─── Learning & Feedback ───────────────────────────────────────────────────
359
+
360
+ analyze_pipeline_result() {
361
+ local pipeline_state="${1:-}"
362
+
363
+ if [[ -z "$pipeline_state" || ! -f "$pipeline_state" ]]; then
364
+ warn "Pipeline state file not found: ${pipeline_state:-<empty>}"
365
+ return 1
366
+ fi
367
+
368
+ info "Analyzing pipeline result..."
369
+
370
+ local status=""
371
+ status=$(sed -n 's/^status: *//p' "$pipeline_state" | head -1)
372
+
373
+ local goal=""
374
+ goal=$(sed -n 's/^goal: *"*\([^"]*\)"*/\1/p' "$pipeline_state" | head -1)
375
+
376
+ # Capture lessons learned
377
+ if [[ "$status" == "complete" || "$status" == "success" ]]; then
378
+ success "Pipeline completed successfully: $goal"
379
+ increment_counter "pipelines_succeeded"
380
+ update_state "consecutive_failures" "0"
381
+ emit_event "autonomous.pipeline_success" "goal=$goal"
382
+ return 0
383
+ else
384
+ warn "Pipeline failed: $goal (status: $status)"
385
+ increment_counter "pipelines_failed"
386
+ local failures
387
+ failures=$(read_state | jq '.consecutive_failures // 0')
388
+ update_state "consecutive_failures" "$((failures + 1))"
389
+ emit_event "autonomous.pipeline_failure" "goal=$goal" "status=$status"
390
+ return 1
391
+ fi
392
+ }
393
+
394
+ # ─── Status & Metrics ───────────────────────────────────────────────────────
395
+
396
+ show_status() {
397
+ ensure_state_dir
398
+
399
+ local state
400
+ state=$(read_state)
401
+
402
+ local status cycles issues_created issues_completed succeeded failed
403
+ status=$(echo "$state" | jq -r '.status')
404
+ cycles=$(echo "$state" | jq -r '.cycles_completed')
405
+ issues_created=$(echo "$state" | jq -r '.issues_created')
406
+ issues_completed=$(echo "$state" | jq -r '.issues_completed')
407
+ succeeded=$(echo "$state" | jq -r '.pipelines_succeeded')
408
+ failed=$(echo "$state" | jq -r '.pipelines_failed')
409
+
410
+ local cycle_interval
411
+ cycle_interval=$(get_config "cycle_interval_minutes" "60")
412
+
413
+ echo ""
414
+ echo -e "${CYAN}${BOLD}╔════════════════════════════════════════════════════╗${RESET}"
415
+ echo -e "${CYAN}${BOLD}║ Autonomous Loop Status${RESET}"
416
+ echo -e "${CYAN}${BOLD}╚════════════════════════════════════════════════════╝${RESET}"
417
+ echo ""
418
+ echo -e " ${BOLD}Status${RESET} ${CYAN}${status}${RESET}"
419
+ echo -e " ${BOLD}Cycles${RESET} ${GREEN}${cycles}${RESET}"
420
+ echo -e " ${BOLD}Issues Created${RESET} ${CYAN}${issues_created}${RESET}"
421
+ echo -e " ${BOLD}Issues Completed${RESET} ${GREEN}${issues_completed}${RESET}"
422
+ echo -e " ${BOLD}Pipelines Succeeded${RESET} ${GREEN}${succeeded}${RESET}"
423
+ echo -e " ${BOLD}Pipelines Failed${RESET} ${RED}${failed}${RESET}"
424
+ echo -e " ${BOLD}Cycle Interval${RESET} ${YELLOW}${cycle_interval}${RESET} minutes"
425
+ echo ""
426
+ }
427
+
428
+ show_history() {
429
+ ensure_state_dir
430
+
431
+ if [[ ! -f "$HISTORY_FILE" ]]; then
432
+ warn "No cycle history recorded yet"
433
+ return 0
434
+ fi
435
+
436
+ echo ""
437
+ echo -e "${CYAN}${BOLD}Recent Cycles${RESET}"
438
+ echo ""
439
+
440
+ tail -20 "$HISTORY_FILE" | while read -r line; do
441
+ local ts status created
442
+ ts=$(echo "$line" | jq -r '.ts')
443
+ status=$(echo "$line" | jq -r '.status')
444
+ created=$(echo "$line" | jq -r '.created')
445
+
446
+ local status_color="$GREEN"
447
+ [[ "$status" != "success" ]] && status_color="$RED"
448
+
449
+ echo -e " ${ts} Status: ${status_color}${status}${RESET} Created: ${CYAN}${created}${RESET}"
450
+ done
451
+ }
452
+
453
+ # ─── Loop Control ──────────────────────────────────────────────────────────
454
+
455
+ start_loop() {
456
+ ensure_state_dir
457
+ init_state
458
+ update_state "status" "running"
459
+
460
+ info "Starting autonomous loop..."
461
+ success "Loop is now running in background"
462
+ success "Run '${CYAN}sw autonomous status${RESET}' to check progress"
463
+
464
+ emit_event "autonomous.started"
465
+ }
466
+
467
+ stop_loop() {
468
+ ensure_state_dir
469
+ update_state "status" "stopped"
470
+ success "Autonomous loop stopped"
471
+ emit_event "autonomous.stopped"
472
+ }
473
+
474
+ pause_loop() {
475
+ ensure_state_dir
476
+ update_state "status" "paused"
477
+ update_state "paused_at" "$(now_iso)"
478
+ success "Autonomous loop paused"
479
+ emit_event "autonomous.paused"
480
+ }
481
+
482
+ resume_loop() {
483
+ ensure_state_dir
484
+ update_state "status" "running"
485
+ update_state "paused_at" "null"
486
+ success "Autonomous loop resumed"
487
+ emit_event "autonomous.resumed"
488
+ }
489
+
490
+ run_single_cycle() {
491
+ ensure_state_dir
492
+ update_state "status" "analyzing"
493
+
494
+ info "Running single analysis cycle..."
495
+
496
+ # Step 1: Analysis
497
+ local findings
498
+ findings=$(mktemp "${TMPDIR:-/tmp}/sw-autonomous-findings.XXXXXX")
499
+ run_analysis_cycle > "$findings" 2>&1 || {
500
+ warn "Analysis failed"
501
+ rm -f "$findings"
502
+ return 1
503
+ }
504
+
505
+ # Step 2: Create issues
506
+ local created
507
+ created=$(process_findings "$findings")
508
+
509
+ # Step 3: Record cycle
510
+ local total_findings
511
+ total_findings=$(jq -s 'length' "$findings" 2>/dev/null || echo "0")
512
+ record_cycle "$total_findings" "$created" "success"
513
+
514
+ increment_counter "cycles_completed"
515
+ update_state "status" "idle"
516
+ update_state "last_cycle" "$(now_iso)"
517
+
518
+ success "Cycle complete. Created $created issues"
519
+ rm -f "$findings"
520
+ }
521
+
522
+ show_config() {
523
+ ensure_state_dir
524
+ echo ""
525
+ echo -e "${CYAN}${BOLD}Autonomous Loop Configuration${RESET}"
526
+ echo ""
527
+ cat "$CONFIG_FILE" | jq '.' 2>/dev/null || cat "$CONFIG_FILE"
528
+ echo ""
529
+ }
530
+
531
+ set_cycle_config() {
532
+ local key="$1"
533
+ local value="$2"
534
+ ensure_state_dir
535
+
536
+ case "$key" in
537
+ interval|cycle-interval)
538
+ set_config "cycle_interval_minutes" "$value"
539
+ success "Cycle interval set to ${CYAN}${value}${RESET} minutes"
540
+ ;;
541
+ max-issues)
542
+ set_config "max_issues_per_cycle" "$value"
543
+ success "Max issues per cycle set to ${CYAN}${value}${RESET}"
544
+ ;;
545
+ max-pipelines|max-concurrent)
546
+ set_config "max_concurrent_pipelines" "$value"
547
+ success "Max concurrent pipelines set to ${CYAN}${value}${RESET}"
548
+ ;;
549
+ approval|human-approval)
550
+ local bool_val="true"
551
+ [[ "$value" == "false" || "$value" == "0" || "$value" == "no" ]] && bool_val="false"
552
+ set_config "enable_human_approval" "$bool_val"
553
+ success "Human approval set to ${CYAN}${bool_val}${RESET}"
554
+ ;;
555
+ rollback)
556
+ local bool_val="true"
557
+ [[ "$value" == "false" || "$value" == "0" || "$value" == "no" ]] && bool_val="false"
558
+ set_config "rollback_on_failures" "$bool_val"
559
+ success "Rollback on failures set to ${CYAN}${bool_val}${RESET}"
560
+ ;;
561
+ *)
562
+ error "Unknown config key: $key"
563
+ return 1
564
+ ;;
565
+ esac
566
+ }
567
+
568
+ # ─── Help ──────────────────────────────────────────────────────────────────
569
+
570
+ show_help() {
571
+ cat << 'EOF'
572
+
573
+ USAGE
574
+ sw autonomous <command> [options]
575
+
576
+ COMMANDS
577
+ start Begin autonomous loop (analyze → create → build → learn → repeat)
578
+ stop Stop the loop gracefully
579
+ pause Pause without losing state
580
+ resume Resume from pause
581
+ cycle Run one analysis cycle manually
582
+ status Show loop status, recent cycles, issue creation stats
583
+ config [show|set] Show or set configuration
584
+ history Show past cycles and their outcomes
585
+ help Show this help message
586
+
587
+ OPTIONS (for config)
588
+ set interval <minutes> Set cycle interval (default 60)
589
+ set max-issues <num> Set max issues per cycle (default 5)
590
+ set max-pipelines <num> Set max concurrent pipelines (default 2)
591
+ set approval <bool> Enable human approval mode (default false)
592
+ set rollback <bool> Rollback on failures (default true)
593
+
594
+ EXAMPLES
595
+ sw autonomous start # Start the loop
596
+ sw autonomous cycle # Run one cycle immediately
597
+ sw autonomous config set interval 30 # Change cycle to 30 minutes
598
+ sw autonomous status # Check progress
599
+ sw autonomous history # View past cycles
600
+
601
+ EOF
602
+ }
603
+
604
+ # ─── Main ──────────────────────────────────────────────────────────────────
605
+
606
+ main() {
607
+ local cmd="${1:-help}"
608
+ shift 2>/dev/null || true
609
+
610
+ case "$cmd" in
611
+ start)
612
+ start_loop
613
+ ;;
614
+ stop)
615
+ stop_loop
616
+ ;;
617
+ pause)
618
+ pause_loop
619
+ ;;
620
+ resume)
621
+ resume_loop
622
+ ;;
623
+ cycle)
624
+ run_single_cycle
625
+ ;;
626
+ status)
627
+ show_status
628
+ show_history
629
+ ;;
630
+ config)
631
+ local subcmd="${1:-show}"
632
+ shift 2>/dev/null || true
633
+ case "$subcmd" in
634
+ show)
635
+ show_config
636
+ ;;
637
+ set)
638
+ set_cycle_config "$@"
639
+ ;;
640
+ *)
641
+ error "Unknown config subcommand: $subcmd"
642
+ show_help
643
+ exit 1
644
+ ;;
645
+ esac
646
+ ;;
647
+ history)
648
+ show_history
649
+ ;;
650
+ help|--help|-h)
651
+ show_help
652
+ ;;
653
+ *)
654
+ error "Unknown command: $cmd"
655
+ show_help
656
+ exit 1
657
+ ;;
658
+ esac
659
+ }
660
+
661
+ # ─── Source Guard ───────────────────────────────────────────────────────────
662
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
663
+ main "$@"
664
+ fi