shipwright-cli 2.0.0 → 2.1.1
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 +160 -72
- package/completions/_shipwright +59 -7
- package/completions/shipwright.bash +24 -4
- package/completions/shipwright.fish +80 -2
- package/dashboard/server.ts +208 -0
- package/docs/tmux-research/TMUX-ARCHITECTURE.md +567 -0
- package/docs/tmux-research/TMUX-AUDIT.md +925 -0
- package/docs/tmux-research/TMUX-BEST-PRACTICES-2025-2026.md +829 -0
- package/docs/tmux-research/TMUX-QUICK-REFERENCE.md +543 -0
- package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +438 -0
- package/package.json +2 -2
- package/scripts/lib/helpers.sh +7 -0
- package/scripts/sw +116 -2
- package/scripts/sw-activity.sh +1 -1
- package/scripts/sw-adaptive.sh +1 -1
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +1 -1
- package/scripts/sw-autonomous.sh +128 -38
- package/scripts/sw-changelog.sh +1 -1
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +1 -1
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +62 -1
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +1 -1
- package/scripts/sw-cost.sh +44 -3
- package/scripts/sw-daemon.sh +155 -27
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +958 -118
- package/scripts/sw-decompose.sh +1 -1
- package/scripts/sw-deps.sh +1 -1
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +1 -1
- package/scripts/sw-docs-agent.sh +1 -1
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +49 -1
- package/scripts/sw-dora.sh +1 -1
- package/scripts/sw-durable.sh +1 -1
- package/scripts/sw-e2e-orchestrator.sh +1 -1
- package/scripts/sw-eventbus.sh +1 -1
- package/scripts/sw-feedback.sh +23 -15
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +1 -1
- package/scripts/sw-fleet-viz.sh +1 -1
- package/scripts/sw-fleet.sh +1 -1
- package/scripts/sw-github-app.sh +1 -1
- package/scripts/sw-github-checks.sh +4 -4
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +1 -1
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +1 -1
- package/scripts/sw-incident.sh +45 -6
- package/scripts/sw-init.sh +150 -24
- package/scripts/sw-instrument.sh +1 -1
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +1 -1
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +204 -19
- package/scripts/sw-memory.sh +18 -1
- package/scripts/sw-mission-control.sh +1 -1
- package/scripts/sw-model-router.sh +1 -1
- package/scripts/sw-otel.sh +1 -1
- package/scripts/sw-oversight.sh +76 -1
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +302 -18
- package/scripts/sw-pm.sh +70 -5
- package/scripts/sw-pr-lifecycle.sh +1 -1
- package/scripts/sw-predictive.sh +8 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +1 -1
- package/scripts/sw-quality.sh +1 -1
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +1853 -178
- package/scripts/sw-regression.sh +1 -1
- package/scripts/sw-release-manager.sh +1 -1
- package/scripts/sw-release.sh +1 -1
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +1 -1
- package/scripts/sw-retro.sh +1 -1
- package/scripts/sw-scale.sh +1 -1
- package/scripts/sw-security-audit.sh +1 -1
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +263 -127
- package/scripts/sw-standup.sh +1 -1
- package/scripts/sw-status.sh +44 -2
- package/scripts/sw-strategic.sh +189 -41
- package/scripts/sw-stream.sh +1 -1
- package/scripts/sw-swarm.sh +42 -5
- package/scripts/sw-team-stages.sh +1 -1
- package/scripts/sw-templates.sh +4 -4
- package/scripts/sw-testgen.sh +66 -15
- package/scripts/sw-tmux-pipeline.sh +1 -1
- package/scripts/sw-tmux-role-color.sh +58 -0
- package/scripts/sw-tmux-status.sh +128 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +1 -1
- package/scripts/sw-tracker.sh +1 -1
- package/scripts/sw-triage.sh +61 -37
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +30 -2
- package/scripts/sw-webhook.sh +1 -1
- package/scripts/sw-widgets.sh +1 -1
- package/scripts/sw-worktree.sh +1 -1
- package/tmux/shipwright-overlay.conf +35 -17
- package/tmux/tmux.conf +26 -21
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="2.
|
|
7
|
+
VERSION="2.1.1"
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
10
10
|
|
|
@@ -188,6 +188,26 @@ if [[ "$JSON_OUTPUT" == "true" ]]; then
|
|
|
188
188
|
fi
|
|
189
189
|
fi
|
|
190
190
|
|
|
191
|
+
# -- database --
|
|
192
|
+
DATABASE_JSON="null"
|
|
193
|
+
_db_file="${HOME}/.shipwright/shipwright.db"
|
|
194
|
+
if command -v sqlite3 &>/dev/null && [[ -f "$_db_file" ]]; then
|
|
195
|
+
_db_ver=$(sqlite3 "$_db_file" "SELECT MAX(version) FROM _schema;" 2>/dev/null || echo "0")
|
|
196
|
+
_db_wal=$(sqlite3 "$_db_file" "PRAGMA journal_mode;" 2>/dev/null || echo "unknown")
|
|
197
|
+
_db_events=$(sqlite3 "$_db_file" "SELECT COUNT(*) FROM events;" 2>/dev/null || echo "0")
|
|
198
|
+
_db_runs=$(sqlite3 "$_db_file" "SELECT COUNT(*) FROM pipeline_runs;" 2>/dev/null || echo "0")
|
|
199
|
+
_db_costs=$(sqlite3 "$_db_file" "SELECT COUNT(*) FROM cost_entries;" 2>/dev/null || echo "0")
|
|
200
|
+
_db_size=$(ls -l "$_db_file" 2>/dev/null | awk '{print $5}')
|
|
201
|
+
DATABASE_JSON=$(jq -n \
|
|
202
|
+
--argjson schema_version "${_db_ver:-0}" \
|
|
203
|
+
--arg wal_mode "$_db_wal" \
|
|
204
|
+
--argjson events "${_db_events:-0}" \
|
|
205
|
+
--argjson runs "${_db_runs:-0}" \
|
|
206
|
+
--argjson costs "${_db_costs:-0}" \
|
|
207
|
+
--argjson size_bytes "${_db_size:-0}" \
|
|
208
|
+
'{schema_version:$schema_version, wal_mode:$wal_mode, events:$events, runs:$runs, costs:$costs, size_bytes:$size_bytes}') || DATABASE_JSON="null"
|
|
209
|
+
fi
|
|
210
|
+
|
|
191
211
|
# -- assemble and output --
|
|
192
212
|
jq -n \
|
|
193
213
|
--arg version "$VERSION" \
|
|
@@ -200,6 +220,7 @@ if [[ "$JSON_OUTPUT" == "true" ]]; then
|
|
|
200
220
|
--argjson heartbeats "$HEARTBEATS_JSON" \
|
|
201
221
|
--argjson remote_machines "$MACHINES_JSON" \
|
|
202
222
|
--argjson connected_developers "$DEVELOPERS_JSON" \
|
|
223
|
+
--argjson database "$DATABASE_JSON" \
|
|
203
224
|
'{
|
|
204
225
|
version: $version,
|
|
205
226
|
timestamp: $timestamp,
|
|
@@ -210,7 +231,8 @@ if [[ "$JSON_OUTPUT" == "true" ]]; then
|
|
|
210
231
|
issue_tracker: $issue_tracker,
|
|
211
232
|
heartbeats: $heartbeats,
|
|
212
233
|
remote_machines: $remote_machines,
|
|
213
|
-
connected_developers: $connected_developers
|
|
234
|
+
connected_developers: $connected_developers,
|
|
235
|
+
database: $database
|
|
214
236
|
}'
|
|
215
237
|
exit 0
|
|
216
238
|
fi
|
|
@@ -708,6 +730,26 @@ if [[ -f "$MACHINES_FILE" ]]; then
|
|
|
708
730
|
fi
|
|
709
731
|
fi
|
|
710
732
|
|
|
733
|
+
# ─── Database ────────────────────────────────────────────────────────────
|
|
734
|
+
|
|
735
|
+
_DB_FILE="${HOME}/.shipwright/shipwright.db"
|
|
736
|
+
if command -v sqlite3 &>/dev/null && [[ -f "$_DB_FILE" ]]; then
|
|
737
|
+
echo ""
|
|
738
|
+
echo -e "${PURPLE}${BOLD} DATABASE${RESET} ${DIM}~/.shipwright/shipwright.db${RESET}"
|
|
739
|
+
echo -e "${DIM} ──────────────────────────────────────────${RESET}"
|
|
740
|
+
|
|
741
|
+
_db_wal=$(sqlite3 "$_DB_FILE" "PRAGMA journal_mode;" 2>/dev/null || echo "?")
|
|
742
|
+
_db_ver=$(sqlite3 "$_DB_FILE" "SELECT MAX(version) FROM _schema;" 2>/dev/null || echo "?")
|
|
743
|
+
_db_size_bytes=$(ls -l "$_DB_FILE" 2>/dev/null | awk '{print $5}')
|
|
744
|
+
_db_size_mb=$(awk -v s="${_db_size_bytes:-0}" 'BEGIN { printf "%.1f", s / 1048576 }')
|
|
745
|
+
_db_events=$(sqlite3 "$_DB_FILE" "SELECT COUNT(*) FROM events;" 2>/dev/null || echo "0")
|
|
746
|
+
_db_runs=$(sqlite3 "$_DB_FILE" "SELECT COUNT(*) FROM pipeline_runs;" 2>/dev/null || echo "0")
|
|
747
|
+
_db_costs=$(sqlite3 "$_DB_FILE" "SELECT COUNT(*) FROM cost_entries;" 2>/dev/null || echo "0")
|
|
748
|
+
|
|
749
|
+
echo -e " ${GREEN}●${RESET} ${BOLD}SQLite${RESET} ${DIM}v${_db_ver} WAL=${_db_wal} ${_db_size_mb}MB${RESET}"
|
|
750
|
+
echo -e " ${DIM}events:${_db_events} runs:${_db_runs} costs:${_db_costs}${RESET}"
|
|
751
|
+
fi
|
|
752
|
+
|
|
711
753
|
# ─── Connected Developers ─────────────────────────────────────────────────
|
|
712
754
|
|
|
713
755
|
# Check if curl and jq are available
|
package/scripts/sw-strategic.sh
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# When sourced, do NOT add set -euo pipefail — the parent handles that.
|
|
8
8
|
# When run directly, main() sets up the error handling.
|
|
9
9
|
|
|
10
|
-
VERSION="2.
|
|
10
|
+
VERSION="2.1.1"
|
|
11
11
|
|
|
12
12
|
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
13
13
|
CYAN='\033[38;2;0;212;255m'
|
|
@@ -57,11 +57,92 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
57
57
|
fi
|
|
58
58
|
|
|
59
59
|
# ─── Constants ────────────────────────────────────────────────────────────────
|
|
60
|
-
STRATEGIC_MAX_ISSUES=
|
|
61
|
-
STRATEGIC_COOLDOWN_SECONDS=
|
|
62
|
-
STRATEGIC_MODEL="claude-
|
|
63
|
-
STRATEGIC_MAX_TOKENS=
|
|
60
|
+
STRATEGIC_MAX_ISSUES=5
|
|
61
|
+
STRATEGIC_COOLDOWN_SECONDS=14400 # 4 hours
|
|
62
|
+
STRATEGIC_MODEL="claude-sonnet-4-5-20250929"
|
|
63
|
+
STRATEGIC_MAX_TOKENS=4096
|
|
64
64
|
STRATEGIC_STRATEGY_LINES=200
|
|
65
|
+
STRATEGIC_LABELS="auto-patrol,ready-to-build,strategic,shipwright"
|
|
66
|
+
|
|
67
|
+
# ─── Semantic Dedup ─────────────────────────────────────────────────────────
|
|
68
|
+
# Cache of existing issue titles (open + recently closed) loaded at cycle start.
|
|
69
|
+
STRATEGIC_TITLE_CACHE=""
|
|
70
|
+
STRATEGIC_OVERLAP_THRESHOLD=60 # Skip if >60% word overlap
|
|
71
|
+
|
|
72
|
+
# Compute word-overlap similarity between two titles (0-100).
|
|
73
|
+
# Uses lowercase word sets, ignoring common stop words.
|
|
74
|
+
strategic_word_overlap() {
|
|
75
|
+
local title_a="$1"
|
|
76
|
+
local title_b="$2"
|
|
77
|
+
|
|
78
|
+
# Normalize: lowercase, strip punctuation, split to words, basic stemming
|
|
79
|
+
local words_a words_b
|
|
80
|
+
words_a=$(printf '%s' "$title_a" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '\n' | \
|
|
81
|
+
sed -E 's/ations?$//; s/tions?$//; s/ments?$//; s/ings?$//; s/ness$//; s/ies$/y/; s/([^s])s$/\1/' | \
|
|
82
|
+
sort -u | grep -vE '^(a|an|the|and|or|for|to|in|of|is|it|by|on|at|with|from|based)$' | grep -v '^$' || true)
|
|
83
|
+
words_b=$(printf '%s' "$title_b" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '\n' | \
|
|
84
|
+
sed -E 's/ations?$//; s/tions?$//; s/ments?$//; s/ings?$//; s/ness$//; s/ies$/y/; s/([^s])s$/\1/' | \
|
|
85
|
+
sort -u | grep -vE '^(a|an|the|and|or|for|to|in|of|is|it|by|on|at|with|from|based)$' | grep -v '^$' || true)
|
|
86
|
+
|
|
87
|
+
[[ -z "$words_a" || -z "$words_b" ]] && echo "0" && return 0
|
|
88
|
+
|
|
89
|
+
# Count words in each set
|
|
90
|
+
local count_a count_b
|
|
91
|
+
count_a=$(printf '%s\n' "$words_a" | wc -l | tr -d ' ')
|
|
92
|
+
count_b=$(printf '%s\n' "$words_b" | wc -l | tr -d ' ')
|
|
93
|
+
|
|
94
|
+
# Count shared words (intersection)
|
|
95
|
+
local shared
|
|
96
|
+
shared=$(comm -12 <(printf '%s\n' "$words_a") <(printf '%s\n' "$words_b") | wc -l | tr -d ' ')
|
|
97
|
+
|
|
98
|
+
# Overlap = shared / min(count_a, count_b) * 100
|
|
99
|
+
local min_count
|
|
100
|
+
if [[ "$count_a" -le "$count_b" ]]; then
|
|
101
|
+
min_count="$count_a"
|
|
102
|
+
else
|
|
103
|
+
min_count="$count_b"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
[[ "$min_count" -eq 0 ]] && echo "0" && return 0
|
|
107
|
+
|
|
108
|
+
echo $(( shared * 100 / min_count ))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Load all open + recently closed issue titles into cache.
|
|
112
|
+
strategic_load_title_cache() {
|
|
113
|
+
STRATEGIC_TITLE_CACHE=""
|
|
114
|
+
|
|
115
|
+
if [[ "${NO_GITHUB:-false}" == "true" ]]; then
|
|
116
|
+
return 0
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
local open_titles closed_titles
|
|
120
|
+
open_titles=$(gh issue list --state open --json title --jq '.[].title' 2>/dev/null || echo "")
|
|
121
|
+
closed_titles=$(gh issue list --state closed --limit 30 --json title --jq '.[].title' 2>/dev/null || echo "")
|
|
122
|
+
|
|
123
|
+
STRATEGIC_TITLE_CACHE="${open_titles}
|
|
124
|
+
${closed_titles}"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Check if a title has >threshold% overlap with any cached title.
|
|
128
|
+
# Returns 0 (true) if a near-duplicate is found, 1 (false) otherwise.
|
|
129
|
+
strategic_is_near_duplicate() {
|
|
130
|
+
local new_title="$1"
|
|
131
|
+
|
|
132
|
+
[[ -z "$STRATEGIC_TITLE_CACHE" ]] && return 1
|
|
133
|
+
|
|
134
|
+
while IFS= read -r existing_title; do
|
|
135
|
+
[[ -z "$existing_title" ]] && continue
|
|
136
|
+
local overlap
|
|
137
|
+
overlap=$(strategic_word_overlap "$new_title" "$existing_title")
|
|
138
|
+
if [[ "$overlap" -gt "$STRATEGIC_OVERLAP_THRESHOLD" ]]; then
|
|
139
|
+
info " Near-duplicate (${overlap}% overlap): \"${existing_title}\"" >&2
|
|
140
|
+
return 0
|
|
141
|
+
fi
|
|
142
|
+
done <<< "$STRATEGIC_TITLE_CACHE"
|
|
143
|
+
|
|
144
|
+
return 1
|
|
145
|
+
}
|
|
65
146
|
|
|
66
147
|
# ─── Cooldown Check ──────────────────────────────────────────────────────────
|
|
67
148
|
strategic_check_cooldown() {
|
|
@@ -257,6 +338,14 @@ strategic_build_prompt() {
|
|
|
257
338
|
open_issues="(GitHub access disabled)"
|
|
258
339
|
fi
|
|
259
340
|
|
|
341
|
+
# Recently closed issues (last 20) — so we don't rebuild what was just shipped
|
|
342
|
+
local recent_closed=""
|
|
343
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]]; then
|
|
344
|
+
recent_closed=$(gh issue list --state closed --limit 20 --json number,title --jq '.[] | "#\(.number): \(.title)"' 2>/dev/null || echo "(could not fetch)")
|
|
345
|
+
else
|
|
346
|
+
recent_closed="(GitHub access disabled)"
|
|
347
|
+
fi
|
|
348
|
+
|
|
260
349
|
# Compose the prompt
|
|
261
350
|
cat <<PROMPT_EOF
|
|
262
351
|
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.
|
|
@@ -278,6 +367,9 @@ ${strategy_content}
|
|
|
278
367
|
## Open Issues (already in progress — do NOT duplicate these)
|
|
279
368
|
${open_issues}
|
|
280
369
|
|
|
370
|
+
## Recently Completed (already built — do NOT recreate these)
|
|
371
|
+
${recent_closed}
|
|
372
|
+
|
|
281
373
|
## Your Task
|
|
282
374
|
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
375
|
|
|
@@ -292,12 +384,14 @@ ACCEPTANCE: <bullet list of acceptance criteria, one per line starting with "- "
|
|
|
292
384
|
---
|
|
293
385
|
|
|
294
386
|
Rules:
|
|
295
|
-
- Do NOT duplicate any open issue
|
|
387
|
+
- Do NOT duplicate any open issue OR any recently completed issue
|
|
296
388
|
- Prioritize based on STRATEGY.md priorities (P0 > P1 > P2 > ...)
|
|
297
389
|
- Focus on concrete, actionable improvements (not vague goals)
|
|
298
390
|
- Each issue should be completable by one autonomous pipeline run
|
|
299
|
-
-
|
|
300
|
-
-
|
|
391
|
+
- Balance: reliability/DX fixes AND strategic new capabilities
|
|
392
|
+
- Think about what would make the biggest impact on success rate, developer experience, and system intelligence
|
|
393
|
+
- Be ambitious — push the platform forward, don't just maintain it
|
|
394
|
+
- Maximum ${STRATEGIC_MAX_ISSUES} issues
|
|
301
395
|
PROMPT_EOF
|
|
302
396
|
}
|
|
303
397
|
|
|
@@ -320,7 +414,7 @@ strategic_call_api() {
|
|
|
320
414
|
printf '%s' "$prompt" > "$tmp_prompt"
|
|
321
415
|
|
|
322
416
|
local response_text
|
|
323
|
-
response_text=$(
|
|
417
|
+
response_text=$(cat "$tmp_prompt" | claude -p --max-turns 1 --model "$STRATEGIC_MODEL" 2>/dev/null || echo "")
|
|
324
418
|
rm -f "$tmp_prompt"
|
|
325
419
|
|
|
326
420
|
if [[ -z "$response_text" ]]; then
|
|
@@ -328,6 +422,14 @@ strategic_call_api() {
|
|
|
328
422
|
return 1
|
|
329
423
|
fi
|
|
330
424
|
|
|
425
|
+
# Strip markdown code fences if present (Sonnet sometimes wraps output)
|
|
426
|
+
response_text=$(printf '%s' "$response_text" | sed '/^```/d')
|
|
427
|
+
|
|
428
|
+
# Debug: show first 200 chars of response
|
|
429
|
+
local preview
|
|
430
|
+
preview=$(printf '%s' "$response_text" | head -c 200)
|
|
431
|
+
info "Response preview: ${preview}..." >&2
|
|
432
|
+
|
|
331
433
|
printf '%s' "$response_text"
|
|
332
434
|
}
|
|
333
435
|
|
|
@@ -360,7 +462,7 @@ strategic_parse_and_create() {
|
|
|
360
462
|
fi
|
|
361
463
|
|
|
362
464
|
if [[ "$created" -ge "$STRATEGIC_MAX_ISSUES" ]]; then
|
|
363
|
-
info "Reached max issues per cycle (${STRATEGIC_MAX_ISSUES})"
|
|
465
|
+
info "Reached max issues per cycle (${STRATEGIC_MAX_ISSUES})" >&2
|
|
364
466
|
break
|
|
365
467
|
fi
|
|
366
468
|
fi
|
|
@@ -372,34 +474,38 @@ strategic_parse_and_create() {
|
|
|
372
474
|
continue
|
|
373
475
|
fi
|
|
374
476
|
|
|
375
|
-
#
|
|
376
|
-
|
|
377
|
-
|
|
477
|
+
# Strip leading markdown bold/italic markers for field matching
|
|
478
|
+
local clean_line
|
|
479
|
+
clean_line=$(echo "$line" | sed 's/^\*\*//;s/\*\*$//' | sed 's/^__//;s/__$//' | sed 's/^[[:space:]]*//')
|
|
480
|
+
|
|
481
|
+
# Parse fields (match with and without markdown formatting)
|
|
482
|
+
if [[ "$clean_line" == ISSUE_TITLE:* ]]; then
|
|
483
|
+
current_title="${clean_line#ISSUE_TITLE: }"
|
|
378
484
|
current_title="${current_title#ISSUE_TITLE:}"
|
|
379
485
|
current_title=$(echo "$current_title" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
380
486
|
in_acceptance=false
|
|
381
|
-
elif [[ "$
|
|
382
|
-
current_priority="${
|
|
487
|
+
elif [[ "$clean_line" == PRIORITY:* ]]; then
|
|
488
|
+
current_priority="${clean_line#PRIORITY: }"
|
|
383
489
|
current_priority="${current_priority#PRIORITY:}"
|
|
384
490
|
current_priority=$(echo "$current_priority" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
385
491
|
in_acceptance=false
|
|
386
|
-
elif [[ "$
|
|
387
|
-
current_complexity="${
|
|
492
|
+
elif [[ "$clean_line" == COMPLEXITY:* ]]; then
|
|
493
|
+
current_complexity="${clean_line#COMPLEXITY: }"
|
|
388
494
|
current_complexity="${current_complexity#COMPLEXITY:}"
|
|
389
495
|
current_complexity=$(echo "$current_complexity" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
390
496
|
in_acceptance=false
|
|
391
|
-
elif [[ "$
|
|
392
|
-
current_strategy="${
|
|
497
|
+
elif [[ "$clean_line" == STRATEGY_AREA:* ]]; then
|
|
498
|
+
current_strategy="${clean_line#STRATEGY_AREA: }"
|
|
393
499
|
current_strategy="${current_strategy#STRATEGY_AREA:}"
|
|
394
500
|
current_strategy=$(echo "$current_strategy" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
395
501
|
in_acceptance=false
|
|
396
|
-
elif [[ "$
|
|
397
|
-
current_description="${
|
|
502
|
+
elif [[ "$clean_line" == DESCRIPTION:* ]]; then
|
|
503
|
+
current_description="${clean_line#DESCRIPTION: }"
|
|
398
504
|
current_description="${current_description#DESCRIPTION:}"
|
|
399
505
|
current_description=$(echo "$current_description" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
400
506
|
in_acceptance=false
|
|
401
|
-
elif [[ "$
|
|
402
|
-
current_acceptance="${
|
|
507
|
+
elif [[ "$clean_line" == ACCEPTANCE:* ]]; then
|
|
508
|
+
current_acceptance="${clean_line#ACCEPTANCE: }"
|
|
403
509
|
current_acceptance="${current_acceptance#ACCEPTANCE:}"
|
|
404
510
|
current_acceptance=$(echo "$current_acceptance" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
405
511
|
in_acceptance=true
|
|
@@ -442,9 +548,15 @@ strategic_create_issue() {
|
|
|
442
548
|
return 1
|
|
443
549
|
fi
|
|
444
550
|
|
|
551
|
+
# Semantic dedup: check word overlap against cached titles
|
|
552
|
+
if strategic_is_near_duplicate "$title"; then
|
|
553
|
+
info " Skipping near-duplicate: ${title}" >&2
|
|
554
|
+
return 1
|
|
555
|
+
fi
|
|
556
|
+
|
|
445
557
|
# Dry-run mode
|
|
446
558
|
if [[ "${NO_GITHUB:-false}" == "true" ]]; then
|
|
447
|
-
info " [dry-run] Would create: ${title}"
|
|
559
|
+
info " [dry-run] Would create: ${title}" >&2
|
|
448
560
|
return 0
|
|
449
561
|
fi
|
|
450
562
|
|
|
@@ -452,7 +564,7 @@ strategic_create_issue() {
|
|
|
452
564
|
local existing
|
|
453
565
|
existing=$(gh issue list --state open --search "$title" --json number,title --jq ".[].title" 2>/dev/null || echo "")
|
|
454
566
|
if echo "$existing" | grep -qF "$title" 2>/dev/null; then
|
|
455
|
-
info " Skipping duplicate: ${title}"
|
|
567
|
+
info " Skipping duplicate: ${title}" >&2
|
|
456
568
|
return 1
|
|
457
569
|
fi
|
|
458
570
|
|
|
@@ -479,29 +591,53 @@ $(echo -e "$acceptance")
|
|
|
479
591
|
BODY_EOF
|
|
480
592
|
)
|
|
481
593
|
|
|
482
|
-
local labels="
|
|
594
|
+
local labels="${STRATEGIC_LABELS}"
|
|
595
|
+
|
|
596
|
+
# Ensure all labels exist (create if missing)
|
|
597
|
+
local IFS=','
|
|
598
|
+
for lbl in $labels; do
|
|
599
|
+
gh label create "$lbl" --color "7c3aed" 2>/dev/null || true
|
|
600
|
+
done
|
|
601
|
+
unset IFS
|
|
483
602
|
|
|
484
|
-
|
|
603
|
+
local issue_url
|
|
604
|
+
issue_url=$(gh issue create \
|
|
485
605
|
--title "$title" \
|
|
486
606
|
--body "$body" \
|
|
487
|
-
--label "$labels" 2>/dev/null || {
|
|
488
|
-
warn " Failed to create issue: ${title}"
|
|
607
|
+
--label "$labels" 2>/dev/null) || {
|
|
608
|
+
warn " Failed to create issue: ${title}" >&2
|
|
489
609
|
return 1
|
|
490
610
|
}
|
|
491
611
|
|
|
492
612
|
emit_event "strategic.issue_created" "title=$title" "priority=$priority" "complexity=$complexity"
|
|
493
|
-
|
|
613
|
+
# Add to title cache so subsequent issues in this cycle don't duplicate
|
|
614
|
+
STRATEGIC_TITLE_CACHE="${STRATEGIC_TITLE_CACHE}
|
|
615
|
+
${title}"
|
|
616
|
+
# Output to stderr so it doesn't pollute the parse_and_create return value
|
|
617
|
+
success " Created issue: ${title} (${issue_url})" >&2
|
|
494
618
|
return 0
|
|
495
619
|
}
|
|
496
620
|
|
|
497
621
|
# ─── Main Strategic Run ──────────────────────────────────────────────────────
|
|
498
622
|
strategic_run() {
|
|
623
|
+
local force=false
|
|
624
|
+
while [[ $# -gt 0 ]]; do
|
|
625
|
+
case "$1" in
|
|
626
|
+
--force|-f) force=true; shift ;;
|
|
627
|
+
*) shift ;;
|
|
628
|
+
esac
|
|
629
|
+
done
|
|
630
|
+
|
|
499
631
|
echo -e "\n${PURPLE}${BOLD}━━━ Strategic Intelligence Agent ━━━${RESET}"
|
|
500
632
|
echo -e "${DIM} Analyzing codebase, strategy, and metrics...${RESET}\n"
|
|
501
633
|
|
|
502
|
-
# Check cooldown
|
|
503
|
-
if
|
|
504
|
-
|
|
634
|
+
# Check cooldown (skip if --force)
|
|
635
|
+
if [[ "$force" != true ]]; then
|
|
636
|
+
if ! strategic_check_cooldown; then
|
|
637
|
+
return 0
|
|
638
|
+
fi
|
|
639
|
+
else
|
|
640
|
+
info "Cooldown bypassed (--force)"
|
|
505
641
|
fi
|
|
506
642
|
|
|
507
643
|
# Check auth token
|
|
@@ -510,6 +646,10 @@ strategic_run() {
|
|
|
510
646
|
return 1
|
|
511
647
|
fi
|
|
512
648
|
|
|
649
|
+
# Load existing issue titles for semantic dedup
|
|
650
|
+
info "Loading issue title cache for dedup..."
|
|
651
|
+
strategic_load_title_cache
|
|
652
|
+
|
|
513
653
|
# Build prompt with all context
|
|
514
654
|
info "Gathering context..."
|
|
515
655
|
local prompt
|
|
@@ -539,7 +679,13 @@ strategic_run() {
|
|
|
539
679
|
echo -e " Issues skipped: ${skipped} (duplicates)"
|
|
540
680
|
echo ""
|
|
541
681
|
|
|
542
|
-
|
|
682
|
+
# Only record cycle completion if we actually ran analysis (for cooldown tracking)
|
|
683
|
+
# This prevents a "0 issues" run from burning the cooldown timer
|
|
684
|
+
if [[ "$created" -gt 0 ]] || [[ "$skipped" -gt 0 ]]; then
|
|
685
|
+
emit_event "strategic.cycle_complete" "issues_created=$created" "issues_skipped=$skipped"
|
|
686
|
+
else
|
|
687
|
+
info "No issues produced — cooldown NOT reset (will retry next cycle)"
|
|
688
|
+
fi
|
|
543
689
|
}
|
|
544
690
|
|
|
545
691
|
# ─── Status Command ──────────────────────────────────────────────────────────
|
|
@@ -605,14 +751,16 @@ strategic_show_help() {
|
|
|
605
751
|
echo -e "${BOLD}Usage:${RESET}"
|
|
606
752
|
echo -e " sw-strategic.sh <command>\n"
|
|
607
753
|
echo -e "${BOLD}Commands:${RESET}"
|
|
608
|
-
echo -e " run
|
|
609
|
-
echo -e " status
|
|
610
|
-
echo -e " help
|
|
754
|
+
echo -e " run [--force] Run a strategic analysis cycle (--force bypasses cooldown)"
|
|
755
|
+
echo -e " status Show last run stats and cooldown"
|
|
756
|
+
echo -e " help Show this help\n"
|
|
611
757
|
echo -e "${BOLD}Environment:${RESET}"
|
|
612
758
|
echo -e " CLAUDE_CODE_OAUTH_TOKEN Required for Claude access"
|
|
613
|
-
echo -e " NO_GITHUB=true
|
|
614
|
-
echo -e "${BOLD}
|
|
615
|
-
echo -e "
|
|
759
|
+
echo -e " NO_GITHUB=true Dry-run mode (no issue creation)\n"
|
|
760
|
+
echo -e "${BOLD}Configuration:${RESET}"
|
|
761
|
+
echo -e " Max issues/cycle: ${STRATEGIC_MAX_ISSUES}"
|
|
762
|
+
echo -e " Cooldown: $(( STRATEGIC_COOLDOWN_SECONDS / 3600 )) hours"
|
|
763
|
+
echo -e " Model: ${STRATEGIC_MODEL}\n"
|
|
616
764
|
}
|
|
617
765
|
|
|
618
766
|
# ─── Daemon Integration (sourced mode) ────────────────────────────────────────
|
|
@@ -643,7 +791,7 @@ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
|
643
791
|
shift 2>/dev/null || true
|
|
644
792
|
|
|
645
793
|
case "$cmd" in
|
|
646
|
-
run) strategic_run ;;
|
|
794
|
+
run) strategic_run "$@" ;;
|
|
647
795
|
status) strategic_status ;;
|
|
648
796
|
help) strategic_show_help ;;
|
|
649
797
|
*)
|
package/scripts/sw-stream.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# ║ Streams tmux pane output in real-time to the dashboard or CLI. ║
|
|
6
6
|
# ║ Captures output periodically, tags by agent/team, supports replay. ║
|
|
7
7
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
8
|
-
VERSION="2.
|
|
8
|
+
VERSION="2.1.1"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|
package/scripts/sw-swarm.sh
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="2.
|
|
9
|
+
VERSION="2.1.1"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
12
|
|
|
12
13
|
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
13
14
|
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
@@ -113,13 +114,33 @@ record_metric() {
|
|
|
113
114
|
|
|
114
115
|
# ─── Spawn a new agent ────────────────────────────────────────────────────
|
|
115
116
|
cmd_spawn() {
|
|
116
|
-
local agent_type="${1:-
|
|
117
|
+
local agent_type="${1:-}"
|
|
118
|
+
shift 2>/dev/null || true
|
|
119
|
+
local task_desc="${1:-}"
|
|
117
120
|
shift 2>/dev/null || true
|
|
118
121
|
|
|
119
122
|
ensure_dirs
|
|
120
123
|
init_registry
|
|
121
124
|
init_config
|
|
122
125
|
|
|
126
|
+
# Recruit-powered type selection when task description given but no explicit type
|
|
127
|
+
if [[ -z "$agent_type" || "$agent_type" == "--task" ]] && [[ -n "$task_desc" ]]; then
|
|
128
|
+
if [[ -x "${SCRIPT_DIR:-}/sw-recruit.sh" ]]; then
|
|
129
|
+
local _recruit_match
|
|
130
|
+
_recruit_match=$(bash "$SCRIPT_DIR/sw-recruit.sh" match --json "$task_desc" 2>/dev/null) || true
|
|
131
|
+
if [[ -n "$_recruit_match" ]]; then
|
|
132
|
+
local _role
|
|
133
|
+
_role=$(echo "$_recruit_match" | jq -r '.primary_role // ""' 2>/dev/null) || true
|
|
134
|
+
case "$_role" in
|
|
135
|
+
architect|security-auditor|incident-responder) agent_type="powerful" ;;
|
|
136
|
+
docs-writer) agent_type="fast" ;;
|
|
137
|
+
*) agent_type="standard" ;;
|
|
138
|
+
esac
|
|
139
|
+
fi
|
|
140
|
+
fi
|
|
141
|
+
fi
|
|
142
|
+
[[ -z "$agent_type" ]] && agent_type="standard"
|
|
143
|
+
|
|
123
144
|
local agent_id
|
|
124
145
|
agent_id=$(gen_agent_id)
|
|
125
146
|
|
|
@@ -165,6 +186,16 @@ cmd_spawn() {
|
|
|
165
186
|
mv "$tmp_file" "$REGISTRY_FILE"
|
|
166
187
|
record_metric "$agent_id" "spawn" "1" "$agent_type"
|
|
167
188
|
|
|
189
|
+
# Create real tmux session for the agent (so scale/loop can send commands)
|
|
190
|
+
if command -v tmux &>/dev/null; then
|
|
191
|
+
local session_name="swarm-${agent_id}"
|
|
192
|
+
if ! tmux has-session -t "$session_name" 2>/dev/null; then
|
|
193
|
+
tmux new-session -d -s "$session_name" -c "$REPO_DIR" \
|
|
194
|
+
"echo \"Agent $agent_id ready (type: $agent_type)\"; while true; do sleep 3600; done" 2>/dev/null && \
|
|
195
|
+
info "Tmux session created: $session_name" || warn "Tmux session creation failed (agent still in registry)"
|
|
196
|
+
fi
|
|
197
|
+
fi
|
|
198
|
+
|
|
168
199
|
success "Spawned agent: ${CYAN}${agent_id}${RESET} (type: ${agent_type})"
|
|
169
200
|
echo ""
|
|
170
201
|
echo -e " Agent ID: ${CYAN}${agent_id}${RESET}"
|
|
@@ -206,18 +237,24 @@ cmd_retire() {
|
|
|
206
237
|
echo ""
|
|
207
238
|
fi
|
|
208
239
|
|
|
209
|
-
#
|
|
240
|
+
# Kill real tmux session if present
|
|
241
|
+
local session_name="swarm-${agent_id}"
|
|
242
|
+
if command -v tmux &>/dev/null && tmux has-session -t "$session_name" 2>/dev/null; then
|
|
243
|
+
tmux kill-session -t "$session_name" 2>/dev/null && info "Tmux session killed: $session_name" || warn "Tmux kill failed for $session_name"
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
# Mark as retiring / remove from registry
|
|
210
247
|
local tmp_file
|
|
211
248
|
tmp_file=$(mktemp)
|
|
212
249
|
|
|
213
250
|
jq --arg aid "$agent_id" \
|
|
214
|
-
'.agents |= map(
|
|
251
|
+
'.agents |= map(select(.id != $aid)) | .active_count = ([.agents[] | select(.status == "active")] | length) | .last_updated = "'$(now_iso)'"' \
|
|
215
252
|
"$REGISTRY_FILE" > "$tmp_file"
|
|
216
253
|
|
|
217
254
|
mv "$tmp_file" "$REGISTRY_FILE"
|
|
218
255
|
record_metric "$agent_id" "retire" "1" "graceful_shutdown"
|
|
219
256
|
|
|
220
|
-
success "
|
|
257
|
+
success "Retired agent: ${CYAN}${agent_id}${RESET}"
|
|
221
258
|
echo ""
|
|
222
259
|
}
|
|
223
260
|
|
package/scripts/sw-templates.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# ║ Templates define reusable agent team configurations (roles, layout, ║
|
|
6
6
|
# ║ focus areas) that shipwright session --template can use to scaffold teams. ║
|
|
7
7
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
8
|
-
VERSION="2.
|
|
8
|
+
VERSION="2.1.1"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|
|
@@ -20,6 +20,8 @@ DIM='\033[2m'
|
|
|
20
20
|
BOLD='\033[1m'
|
|
21
21
|
RESET='\033[0m'
|
|
22
22
|
|
|
23
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
|
+
|
|
23
25
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
24
26
|
# shellcheck source=lib/compat.sh
|
|
25
27
|
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
@@ -29,8 +31,6 @@ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
|
29
31
|
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
30
32
|
|
|
31
33
|
# ─── Template Discovery ─────────────────────────────────────────────────────
|
|
32
|
-
|
|
33
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
34
34
|
REPO_TEMPLATES_DIR="$(cd "$SCRIPT_DIR/../tmux/templates" 2>/dev/null && pwd)" || REPO_TEMPLATES_DIR=""
|
|
35
35
|
USER_TEMPLATES_DIR="${HOME}/.shipwright/templates"
|
|
36
36
|
|
|
@@ -77,7 +77,7 @@ json_agent_count() {
|
|
|
77
77
|
if command -v jq &>/dev/null; then
|
|
78
78
|
jq -r '.agents // [] | length' "$file" 2>/dev/null
|
|
79
79
|
else
|
|
80
|
-
grep -c '"name"' "$file" 2>/dev/null
|
|
80
|
+
grep -c '"name"' "$file" 2>/dev/null || echo "0"
|
|
81
81
|
fi
|
|
82
82
|
}
|
|
83
83
|
|