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.
- package/README.md +114 -36
- package/completions/_shipwright +212 -32
- package/completions/shipwright.bash +97 -25
- package/docs/strategy/01-market-research.md +619 -0
- package/docs/strategy/02-mission-and-brand.md +587 -0
- package/docs/strategy/03-gtm-and-roadmap.md +759 -0
- package/docs/strategy/QUICK-START.txt +289 -0
- package/docs/strategy/README.md +172 -0
- package/package.json +4 -2
- package/scripts/sw +208 -1
- package/scripts/sw-activity.sh +500 -0
- package/scripts/sw-adaptive.sh +925 -0
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +613 -0
- package/scripts/sw-autonomous.sh +664 -0
- package/scripts/sw-changelog.sh +704 -0
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +637 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +432 -130
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +540 -0
- package/scripts/sw-decompose.sh +539 -0
- package/scripts/sw-deps.sh +551 -0
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +412 -0
- package/scripts/sw-docs-agent.sh +539 -0
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +59 -1
- package/scripts/sw-dora.sh +615 -0
- package/scripts/sw-durable.sh +710 -0
- package/scripts/sw-e2e-orchestrator.sh +535 -0
- package/scripts/sw-eventbus.sh +393 -0
- package/scripts/sw-feedback.sh +471 -0
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +567 -0
- package/scripts/sw-fleet-viz.sh +404 -0
- package/scripts/sw-fleet.sh +8 -1
- package/scripts/sw-github-app.sh +596 -0
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +569 -0
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +559 -0
- package/scripts/sw-incident.sh +617 -0
- package/scripts/sw-init.sh +88 -1
- package/scripts/sw-instrument.sh +699 -0
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +363 -28
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +64 -3
- package/scripts/sw-memory.sh +1 -1
- package/scripts/sw-mission-control.sh +487 -0
- package/scripts/sw-model-router.sh +545 -0
- package/scripts/sw-otel.sh +596 -0
- package/scripts/sw-oversight.sh +689 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +687 -24
- package/scripts/sw-pm.sh +693 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +1 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +573 -0
- package/scripts/sw-regression.sh +642 -0
- package/scripts/sw-release-manager.sh +736 -0
- package/scripts/sw-release.sh +706 -0
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +520 -0
- package/scripts/sw-retro.sh +691 -0
- package/scripts/sw-scale.sh +444 -0
- package/scripts/sw-security-audit.sh +505 -0
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +658 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +583 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +515 -0
- package/scripts/sw-tmux-pipeline.sh +554 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +485 -0
- package/scripts/sw-tracker-github.sh +188 -0
- package/scripts/sw-tracker-jira.sh +172 -0
- package/scripts/sw-tracker-linear.sh +251 -0
- package/scripts/sw-tracker.sh +117 -2
- package/scripts/sw-triage.sh +603 -0
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +677 -0
- package/scripts/sw-webhook.sh +627 -0
- package/scripts/sw-widgets.sh +530 -0
- 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
|