shipwright-cli 2.3.0 → 2.4.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 +82 -20
- package/config/policy.json +160 -2
- package/config/policy.schema.json +162 -1
- package/dashboard/public/index.html +1 -1
- package/dashboard/src/core/api.test.ts +362 -0
- package/dashboard/src/core/router.test.ts +266 -0
- package/dashboard/src/core/state.test.ts +235 -0
- package/dashboard/src/core/ws.test.ts +216 -0
- package/dashboard/src/design/icons.test.ts +105 -0
- package/dashboard/src/design/tokens.test.ts +204 -0
- package/dashboard/tsconfig.json +1 -1
- package/dashboard/vitest.config.ts +27 -0
- package/package.json +23 -4
- package/scripts/lib/pipeline-stages.sh +59 -0
- package/scripts/sw +1 -1
- 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 +230 -13
- 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 +1 -1
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +1 -1
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +1 -1
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +1 -1
- 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-doc-fleet.sh +1 -1
- package/scripts/sw-docs-agent.sh +1 -1
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +1 -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-evidence.sh +664 -0
- package/scripts/sw-feedback.sh +1 -1
- 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 +1 -1
- 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 +244 -1
- package/scripts/sw-init.sh +1 -1
- 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 +1 -1
- package/scripts/sw-memory.sh +1 -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 +1 -1
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +1 -1
- package/scripts/sw-pm.sh +1 -1
- package/scripts/sw-pr-lifecycle.sh +177 -5
- 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 +1 -1
- package/scripts/sw-quality.sh +1 -1
- package/scripts/sw-reaper.sh +1 -1
- 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 +4 -1
- package/scripts/sw-review-rerun.sh +220 -0
- package/scripts/sw-scale.sh +1 -1
- package/scripts/sw-security-audit.sh +1 -1
- package/scripts/sw-self-optimize.sh +99 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +1 -1
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +1 -1
- package/scripts/sw-stream.sh +1 -1
- package/scripts/sw-swarm.sh +1 -1
- package/scripts/sw-team-stages.sh +1 -1
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +1 -1
- package/scripts/sw-tmux-pipeline.sh +1 -1
- 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 +198 -11
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +1 -1
- package/scripts/sw-webhook.sh +1 -1
- package/scripts/sw-widgets.sh +1 -1
- package/scripts/sw-worktree.sh +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
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.4.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -206,6 +206,100 @@ optimize_analyze_outcome() {
|
|
|
206
206
|
success "Outcome recorded for issue #${issue_number:-unknown} (${result:-unknown})"
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
210
|
+
# RETRO INGEST
|
|
211
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
212
|
+
|
|
213
|
+
# optimize_ingest_retro
|
|
214
|
+
# Read most recent retro JSON, append summary to outcomes, adjust template weights
|
|
215
|
+
optimize_ingest_retro() {
|
|
216
|
+
local retros_dir="${HOME}/.shipwright/retros"
|
|
217
|
+
|
|
218
|
+
if [[ ! -d "$retros_dir" ]]; then
|
|
219
|
+
return 0
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
local latest_retro
|
|
223
|
+
latest_retro=$(ls -t "$retros_dir"/retro-*.json 2>/dev/null | head -1)
|
|
224
|
+
[[ -z "$latest_retro" || ! -f "$latest_retro" ]] && return 0
|
|
225
|
+
|
|
226
|
+
if ! command -v jq &>/dev/null; then
|
|
227
|
+
warn "jq required for retro ingest — skipping"
|
|
228
|
+
return 0
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
ensure_optimization_dir
|
|
232
|
+
|
|
233
|
+
# Extract metrics from retro JSON
|
|
234
|
+
local analysis_json
|
|
235
|
+
analysis_json=$(jq -r '.analysis // {}' "$latest_retro" 2>/dev/null || echo "{}")
|
|
236
|
+
[[ -z "$analysis_json" || "$analysis_json" == "null" ]] && return 0
|
|
237
|
+
|
|
238
|
+
local success_rate avg_duration slowest_stage quality_score retries from_date to_date
|
|
239
|
+
success_rate=$(echo "$analysis_json" | jq -r 'if .pipelines > 0 then ((.succeeded // 0) / .pipelines * 100) | floor else 0 end' 2>/dev/null || echo "0")
|
|
240
|
+
avg_duration=$(echo "$analysis_json" | jq -r '.avg_duration // 0' 2>/dev/null || echo "0")
|
|
241
|
+
slowest_stage=$(echo "$analysis_json" | jq -r '.slowest_stage // ""' 2>/dev/null || echo "")
|
|
242
|
+
quality_score=$(echo "$analysis_json" | jq -r '.quality_score // 0' 2>/dev/null || echo "0")
|
|
243
|
+
retries=$(echo "$analysis_json" | jq -r '.retries // 0' 2>/dev/null || echo "0")
|
|
244
|
+
from_date=$(jq -r '.from_date // ""' "$latest_retro" 2>/dev/null || echo "")
|
|
245
|
+
to_date=$(jq -r '.to_date // ""' "$latest_retro" 2>/dev/null || echo "")
|
|
246
|
+
|
|
247
|
+
# Append retro_summary to outcomes.jsonl
|
|
248
|
+
local retro_record
|
|
249
|
+
retro_record=$(jq -c -n \
|
|
250
|
+
--arg ts "$(now_iso)" \
|
|
251
|
+
--argjson success_rate "${success_rate:-0}" \
|
|
252
|
+
--argjson avg_duration "${avg_duration:-0}" \
|
|
253
|
+
--arg slowest_stage "${slowest_stage:-}" \
|
|
254
|
+
--argjson quality_score "${quality_score:-0}" \
|
|
255
|
+
--argjson retries "${retries:-0}" \
|
|
256
|
+
--arg from_date "${from_date:-}" \
|
|
257
|
+
--arg to_date "${to_date:-}" \
|
|
258
|
+
'{
|
|
259
|
+
ts: $ts,
|
|
260
|
+
type: "retro_summary",
|
|
261
|
+
success_rate: $success_rate,
|
|
262
|
+
avg_duration: $avg_duration,
|
|
263
|
+
slowest_stage: $slowest_stage,
|
|
264
|
+
quality_score: $quality_score,
|
|
265
|
+
retries: $retries,
|
|
266
|
+
from_date: $from_date,
|
|
267
|
+
to_date: $to_date
|
|
268
|
+
}')
|
|
269
|
+
echo "$retro_record" >> "$OUTCOMES_FILE"
|
|
270
|
+
|
|
271
|
+
# Adjust template weights when quality is low — boost templates with stronger performance
|
|
272
|
+
# (success_rate in .weights is avg weight; values > 1.0 indicate above-average success)
|
|
273
|
+
if [[ "${quality_score:-0}" -lt 70 ]] && [[ -f "$TEMPLATE_WEIGHTS_FILE" ]]; then
|
|
274
|
+
info "Quality score low (${quality_score}) — boosting templates with stronger quality gates"
|
|
275
|
+
local tmp_weights
|
|
276
|
+
tmp_weights=$(mktemp "${TEMPLATE_WEIGHTS_FILE}.tmp.XXXXXX")
|
|
277
|
+
trap "rm -f '$tmp_weights'" RETURN
|
|
278
|
+
if jq '
|
|
279
|
+
if .weights then
|
|
280
|
+
.weights |= with_entries(
|
|
281
|
+
if (.value.success_rate >= 1.2) and (.value.raw_weights != null) then
|
|
282
|
+
.value.raw_weights |= with_entries(.value = ((.value * 1.15) | if . > 2.0 then 2.0 else . end))
|
|
283
|
+
else . end
|
|
284
|
+
)
|
|
285
|
+
else . end
|
|
286
|
+
' "$TEMPLATE_WEIGHTS_FILE" > "$tmp_weights" 2>/dev/null && [[ -s "$tmp_weights" ]]; then
|
|
287
|
+
mv "$tmp_weights" "$TEMPLATE_WEIGHTS_FILE"
|
|
288
|
+
else
|
|
289
|
+
rm -f "$tmp_weights"
|
|
290
|
+
fi
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
type rotate_jsonl &>/dev/null 2>&1 && rotate_jsonl "$OUTCOMES_FILE" 10000
|
|
294
|
+
|
|
295
|
+
emit_event "optimize.retro_ingested" \
|
|
296
|
+
"success_rate=${success_rate:-0}" \
|
|
297
|
+
"quality_score=${quality_score:-0}" \
|
|
298
|
+
"slowest_stage=${slowest_stage:-}"
|
|
299
|
+
|
|
300
|
+
success "Retro ingested from $(basename "$latest_retro")"
|
|
301
|
+
}
|
|
302
|
+
|
|
209
303
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
210
304
|
# TEMPLATE TUNING
|
|
211
305
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
@@ -233,6 +327,8 @@ optimize_tune_templates() {
|
|
|
233
327
|
|
|
234
328
|
# Extract template, labels, result from each outcome line
|
|
235
329
|
while IFS= read -r line; do
|
|
330
|
+
# Skip retro_summary and other non-pipeline outcome lines
|
|
331
|
+
[[ "$(echo "$line" | jq -r '.type // ""' 2>/dev/null)" == "retro_summary" ]] && continue
|
|
236
332
|
local template result labels_str
|
|
237
333
|
template=$(echo "$line" | jq -r '.template // "unknown"' 2>/dev/null) || continue
|
|
238
334
|
result=$(echo "$line" | jq -r '.result // "unknown"' 2>/dev/null) || continue
|
|
@@ -1176,6 +1272,7 @@ show_help() {
|
|
|
1176
1272
|
echo " analyze-outcome <state-file> Analyze a completed pipeline outcome"
|
|
1177
1273
|
echo " tune Run full optimization analysis"
|
|
1178
1274
|
echo " report Show optimization report (last 7 days)"
|
|
1275
|
+
echo " ingest-retro Ingest most recent retro into optimization loop"
|
|
1179
1276
|
echo " evolve-memory Prune/strengthen/promote memory patterns"
|
|
1180
1277
|
echo " help Show this help"
|
|
1181
1278
|
echo ""
|
|
@@ -1197,6 +1294,7 @@ main() {
|
|
|
1197
1294
|
case "$cmd" in
|
|
1198
1295
|
analyze-outcome) optimize_analyze_outcome "$@" ;;
|
|
1199
1296
|
tune) optimize_full_analysis ;;
|
|
1297
|
+
ingest-retro) optimize_ingest_retro ;;
|
|
1200
1298
|
report) optimize_report ;;
|
|
1201
1299
|
evolve-memory) optimize_evolve_memory ;;
|
|
1202
1300
|
help|--help|-h) show_help ;;
|
package/scripts/sw-session.sh
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# ║ Supports --template to scaffold from a team template and --terminal ║
|
|
9
9
|
# ║ to select a terminal adapter (tmux, iterm2, wezterm). ║
|
|
10
10
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
11
|
-
VERSION="2.
|
|
11
|
+
VERSION="2.4.0"
|
|
12
12
|
set -euo pipefail
|
|
13
13
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
14
14
|
|
package/scripts/sw-setup.sh
CHANGED
package/scripts/sw-standup.sh
CHANGED
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.4.0"
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
10
10
|
|
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.4.0"
|
|
11
11
|
|
|
12
12
|
# ─── Paths (set defaults if not provided by parent) ──────────────────────────
|
|
13
13
|
SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
|
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.4.0"
|
|
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
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.4.0"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|
package/scripts/sw-testgen.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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.4.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Handle subcommands ───────────────────────────────────────────────────────
|
package/scripts/sw-tmux.sh
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# ║ shipwright tmux fix — Auto-fix common issues ║
|
|
12
12
|
# ║ shipwright tmux reload — Reload tmux config ║
|
|
13
13
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
14
|
-
VERSION="2.
|
|
14
|
+
VERSION="2.4.0"
|
|
15
15
|
set -euo pipefail
|
|
16
16
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
17
17
|
|
package/scripts/sw-trace.sh
CHANGED
package/scripts/sw-tracker.sh
CHANGED
package/scripts/sw-triage.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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.4.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -165,6 +165,125 @@ analyze_effort() {
|
|
|
165
165
|
esac
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
# analyze_with_ai <title> <body>
|
|
169
|
+
# AI-driven triage via intelligence engine. Returns JSON or empty on failure.
|
|
170
|
+
# Schema: {type, complexity, risk, effort, labels[], summary}
|
|
171
|
+
# Always falls back to keyword-based analysis when AI unavailable.
|
|
172
|
+
analyze_with_ai() {
|
|
173
|
+
local title="$1"
|
|
174
|
+
local body="$2"
|
|
175
|
+
local combined="${title} ${body}"
|
|
176
|
+
|
|
177
|
+
# Check sw-intelligence.sh is sourceable and claude CLI exists
|
|
178
|
+
if [[ ! -f "${SCRIPT_DIR}/sw-intelligence.sh" ]]; then
|
|
179
|
+
return 1
|
|
180
|
+
fi
|
|
181
|
+
if ! command -v claude &>/dev/null; then
|
|
182
|
+
return 1
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# Source intelligence (provides _intelligence_call_claude, compute_md5)
|
|
186
|
+
if ! source "${SCRIPT_DIR}/sw-intelligence.sh" 2>/dev/null; then
|
|
187
|
+
return 1
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
local prompt
|
|
191
|
+
prompt="Analyze this GitHub issue and return ONLY a valid JSON object (no markdown, no explanation).
|
|
192
|
+
|
|
193
|
+
Title: ${title}
|
|
194
|
+
|
|
195
|
+
Body: ${body}
|
|
196
|
+
|
|
197
|
+
Return JSON with exactly these fields:
|
|
198
|
+
{
|
|
199
|
+
\"type\": \"<bug|feature|security|performance|refactor|docs|chore>\",
|
|
200
|
+
\"complexity\": \"<trivial|simple|moderate|complex|epic>\",
|
|
201
|
+
\"risk\": \"<low|medium|high|critical>\",
|
|
202
|
+
\"effort\": \"<xs|s|m|l|xl>\",
|
|
203
|
+
\"labels\": [\"type:X\", \"complexity:X\", \"risk:X\", \"priority:X\", \"effort:X\"],
|
|
204
|
+
\"summary\": \"<brief one-line summary>\"
|
|
205
|
+
}"
|
|
206
|
+
|
|
207
|
+
local cache_key
|
|
208
|
+
cache_key="triage_analyze_$(compute_md5 --string "$combined" 2>/dev/null || echo "$(echo "$combined" | md5 2>/dev/null | cut -c1-16)")"
|
|
209
|
+
|
|
210
|
+
local result
|
|
211
|
+
result=$(_intelligence_call_claude "$prompt" "$cache_key" 2>/dev/null) || true
|
|
212
|
+
|
|
213
|
+
if [[ -z "$result" ]] || echo "$result" | jq -e '.' &>/dev/null; then
|
|
214
|
+
: # result is empty or valid JSON
|
|
215
|
+
else
|
|
216
|
+
return 1
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
# Validate required fields and normalize
|
|
220
|
+
local type_val complexity_val risk_val effort_val labels_val
|
|
221
|
+
type_val=$(echo "$result" | jq -r '.type // empty' 2>/dev/null)
|
|
222
|
+
complexity_val=$(echo "$result" | jq -r '.complexity // empty' 2>/dev/null)
|
|
223
|
+
risk_val=$(echo "$result" | jq -r '.risk // empty' 2>/dev/null)
|
|
224
|
+
effort_val=$(echo "$result" | jq -r '.effort // empty' 2>/dev/null)
|
|
225
|
+
labels_val=$(echo "$result" | jq -r '.labels // []' 2>/dev/null)
|
|
226
|
+
|
|
227
|
+
# Reject if we got an error object
|
|
228
|
+
if echo "$result" | jq -e '.error' &>/dev/null; then
|
|
229
|
+
return 1
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
# Need at least type or complexity to consider AI result useful
|
|
233
|
+
if [[ -z "$type_val" && -z "$complexity_val" && -z "$risk_val" ]]; then
|
|
234
|
+
return 1
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
# Normalize to valid triage schema values
|
|
238
|
+
local valid_type
|
|
239
|
+
case "$(echo "$type_val" | tr '[:upper:]' '[:lower:]')" in
|
|
240
|
+
bug) valid_type="bug" ;;
|
|
241
|
+
feature) valid_type="feature" ;;
|
|
242
|
+
security) valid_type="security" ;;
|
|
243
|
+
performance) valid_type="performance" ;;
|
|
244
|
+
refactor) valid_type="refactor" ;;
|
|
245
|
+
docs) valid_type="docs" ;;
|
|
246
|
+
chore) valid_type="chore" ;;
|
|
247
|
+
*) valid_type="" ;;
|
|
248
|
+
esac
|
|
249
|
+
local valid_complexity
|
|
250
|
+
case "$(echo "$complexity_val" | tr '[:upper:]' '[:lower:]')" in
|
|
251
|
+
trivial) valid_complexity="trivial" ;;
|
|
252
|
+
simple) valid_complexity="simple" ;;
|
|
253
|
+
moderate) valid_complexity="moderate" ;;
|
|
254
|
+
complex) valid_complexity="complex" ;;
|
|
255
|
+
epic) valid_complexity="epic" ;;
|
|
256
|
+
*) valid_complexity="" ;;
|
|
257
|
+
esac
|
|
258
|
+
local valid_risk
|
|
259
|
+
case "$(echo "$risk_val" | tr '[:upper:]' '[:lower:]')" in
|
|
260
|
+
low) valid_risk="low" ;;
|
|
261
|
+
medium) valid_risk="medium" ;;
|
|
262
|
+
high) valid_risk="high" ;;
|
|
263
|
+
critical) valid_risk="critical" ;;
|
|
264
|
+
*) valid_risk="" ;;
|
|
265
|
+
esac
|
|
266
|
+
local valid_effort
|
|
267
|
+
case "$(echo "$effort_val" | tr '[:upper:]' '[:lower:]')" in
|
|
268
|
+
xs) valid_effort="xs" ;;
|
|
269
|
+
s) valid_effort="s" ;;
|
|
270
|
+
m) valid_effort="m" ;;
|
|
271
|
+
l) valid_effort="l" ;;
|
|
272
|
+
xl) valid_effort="xl" ;;
|
|
273
|
+
*) valid_effort="" ;;
|
|
274
|
+
esac
|
|
275
|
+
|
|
276
|
+
jq -n \
|
|
277
|
+
--arg type "${valid_type:-}" \
|
|
278
|
+
--arg complexity "${valid_complexity:-}" \
|
|
279
|
+
--arg risk "${valid_risk:-}" \
|
|
280
|
+
--arg effort "${valid_effort:-}" \
|
|
281
|
+
--argjson labels "${labels_val:-[]}" \
|
|
282
|
+
--arg summary "$(echo "$result" | jq -r '.summary // ""' 2>/dev/null)" \
|
|
283
|
+
'{type: $type, complexity: $complexity, risk: $risk, effort: $effort, labels: $labels, summary: $summary}'
|
|
284
|
+
return 0
|
|
285
|
+
}
|
|
286
|
+
|
|
168
287
|
# suggest_labels <type> <complexity> <risk> <effort>
|
|
169
288
|
# Generates label recommendations
|
|
170
289
|
suggest_labels() {
|
|
@@ -199,9 +318,37 @@ suggest_labels() {
|
|
|
199
318
|
|
|
200
319
|
# ─── Subcommand: analyze ──────────────────────────────────────────────────
|
|
201
320
|
|
|
321
|
+
# Check if AI triage should be used (TRIAGE_AI env, --ai flag, or daemon-config)
|
|
322
|
+
_triage_use_ai() {
|
|
323
|
+
if [[ "${TRIAGE_AI:-}" == "1" || "${TRIAGE_AI:-}" == "true" ]]; then
|
|
324
|
+
return 0
|
|
325
|
+
fi
|
|
326
|
+
local config="${REPO_DIR}/.claude/daemon-config.json"
|
|
327
|
+
if [[ -f "$config" ]]; then
|
|
328
|
+
local enabled
|
|
329
|
+
enabled=$(jq -r '.intelligence.enabled // false' "$config" 2>/dev/null || echo "false")
|
|
330
|
+
[[ "$enabled" == "true" ]]
|
|
331
|
+
else
|
|
332
|
+
return 1
|
|
333
|
+
fi
|
|
334
|
+
}
|
|
335
|
+
|
|
202
336
|
cmd_analyze() {
|
|
203
|
-
local issue="
|
|
204
|
-
|
|
337
|
+
local issue=""
|
|
338
|
+
local use_ai=false
|
|
339
|
+
|
|
340
|
+
# Parse args for --ai and issue number
|
|
341
|
+
while [[ $# -gt 0 ]]; do
|
|
342
|
+
case "$1" in
|
|
343
|
+
--ai) use_ai=true; shift ;;
|
|
344
|
+
*) [[ -z "$issue" ]] && issue="$1"; shift ;;
|
|
345
|
+
esac
|
|
346
|
+
done
|
|
347
|
+
|
|
348
|
+
[[ -z "$issue" ]] && { error "Usage: triage analyze [--ai] <issue>"; exit 1; }
|
|
349
|
+
|
|
350
|
+
# Enable AI if --ai flag or config
|
|
351
|
+
_triage_use_ai && use_ai=true
|
|
205
352
|
|
|
206
353
|
check_gh
|
|
207
354
|
|
|
@@ -223,13 +370,45 @@ cmd_analyze() {
|
|
|
223
370
|
|
|
224
371
|
local combined_text="${title} ${body}"
|
|
225
372
|
|
|
226
|
-
#
|
|
227
|
-
local
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
373
|
+
# Keyword-based analysis (always run for fallback)
|
|
374
|
+
local kw_type kw_complexity kw_risk kw_effort kw_labels
|
|
375
|
+
kw_type=$(analyze_type "$combined_text")
|
|
376
|
+
kw_complexity=$(analyze_complexity "$combined_text")
|
|
377
|
+
kw_risk=$(analyze_risk "$combined_text")
|
|
378
|
+
kw_effort=$(analyze_effort "$kw_complexity" "$kw_risk")
|
|
379
|
+
kw_labels=$(suggest_labels "$kw_type" "$kw_complexity" "$kw_risk" "$kw_effort")
|
|
380
|
+
|
|
381
|
+
# Try AI analysis first when enabled
|
|
382
|
+
local type="$kw_type" complexity="$kw_complexity" risk="$kw_risk" effort="$kw_effort" labels="$kw_labels"
|
|
383
|
+
if $use_ai; then
|
|
384
|
+
local ai_result
|
|
385
|
+
if ai_result=$(analyze_with_ai "$title" "$body" 2>/dev/null); then
|
|
386
|
+
local ai_type ai_complexity ai_risk ai_effort ai_labels
|
|
387
|
+
ai_type=$(echo "$ai_result" | jq -r '.type // empty')
|
|
388
|
+
ai_complexity=$(echo "$ai_result" | jq -r '.complexity // empty')
|
|
389
|
+
ai_risk=$(echo "$ai_result" | jq -r '.risk // empty')
|
|
390
|
+
ai_effort=$(echo "$ai_result" | jq -r '.effort // empty')
|
|
391
|
+
ai_labels=$(echo "$ai_result" | jq -r '.labels // []' 2>/dev/null)
|
|
392
|
+
|
|
393
|
+
# Merge: AI takes precedence where available
|
|
394
|
+
[[ -n "$ai_type" ]] && type="$ai_type"
|
|
395
|
+
[[ -n "$ai_complexity" ]] && complexity="$ai_complexity"
|
|
396
|
+
[[ -n "$ai_risk" ]] && risk="$ai_risk"
|
|
397
|
+
if [[ -n "$ai_effort" ]]; then
|
|
398
|
+
effort="$ai_effort"
|
|
399
|
+
else
|
|
400
|
+
effort=$(analyze_effort "$complexity" "$risk")
|
|
401
|
+
fi
|
|
402
|
+
if [[ -n "$ai_labels" && "$ai_labels" != "[]" ]]; then
|
|
403
|
+
labels=$(echo "$ai_labels" | jq -r 'join(" ")')
|
|
404
|
+
else
|
|
405
|
+
labels=$(suggest_labels "$type" "$complexity" "$risk" "$effort")
|
|
406
|
+
fi
|
|
407
|
+
info "AI triage applied"
|
|
408
|
+
else
|
|
409
|
+
warn "AI triage unavailable, using keyword analysis"
|
|
410
|
+
fi
|
|
411
|
+
fi
|
|
233
412
|
|
|
234
413
|
# Output as structured JSON
|
|
235
414
|
cat << EOF
|
|
@@ -575,7 +754,7 @@ cmd_help() {
|
|
|
575
754
|
echo -e " ${CYAN}shipwright triage${RESET} <subcommand> [options]"
|
|
576
755
|
echo ""
|
|
577
756
|
echo -e "${BOLD}SUBCOMMANDS${RESET}"
|
|
578
|
-
echo -e " ${CYAN}analyze <issue>${RESET}
|
|
757
|
+
echo -e " ${CYAN}analyze [--ai] <issue>${RESET} Analyze issue and suggest labels (outputs JSON)"
|
|
579
758
|
echo -e " ${CYAN}label <issue>${RESET} Apply suggested labels to issue"
|
|
580
759
|
echo -e " ${CYAN}prioritize${RESET} Score and rank all open issues by priority"
|
|
581
760
|
echo -e " ${CYAN}team <issue>${RESET} Recommend team size & pipeline template"
|
|
@@ -585,6 +764,8 @@ cmd_help() {
|
|
|
585
764
|
echo ""
|
|
586
765
|
echo -e "${BOLD}EXAMPLES${RESET}"
|
|
587
766
|
echo -e " ${DIM}shipwright triage analyze 42${RESET}"
|
|
767
|
+
echo -e " ${DIM}shipwright triage analyze --ai 42${RESET}"
|
|
768
|
+
echo -e " ${DIM}shipwright triage --ai analyze 42${RESET}"
|
|
588
769
|
echo -e " ${DIM}shipwright triage label 42${RESET}"
|
|
589
770
|
echo -e " ${DIM}shipwright triage prioritize${RESET}"
|
|
590
771
|
echo -e " ${DIM}shipwright triage team 42${RESET}"
|
|
@@ -596,6 +777,12 @@ cmd_help() {
|
|
|
596
777
|
# ─── Main ──────────────────────────────────────────────────────────────────
|
|
597
778
|
|
|
598
779
|
main() {
|
|
780
|
+
# Parse global --ai flag (enables AI triage for this invocation)
|
|
781
|
+
if [[ "${1:-}" == "--ai" ]]; then
|
|
782
|
+
export TRIAGE_AI=1
|
|
783
|
+
shift
|
|
784
|
+
fi
|
|
785
|
+
|
|
599
786
|
local cmd="${1:-help}"
|
|
600
787
|
shift 2>/dev/null || true
|
|
601
788
|
|
package/scripts/sw-upgrade.sh
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
3
|
# ║ sw upgrade — Detect and apply updates from the repo ║
|
|
4
4
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
5
|
-
VERSION="2.
|
|
5
|
+
VERSION="2.4.0"
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
package/scripts/sw-ux.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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.4.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
package/scripts/sw-webhook.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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.4.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
package/scripts/sw-widgets.sh
CHANGED
package/scripts/sw-worktree.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# ║ Each agent gets its own worktree so parallel agents don't clobber ║
|
|
6
6
|
# ║ each other's files. Worktrees live in .worktrees/ relative to root. ║
|
|
7
7
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
8
|
-
VERSION="2.
|
|
8
|
+
VERSION="2.4.0"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|