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
package/scripts/sw-status.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# ║ ║
|
|
5
5
|
# ║ Shows running teams, agent windows, and task progress. ║
|
|
6
6
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
7
|
-
VERSION="
|
|
7
|
+
VERSION="2.0.0"
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
10
10
|
|
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ sw-strategic.sh — Strategic Intelligence Agent ║
|
|
4
|
+
# ║ Reads strategy, metrics, and codebase to create high-impact issues ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
# This file can be BOTH sourced (by sw-daemon.sh) and run standalone.
|
|
7
|
+
# When sourced, do NOT add set -euo pipefail — the parent handles that.
|
|
8
|
+
# When run directly, main() sets up the error handling.
|
|
9
|
+
|
|
10
|
+
VERSION="2.0.0"
|
|
11
|
+
|
|
12
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
13
|
+
CYAN='\033[38;2;0;212;255m'
|
|
14
|
+
PURPLE='\033[38;2;124;58;237m'
|
|
15
|
+
BLUE='\033[38;2;0;102;255m'
|
|
16
|
+
GREEN='\033[38;2;74;222;128m'
|
|
17
|
+
YELLOW='\033[38;2;250;204;21m'
|
|
18
|
+
RED='\033[38;2;248;113;113m'
|
|
19
|
+
DIM='\033[2m'
|
|
20
|
+
BOLD='\033[1m'
|
|
21
|
+
RESET='\033[0m'
|
|
22
|
+
|
|
23
|
+
# ─── Helpers (define fallbacks if not provided by parent) ─────────────────────
|
|
24
|
+
# When sourced by sw-daemon.sh, these are already defined. When run standalone
|
|
25
|
+
# or sourced by tests, we define them here.
|
|
26
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
27
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
28
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
29
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
30
|
+
[[ "$(type -t now_epoch 2>/dev/null)" == "function" ]] || now_epoch() { date +%s; }
|
|
31
|
+
[[ "$(type -t now_iso 2>/dev/null)" == "function" ]] || now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
32
|
+
|
|
33
|
+
# ─── Paths (set defaults if not provided by parent) ──────────────────────────
|
|
34
|
+
SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
|
|
35
|
+
REPO_DIR="${REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
36
|
+
EVENTS_FILE="${EVENTS_FILE:-${HOME}/.shipwright/events.jsonl}"
|
|
37
|
+
|
|
38
|
+
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
39
|
+
emit_event() {
|
|
40
|
+
local event_type="$1"
|
|
41
|
+
shift
|
|
42
|
+
local json_fields=""
|
|
43
|
+
for kv in "$@"; do
|
|
44
|
+
local key="${kv%%=*}"
|
|
45
|
+
local val="${kv#*=}"
|
|
46
|
+
if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
|
47
|
+
json_fields="${json_fields},\"${key}\":${val}"
|
|
48
|
+
else
|
|
49
|
+
local escaped_val
|
|
50
|
+
escaped_val=$(printf '%s' "$val" | jq -Rs '.' 2>/dev/null || printf '"%s"' "${val//\"/\\\"}")
|
|
51
|
+
json_fields="${json_fields},\"${key}\":${escaped_val}"
|
|
52
|
+
fi
|
|
53
|
+
done
|
|
54
|
+
mkdir -p "${HOME}/.shipwright"
|
|
55
|
+
echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
|
|
56
|
+
}
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# ─── Constants ────────────────────────────────────────────────────────────────
|
|
60
|
+
STRATEGIC_MAX_ISSUES=3
|
|
61
|
+
STRATEGIC_COOLDOWN_SECONDS=43200 # 12 hours
|
|
62
|
+
STRATEGIC_MODEL="claude-haiku-4-5-20251001"
|
|
63
|
+
STRATEGIC_MAX_TOKENS=2048
|
|
64
|
+
STRATEGIC_STRATEGY_LINES=200
|
|
65
|
+
|
|
66
|
+
# ─── Cooldown Check ──────────────────────────────────────────────────────────
|
|
67
|
+
strategic_check_cooldown() {
|
|
68
|
+
local events_file="${EVENTS_FILE:-${HOME}/.shipwright/events.jsonl}"
|
|
69
|
+
if [[ ! -f "$events_file" ]]; then
|
|
70
|
+
return 0 # No events file — no cooldown
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
local now_e
|
|
74
|
+
now_e=$(now_epoch)
|
|
75
|
+
local last_run
|
|
76
|
+
last_run=$(grep '"strategic.cycle_complete"' "$events_file" 2>/dev/null | tail -1 | jq -r '.ts_epoch // 0' 2>/dev/null || echo "0")
|
|
77
|
+
|
|
78
|
+
local elapsed=$(( now_e - last_run ))
|
|
79
|
+
if [[ "$elapsed" -lt "$STRATEGIC_COOLDOWN_SECONDS" ]]; then
|
|
80
|
+
local remaining=$(( (STRATEGIC_COOLDOWN_SECONDS - elapsed) / 60 ))
|
|
81
|
+
info "Strategic cooldown active — ${remaining} minutes remaining"
|
|
82
|
+
return 1
|
|
83
|
+
fi
|
|
84
|
+
return 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# ─── Gather Context ──────────────────────────────────────────────────────────
|
|
88
|
+
strategic_gather_context() {
|
|
89
|
+
local repo_dir="${REPO_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
90
|
+
local script_dir="${SCRIPT_DIR:-${repo_dir}/scripts}"
|
|
91
|
+
local events_file="${EVENTS_FILE:-${HOME}/.shipwright/events.jsonl}"
|
|
92
|
+
|
|
93
|
+
# 1. Read STRATEGY.md (truncated)
|
|
94
|
+
local strategy_content=""
|
|
95
|
+
if [[ -f "${repo_dir}/STRATEGY.md" ]]; then
|
|
96
|
+
strategy_content=$(head -n "$STRATEGIC_STRATEGY_LINES" "${repo_dir}/STRATEGY.md")
|
|
97
|
+
else
|
|
98
|
+
strategy_content="(No STRATEGY.md found)"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# 2. Codebase stats
|
|
102
|
+
local total_scripts=0
|
|
103
|
+
local untested_scripts=""
|
|
104
|
+
local untested_count=0
|
|
105
|
+
local total_tests=0
|
|
106
|
+
|
|
107
|
+
for script in "$script_dir"/sw-*.sh; do
|
|
108
|
+
[[ ! -f "$script" ]] && continue
|
|
109
|
+
local base
|
|
110
|
+
base=$(basename "$script" .sh)
|
|
111
|
+
[[ "$base" == *-test ]] && continue
|
|
112
|
+
[[ "$base" == sw-tracker-linear ]] && continue
|
|
113
|
+
[[ "$base" == sw-tracker-jira ]] && continue
|
|
114
|
+
[[ "$base" == sw-patrol-meta ]] && continue
|
|
115
|
+
[[ "$base" == sw-strategic ]] && continue
|
|
116
|
+
total_scripts=$((total_scripts + 1))
|
|
117
|
+
|
|
118
|
+
local test_file="$script_dir/${base}-test.sh"
|
|
119
|
+
if [[ -f "$test_file" ]]; then
|
|
120
|
+
total_tests=$((total_tests + 1))
|
|
121
|
+
else
|
|
122
|
+
untested_count=$((untested_count + 1))
|
|
123
|
+
untested_scripts="${untested_scripts} - ${base}.sh\n"
|
|
124
|
+
fi
|
|
125
|
+
done
|
|
126
|
+
|
|
127
|
+
# 3. Pipeline performance (last 7 days)
|
|
128
|
+
local completed=0
|
|
129
|
+
local failed=0
|
|
130
|
+
local success_rate="N/A"
|
|
131
|
+
local common_failures=""
|
|
132
|
+
|
|
133
|
+
if [[ -f "$events_file" ]]; then
|
|
134
|
+
local now_e
|
|
135
|
+
now_e=$(now_epoch)
|
|
136
|
+
local seven_days_ago=$(( now_e - 604800 ))
|
|
137
|
+
|
|
138
|
+
completed=$(jq -s "[.[] | select(.type == \"pipeline.completed\" and .result == \"success\" and (.ts_epoch // 0) >= $seven_days_ago)] | length" "$events_file" 2>/dev/null || echo "0")
|
|
139
|
+
failed=$(jq -s "[.[] | select(.type == \"pipeline.completed\" and .result != \"success\" and (.ts_epoch // 0) >= $seven_days_ago)] | length" "$events_file" 2>/dev/null || echo "0")
|
|
140
|
+
|
|
141
|
+
local total_pipelines=$(( completed + failed ))
|
|
142
|
+
if [[ "$total_pipelines" -gt 0 ]]; then
|
|
143
|
+
success_rate=$(( completed * 100 / total_pipelines ))
|
|
144
|
+
success_rate="${success_rate}%"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
common_failures=$(jq -s "
|
|
148
|
+
[.[] | select(.type == \"pipeline.completed\" and .result != \"success\" and (.ts_epoch // 0) >= $seven_days_ago)]
|
|
149
|
+
| group_by(.failed_stage // \"unknown\")
|
|
150
|
+
| map({stage: .[0].failed_stage // \"unknown\", count: length})
|
|
151
|
+
| sort_by(-.count)
|
|
152
|
+
| .[0:5]
|
|
153
|
+
| map(\"\(.stage) (\(.count)x)\")
|
|
154
|
+
| join(\", \")
|
|
155
|
+
" "$events_file" 2>/dev/null || echo "none")
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# 4. Open issues
|
|
159
|
+
local open_issues=""
|
|
160
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]]; then
|
|
161
|
+
open_issues=$(gh issue list --state open --json number,title,labels --jq '.[] | "#\(.number): \(.title) [\(.labels | map(.name) | join(","))]"' 2>/dev/null | head -50 || echo "(could not fetch issues)")
|
|
162
|
+
else
|
|
163
|
+
open_issues="(GitHub access disabled)"
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# Build the context output
|
|
167
|
+
printf '%s\n' "STRATEGY_CONTENT<<EOF"
|
|
168
|
+
printf '%s\n' "$strategy_content"
|
|
169
|
+
printf '%s\n' "EOF"
|
|
170
|
+
printf '%s\n' "TOTAL_SCRIPTS=${total_scripts}"
|
|
171
|
+
printf '%s\n' "TOTAL_TESTS=${total_tests}"
|
|
172
|
+
printf '%s\n' "UNTESTED_COUNT=${untested_count}"
|
|
173
|
+
printf '%s\n' "UNTESTED_SCRIPTS<<EOF"
|
|
174
|
+
printf '%b' "$untested_scripts"
|
|
175
|
+
printf '%s\n' "EOF"
|
|
176
|
+
printf '%s\n' "PIPELINES_COMPLETED=${completed}"
|
|
177
|
+
printf '%s\n' "PIPELINES_FAILED=${failed}"
|
|
178
|
+
printf '%s\n' "SUCCESS_RATE=${success_rate}"
|
|
179
|
+
printf '%s\n' "COMMON_FAILURES=${common_failures}"
|
|
180
|
+
printf '%s\n' "OPEN_ISSUES<<EOF"
|
|
181
|
+
printf '%s\n' "$open_issues"
|
|
182
|
+
printf '%s\n' "EOF"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# ─── Build Prompt ─────────────────────────────────────────────────────────────
|
|
186
|
+
strategic_build_prompt() {
|
|
187
|
+
local repo_dir="${REPO_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
188
|
+
local script_dir="${SCRIPT_DIR:-${repo_dir}/scripts}"
|
|
189
|
+
local events_file="${EVENTS_FILE:-${HOME}/.shipwright/events.jsonl}"
|
|
190
|
+
|
|
191
|
+
# Read STRATEGY.md
|
|
192
|
+
local strategy_content=""
|
|
193
|
+
if [[ -f "${repo_dir}/STRATEGY.md" ]]; then
|
|
194
|
+
strategy_content=$(head -n "$STRATEGIC_STRATEGY_LINES" "${repo_dir}/STRATEGY.md")
|
|
195
|
+
else
|
|
196
|
+
strategy_content="(No STRATEGY.md found)"
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
# Codebase stats
|
|
200
|
+
local total_scripts=0
|
|
201
|
+
local untested_list=""
|
|
202
|
+
local untested_count=0
|
|
203
|
+
local total_tests=0
|
|
204
|
+
|
|
205
|
+
for script in "$script_dir"/sw-*.sh; do
|
|
206
|
+
[[ ! -f "$script" ]] && continue
|
|
207
|
+
local base
|
|
208
|
+
base=$(basename "$script" .sh)
|
|
209
|
+
[[ "$base" == *-test ]] && continue
|
|
210
|
+
[[ "$base" == sw-tracker-linear ]] && continue
|
|
211
|
+
[[ "$base" == sw-tracker-jira ]] && continue
|
|
212
|
+
[[ "$base" == sw-patrol-meta ]] && continue
|
|
213
|
+
[[ "$base" == sw-strategic ]] && continue
|
|
214
|
+
total_scripts=$((total_scripts + 1))
|
|
215
|
+
|
|
216
|
+
if [[ -f "$script_dir/${base}-test.sh" ]]; then
|
|
217
|
+
total_tests=$((total_tests + 1))
|
|
218
|
+
else
|
|
219
|
+
untested_count=$((untested_count + 1))
|
|
220
|
+
untested_list="${untested_list}\n - ${base}.sh"
|
|
221
|
+
fi
|
|
222
|
+
done
|
|
223
|
+
|
|
224
|
+
# Pipeline performance (last 7 days)
|
|
225
|
+
local completed=0 failed=0 success_rate="N/A" common_failures="none"
|
|
226
|
+
if [[ -f "$events_file" ]]; then
|
|
227
|
+
local now_e
|
|
228
|
+
now_e=$(now_epoch)
|
|
229
|
+
local seven_days_ago=$(( now_e - 604800 ))
|
|
230
|
+
|
|
231
|
+
completed=$(jq -s "[.[] | select(.type == \"pipeline.completed\" and .result == \"success\" and (.ts_epoch // 0) >= $seven_days_ago)] | length" "$events_file" 2>/dev/null || echo "0")
|
|
232
|
+
failed=$(jq -s "[.[] | select(.type == \"pipeline.completed\" and .result != \"success\" and (.ts_epoch // 0) >= $seven_days_ago)] | length" "$events_file" 2>/dev/null || echo "0")
|
|
233
|
+
|
|
234
|
+
local total_pipelines=$(( completed + failed ))
|
|
235
|
+
if [[ "$total_pipelines" -gt 0 ]]; then
|
|
236
|
+
success_rate="$(( completed * 100 / total_pipelines ))%"
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
common_failures=$(jq -s "
|
|
240
|
+
[.[] | select(.type == \"pipeline.completed\" and .result != \"success\" and (.ts_epoch // 0) >= $seven_days_ago)]
|
|
241
|
+
| group_by(.failed_stage // \"unknown\")
|
|
242
|
+
| map({stage: .[0].failed_stage // \"unknown\", count: length})
|
|
243
|
+
| sort_by(-.count)
|
|
244
|
+
| .[0:5]
|
|
245
|
+
| map(\"\(.stage) (\(.count)x)\")
|
|
246
|
+
| join(\", \")
|
|
247
|
+
" "$events_file" 2>/dev/null || echo "none")
|
|
248
|
+
# Empty string → "none"
|
|
249
|
+
common_failures="${common_failures:-none}"
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
# Open issues
|
|
253
|
+
local open_issues=""
|
|
254
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]]; then
|
|
255
|
+
open_issues=$(gh issue list --state open --json number,title --jq '.[] | "#\(.number): \(.title)"' 2>/dev/null | head -50 || echo "(could not fetch)")
|
|
256
|
+
else
|
|
257
|
+
open_issues="(GitHub access disabled)"
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
# Compose the prompt
|
|
261
|
+
cat <<PROMPT_EOF
|
|
262
|
+
You are the Strategic PM for Shipwright — an autonomous software delivery system. Your job is to analyze the current state and recommend 1-3 high-impact improvements to build next.
|
|
263
|
+
|
|
264
|
+
## Strategy (from STRATEGY.md)
|
|
265
|
+
${strategy_content}
|
|
266
|
+
|
|
267
|
+
## Current Codebase
|
|
268
|
+
- Total scripts: ${total_scripts}
|
|
269
|
+
- Scripts with tests: ${total_tests}
|
|
270
|
+
- Scripts without tests (${untested_count}):$(echo -e "$untested_list")
|
|
271
|
+
|
|
272
|
+
## Recent Pipeline Performance (last 7 days)
|
|
273
|
+
- Pipelines completed successfully: ${completed}
|
|
274
|
+
- Pipelines failed: ${failed}
|
|
275
|
+
- Success rate: ${success_rate}
|
|
276
|
+
- Common failure stages: ${common_failures}
|
|
277
|
+
|
|
278
|
+
## Open Issues (already in progress — do NOT duplicate these)
|
|
279
|
+
${open_issues}
|
|
280
|
+
|
|
281
|
+
## Your Task
|
|
282
|
+
Based on the strategy priorities and current data, recommend 1-3 concrete improvements to build next. Each should be a single, well-scoped task completable by one autonomous pipeline run.
|
|
283
|
+
|
|
284
|
+
For each recommendation, provide EXACTLY this format (no extra fields, no deviations):
|
|
285
|
+
|
|
286
|
+
ISSUE_TITLE: <concise, actionable title>
|
|
287
|
+
PRIORITY: <P0|P1|P2|P3|P4|P5>
|
|
288
|
+
COMPLEXITY: <fast|standard|full>
|
|
289
|
+
STRATEGY_AREA: <which priority area from strategy, e.g. "P0: Reliability">
|
|
290
|
+
DESCRIPTION: <2-3 sentences describing what to build and why it matters>
|
|
291
|
+
ACCEPTANCE: <bullet list of acceptance criteria, one per line starting with "- ">
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
Rules:
|
|
295
|
+
- Do NOT duplicate any open issue listed above
|
|
296
|
+
- Prioritize based on STRATEGY.md priorities (P0 > P1 > P2 > ...)
|
|
297
|
+
- Focus on concrete, actionable improvements (not vague goals)
|
|
298
|
+
- Each issue should be completable by one autonomous pipeline run
|
|
299
|
+
- Prefer reliability and DX improvements over new features
|
|
300
|
+
- Maximum 3 issues
|
|
301
|
+
PROMPT_EOF
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
# ─── Call Anthropic API ───────────────────────────────────────────────────────
|
|
305
|
+
strategic_call_api() {
|
|
306
|
+
local prompt="$1"
|
|
307
|
+
|
|
308
|
+
if [[ -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then
|
|
309
|
+
error "CLAUDE_CODE_OAUTH_TOKEN not set — cannot run strategic analysis"
|
|
310
|
+
return 1
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
if ! command -v claude &>/dev/null; then
|
|
314
|
+
error "Claude Code CLI not found — install with: npm install -g @anthropic-ai/claude-code"
|
|
315
|
+
return 1
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
local tmp_prompt
|
|
319
|
+
tmp_prompt=$(mktemp)
|
|
320
|
+
printf '%s' "$prompt" > "$tmp_prompt"
|
|
321
|
+
|
|
322
|
+
local response_text
|
|
323
|
+
response_text=$(claude -p "$(cat "$tmp_prompt")" --max-turns 1 --model "$STRATEGIC_MODEL" 2>/dev/null || echo "")
|
|
324
|
+
rm -f "$tmp_prompt"
|
|
325
|
+
|
|
326
|
+
if [[ -z "$response_text" ]]; then
|
|
327
|
+
error "Claude returned empty response"
|
|
328
|
+
return 1
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
printf '%s' "$response_text"
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# ─── Parse Response & Create Issues ──────────────────────────────────────────
|
|
335
|
+
strategic_parse_and_create() {
|
|
336
|
+
local response="$1"
|
|
337
|
+
local created=0
|
|
338
|
+
local skipped=0
|
|
339
|
+
|
|
340
|
+
# Split response into issue blocks by "---" delimiter
|
|
341
|
+
local current_title="" current_priority="" current_complexity=""
|
|
342
|
+
local current_strategy="" current_description="" current_acceptance=""
|
|
343
|
+
local in_acceptance=false
|
|
344
|
+
|
|
345
|
+
while IFS= read -r line; do
|
|
346
|
+
# Strip carriage returns
|
|
347
|
+
line="${line//$'\r'/}"
|
|
348
|
+
|
|
349
|
+
if [[ "$line" == "---" ]] || [[ "$line" == "---"* && ${#line} -le 5 ]]; then
|
|
350
|
+
# End of block — create issue if we have a title
|
|
351
|
+
if [[ -n "$current_title" ]]; then
|
|
352
|
+
strategic_create_issue \
|
|
353
|
+
"$current_title" "$current_priority" "$current_complexity" \
|
|
354
|
+
"$current_strategy" "$current_description" "$current_acceptance"
|
|
355
|
+
local rc=$?
|
|
356
|
+
if [[ $rc -eq 0 ]]; then
|
|
357
|
+
created=$((created + 1))
|
|
358
|
+
else
|
|
359
|
+
skipped=$((skipped + 1))
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
if [[ "$created" -ge "$STRATEGIC_MAX_ISSUES" ]]; then
|
|
363
|
+
info "Reached max issues per cycle (${STRATEGIC_MAX_ISSUES})"
|
|
364
|
+
break
|
|
365
|
+
fi
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
# Reset for next block
|
|
369
|
+
current_title="" current_priority="" current_complexity=""
|
|
370
|
+
current_strategy="" current_description="" current_acceptance=""
|
|
371
|
+
in_acceptance=false
|
|
372
|
+
continue
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
# Parse fields
|
|
376
|
+
if [[ "$line" == ISSUE_TITLE:* ]]; then
|
|
377
|
+
current_title="${line#ISSUE_TITLE: }"
|
|
378
|
+
current_title="${current_title#ISSUE_TITLE:}"
|
|
379
|
+
current_title=$(echo "$current_title" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
380
|
+
in_acceptance=false
|
|
381
|
+
elif [[ "$line" == PRIORITY:* ]]; then
|
|
382
|
+
current_priority="${line#PRIORITY: }"
|
|
383
|
+
current_priority="${current_priority#PRIORITY:}"
|
|
384
|
+
current_priority=$(echo "$current_priority" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
385
|
+
in_acceptance=false
|
|
386
|
+
elif [[ "$line" == COMPLEXITY:* ]]; then
|
|
387
|
+
current_complexity="${line#COMPLEXITY: }"
|
|
388
|
+
current_complexity="${current_complexity#COMPLEXITY:}"
|
|
389
|
+
current_complexity=$(echo "$current_complexity" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
390
|
+
in_acceptance=false
|
|
391
|
+
elif [[ "$line" == STRATEGY_AREA:* ]]; then
|
|
392
|
+
current_strategy="${line#STRATEGY_AREA: }"
|
|
393
|
+
current_strategy="${current_strategy#STRATEGY_AREA:}"
|
|
394
|
+
current_strategy=$(echo "$current_strategy" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
395
|
+
in_acceptance=false
|
|
396
|
+
elif [[ "$line" == DESCRIPTION:* ]]; then
|
|
397
|
+
current_description="${line#DESCRIPTION: }"
|
|
398
|
+
current_description="${current_description#DESCRIPTION:}"
|
|
399
|
+
current_description=$(echo "$current_description" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
400
|
+
in_acceptance=false
|
|
401
|
+
elif [[ "$line" == ACCEPTANCE:* ]]; then
|
|
402
|
+
current_acceptance="${line#ACCEPTANCE: }"
|
|
403
|
+
current_acceptance="${current_acceptance#ACCEPTANCE:}"
|
|
404
|
+
current_acceptance=$(echo "$current_acceptance" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
405
|
+
in_acceptance=true
|
|
406
|
+
elif [[ "$in_acceptance" == true && "$line" == "- "* ]]; then
|
|
407
|
+
# Continuation of acceptance criteria
|
|
408
|
+
if [[ -n "$current_acceptance" ]]; then
|
|
409
|
+
current_acceptance="${current_acceptance}\n${line}"
|
|
410
|
+
else
|
|
411
|
+
current_acceptance="$line"
|
|
412
|
+
fi
|
|
413
|
+
fi
|
|
414
|
+
done <<< "$response"
|
|
415
|
+
|
|
416
|
+
# Handle last block (if no trailing ---)
|
|
417
|
+
if [[ -n "$current_title" && "$created" -lt "$STRATEGIC_MAX_ISSUES" ]]; then
|
|
418
|
+
strategic_create_issue \
|
|
419
|
+
"$current_title" "$current_priority" "$current_complexity" \
|
|
420
|
+
"$current_strategy" "$current_description" "$current_acceptance"
|
|
421
|
+
local rc=$?
|
|
422
|
+
if [[ $rc -eq 0 ]]; then
|
|
423
|
+
created=$((created + 1))
|
|
424
|
+
else
|
|
425
|
+
skipped=$((skipped + 1))
|
|
426
|
+
fi
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
echo "${created}:${skipped}"
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
# ─── Create Single Issue ─────────────────────────────────────────────────────
|
|
433
|
+
strategic_create_issue() {
|
|
434
|
+
local title="$1"
|
|
435
|
+
local priority="${2:-P2}"
|
|
436
|
+
local complexity="${3:-standard}"
|
|
437
|
+
local strategy_area="${4:-}"
|
|
438
|
+
local description="${5:-}"
|
|
439
|
+
local acceptance="${6:-}"
|
|
440
|
+
|
|
441
|
+
if [[ -z "$title" ]]; then
|
|
442
|
+
return 1
|
|
443
|
+
fi
|
|
444
|
+
|
|
445
|
+
# Dry-run mode
|
|
446
|
+
if [[ "${NO_GITHUB:-false}" == "true" ]]; then
|
|
447
|
+
info " [dry-run] Would create: ${title}"
|
|
448
|
+
return 0
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
# Dedup: check if an open issue with this exact title already exists
|
|
452
|
+
local existing
|
|
453
|
+
existing=$(gh issue list --state open --search "$title" --json number,title --jq ".[].title" 2>/dev/null || echo "")
|
|
454
|
+
if echo "$existing" | grep -qF "$title" 2>/dev/null; then
|
|
455
|
+
info " Skipping duplicate: ${title}"
|
|
456
|
+
return 1
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
# Build issue body
|
|
460
|
+
local timestamp
|
|
461
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
462
|
+
|
|
463
|
+
local body
|
|
464
|
+
body=$(cat <<BODY_EOF
|
|
465
|
+
## Strategic Improvement
|
|
466
|
+
|
|
467
|
+
$(echo -e "$description")
|
|
468
|
+
|
|
469
|
+
### Acceptance Criteria
|
|
470
|
+
$(echo -e "$acceptance")
|
|
471
|
+
|
|
472
|
+
### Context
|
|
473
|
+
- **Priority**: ${priority}
|
|
474
|
+
- **Complexity**: ${complexity}
|
|
475
|
+
- **Generated by**: Strategic Intelligence Agent
|
|
476
|
+
- **Strategy alignment**: ${strategy_area}
|
|
477
|
+
|
|
478
|
+
<!-- STRATEGIC-CYCLE: ${timestamp} -->
|
|
479
|
+
BODY_EOF
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
local labels="auto-patrol,ready-to-build,strategic"
|
|
483
|
+
|
|
484
|
+
gh issue create \
|
|
485
|
+
--title "$title" \
|
|
486
|
+
--body "$body" \
|
|
487
|
+
--label "$labels" 2>/dev/null || {
|
|
488
|
+
warn " Failed to create issue: ${title}"
|
|
489
|
+
return 1
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
emit_event "strategic.issue_created" "title=$title" "priority=$priority" "complexity=$complexity"
|
|
493
|
+
success " Created issue: ${title}"
|
|
494
|
+
return 0
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
# ─── Main Strategic Run ──────────────────────────────────────────────────────
|
|
498
|
+
strategic_run() {
|
|
499
|
+
echo -e "\n${PURPLE}${BOLD}━━━ Strategic Intelligence Agent ━━━${RESET}"
|
|
500
|
+
echo -e "${DIM} Analyzing codebase, strategy, and metrics...${RESET}\n"
|
|
501
|
+
|
|
502
|
+
# Check cooldown
|
|
503
|
+
if ! strategic_check_cooldown; then
|
|
504
|
+
return 0
|
|
505
|
+
fi
|
|
506
|
+
|
|
507
|
+
# Check auth token
|
|
508
|
+
if [[ -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then
|
|
509
|
+
error "CLAUDE_CODE_OAUTH_TOKEN not set — strategic analysis requires Claude access"
|
|
510
|
+
return 1
|
|
511
|
+
fi
|
|
512
|
+
|
|
513
|
+
# Build prompt with all context
|
|
514
|
+
info "Gathering context..."
|
|
515
|
+
local prompt
|
|
516
|
+
prompt=$(strategic_build_prompt)
|
|
517
|
+
|
|
518
|
+
# Call Anthropic API
|
|
519
|
+
info "Calling ${STRATEGIC_MODEL} for strategic analysis..."
|
|
520
|
+
local response
|
|
521
|
+
response=$(strategic_call_api "$prompt") || {
|
|
522
|
+
error "Strategic analysis API call failed"
|
|
523
|
+
emit_event "strategic.cycle_failed" "reason=api_error"
|
|
524
|
+
return 1
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
# Parse and create issues
|
|
528
|
+
info "Processing recommendations..."
|
|
529
|
+
local result
|
|
530
|
+
result=$(strategic_parse_and_create "$response")
|
|
531
|
+
|
|
532
|
+
local created="${result%%:*}"
|
|
533
|
+
local skipped="${result##*:}"
|
|
534
|
+
|
|
535
|
+
# Summary
|
|
536
|
+
echo ""
|
|
537
|
+
echo -e "${PURPLE}${BOLD}━━━ Strategic Summary ━━━${RESET}"
|
|
538
|
+
echo -e " Issues created: ${created}"
|
|
539
|
+
echo -e " Issues skipped: ${skipped} (duplicates)"
|
|
540
|
+
echo ""
|
|
541
|
+
|
|
542
|
+
emit_event "strategic.cycle_complete" "issues_created=$created" "issues_skipped=$skipped"
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# ─── Status Command ──────────────────────────────────────────────────────────
|
|
546
|
+
strategic_status() {
|
|
547
|
+
echo -e "\n${PURPLE}${BOLD}━━━ Strategic Agent Status ━━━${RESET}\n"
|
|
548
|
+
|
|
549
|
+
local events_file="${EVENTS_FILE:-${HOME}/.shipwright/events.jsonl}"
|
|
550
|
+
|
|
551
|
+
if [[ ! -f "$events_file" ]]; then
|
|
552
|
+
info "No events data available"
|
|
553
|
+
return 0
|
|
554
|
+
fi
|
|
555
|
+
|
|
556
|
+
# Last run
|
|
557
|
+
local last_run_line
|
|
558
|
+
last_run_line=$(grep '"strategic.cycle_complete"' "$events_file" 2>/dev/null | tail -1 || echo "")
|
|
559
|
+
|
|
560
|
+
if [[ -z "$last_run_line" ]]; then
|
|
561
|
+
info "No strategic cycles recorded yet"
|
|
562
|
+
return 0
|
|
563
|
+
fi
|
|
564
|
+
|
|
565
|
+
local last_ts last_created last_skipped
|
|
566
|
+
last_ts=$(echo "$last_run_line" | jq -r '.ts // "unknown"' 2>/dev/null || echo "unknown")
|
|
567
|
+
last_created=$(echo "$last_run_line" | jq -r '.issues_created // 0' 2>/dev/null || echo "0")
|
|
568
|
+
last_skipped=$(echo "$last_run_line" | jq -r '.issues_skipped // 0' 2>/dev/null || echo "0")
|
|
569
|
+
|
|
570
|
+
echo -e " Last run: ${last_ts}"
|
|
571
|
+
echo -e " Issues created: ${last_created}"
|
|
572
|
+
echo -e " Issues skipped: ${last_skipped}"
|
|
573
|
+
|
|
574
|
+
# Cooldown status
|
|
575
|
+
local last_epoch
|
|
576
|
+
last_epoch=$(echo "$last_run_line" | jq -r '.ts_epoch // 0' 2>/dev/null || echo "0")
|
|
577
|
+
local now_e
|
|
578
|
+
now_e=$(now_epoch)
|
|
579
|
+
local elapsed=$(( now_e - last_epoch ))
|
|
580
|
+
|
|
581
|
+
if [[ "$elapsed" -lt "$STRATEGIC_COOLDOWN_SECONDS" ]]; then
|
|
582
|
+
local remaining_min=$(( (STRATEGIC_COOLDOWN_SECONDS - elapsed) / 60 ))
|
|
583
|
+
echo -e " Cooldown: ${YELLOW}${remaining_min} min remaining${RESET}"
|
|
584
|
+
else
|
|
585
|
+
echo -e " Cooldown: ${GREEN}Ready${RESET}"
|
|
586
|
+
fi
|
|
587
|
+
|
|
588
|
+
# Total issues created
|
|
589
|
+
local total_created
|
|
590
|
+
total_created=$(grep '"strategic.issue_created"' "$events_file" 2>/dev/null | wc -l | tr -d ' ' || echo "0")
|
|
591
|
+
echo -e " Total created: ${total_created} issues (all time)"
|
|
592
|
+
|
|
593
|
+
# Total cycles
|
|
594
|
+
local total_cycles
|
|
595
|
+
total_cycles=$(grep '"strategic.cycle_complete"' "$events_file" 2>/dev/null | wc -l | tr -d ' ' || echo "0")
|
|
596
|
+
echo -e " Total cycles: ${total_cycles}"
|
|
597
|
+
|
|
598
|
+
echo ""
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
# ─── Help ─────────────────────────────────────────────────────────────────────
|
|
602
|
+
strategic_show_help() {
|
|
603
|
+
echo -e "${PURPLE}${BOLD}Shipwright Strategic Intelligence Agent${RESET} v${VERSION}\n"
|
|
604
|
+
echo -e "Reads strategy, metrics, and codebase state to create high-impact improvement issues.\n"
|
|
605
|
+
echo -e "${BOLD}Usage:${RESET}"
|
|
606
|
+
echo -e " sw-strategic.sh <command>\n"
|
|
607
|
+
echo -e "${BOLD}Commands:${RESET}"
|
|
608
|
+
echo -e " run Run a strategic analysis cycle"
|
|
609
|
+
echo -e " status Show last run stats and cooldown"
|
|
610
|
+
echo -e " help Show this help\n"
|
|
611
|
+
echo -e "${BOLD}Environment:${RESET}"
|
|
612
|
+
echo -e " CLAUDE_CODE_OAUTH_TOKEN Required for Claude access"
|
|
613
|
+
echo -e " NO_GITHUB=true Dry-run mode (no issue creation)\n"
|
|
614
|
+
echo -e "${BOLD}Cooldown:${RESET}"
|
|
615
|
+
echo -e " 12 hours between cycles (checks events.jsonl)\n"
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
# ─── Daemon Integration (sourced mode) ────────────────────────────────────────
|
|
619
|
+
strategic_patrol_run() {
|
|
620
|
+
# Called by daemon during patrol cycle
|
|
621
|
+
# Check cooldown (12h minimum between runs)
|
|
622
|
+
# Requires CLAUDE_CODE_OAUTH_TOKEN
|
|
623
|
+
if [[ -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then
|
|
624
|
+
echo -e " ${DIM}●${RESET} Strategic patrol skipped (no CLAUDE_CODE_OAUTH_TOKEN)"
|
|
625
|
+
return 0
|
|
626
|
+
fi
|
|
627
|
+
|
|
628
|
+
if ! strategic_check_cooldown; then
|
|
629
|
+
return 0
|
|
630
|
+
fi
|
|
631
|
+
|
|
632
|
+
echo -e "\n ${BOLD}Strategic Intelligence Patrol${RESET}"
|
|
633
|
+
strategic_run
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
# ─── Source Guard ─────────────────────────────────────────────────────────────
|
|
637
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
638
|
+
set -euo pipefail
|
|
639
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
640
|
+
|
|
641
|
+
main() {
|
|
642
|
+
local cmd="${1:-help}"
|
|
643
|
+
shift 2>/dev/null || true
|
|
644
|
+
|
|
645
|
+
case "$cmd" in
|
|
646
|
+
run) strategic_run ;;
|
|
647
|
+
status) strategic_status ;;
|
|
648
|
+
help) strategic_show_help ;;
|
|
649
|
+
*)
|
|
650
|
+
error "Unknown command: $cmd"
|
|
651
|
+
strategic_show_help
|
|
652
|
+
exit 1
|
|
653
|
+
;;
|
|
654
|
+
esac
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
main "$@"
|
|
658
|
+
fi
|