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-pipeline.sh
CHANGED
|
@@ -11,7 +11,7 @@ unset CLAUDECODE 2>/dev/null || true
|
|
|
11
11
|
# Ignore SIGHUP so tmux attach/detach doesn't kill long-running plan/design/review stages
|
|
12
12
|
trap '' HUP
|
|
13
13
|
|
|
14
|
-
VERSION="2.
|
|
14
|
+
VERSION="2.1.1"
|
|
15
15
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
16
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
17
17
|
|
|
@@ -202,7 +202,9 @@ PIPELINE_CONFIG=""
|
|
|
202
202
|
TEST_CMD=""
|
|
203
203
|
MODEL=""
|
|
204
204
|
AGENTS=""
|
|
205
|
+
PIPELINE_AGENT_ID="${PIPELINE_AGENT_ID:-pipeline-$$}"
|
|
205
206
|
SKIP_GATES=false
|
|
207
|
+
HEADLESS=false
|
|
206
208
|
GIT_BRANCH=""
|
|
207
209
|
GITHUB_ISSUE=""
|
|
208
210
|
TASK_TYPE=""
|
|
@@ -223,7 +225,9 @@ AUTO_WORKTREE=false
|
|
|
223
225
|
WORKTREE_NAME=""
|
|
224
226
|
CLEANUP_WORKTREE=false
|
|
225
227
|
ORIGINAL_REPO_DIR=""
|
|
228
|
+
REPO_OVERRIDE=""
|
|
226
229
|
_cleanup_done=""
|
|
230
|
+
PIPELINE_EXIT_CODE=1 # assume failure until run_pipeline succeeds
|
|
227
231
|
|
|
228
232
|
# GitHub metadata (populated during intake)
|
|
229
233
|
ISSUE_LABELS=""
|
|
@@ -264,11 +268,14 @@ show_help() {
|
|
|
264
268
|
echo -e "${BOLD}START OPTIONS${RESET}"
|
|
265
269
|
echo -e " ${DIM}--goal \"description\"${RESET} What to build (required unless --issue)"
|
|
266
270
|
echo -e " ${DIM}--issue <number>${RESET} Fetch goal from GitHub issue"
|
|
271
|
+
echo -e " ${DIM}--repo <path>${RESET} Change to directory before running (must be a git repo)"
|
|
272
|
+
echo -e " ${DIM}--local${RESET} Alias for --no-github --no-github-label (local-only mode)"
|
|
267
273
|
echo -e " ${DIM}--pipeline <name>${RESET} Pipeline template (default: standard)"
|
|
268
274
|
echo -e " ${DIM}--test-cmd \"command\"${RESET} Override test command (auto-detected if omitted)"
|
|
269
275
|
echo -e " ${DIM}--model <model>${RESET} Override AI model (opus, sonnet, haiku)"
|
|
270
276
|
echo -e " ${DIM}--agents <n>${RESET} Override agent count"
|
|
271
277
|
echo -e " ${DIM}--skip-gates${RESET} Auto-approve all gates (fully autonomous)"
|
|
278
|
+
echo -e " ${DIM}--headless${RESET} Full headless mode (skip gates, no prompts)"
|
|
272
279
|
echo -e " ${DIM}--base <branch>${RESET} Base branch for PR (default: main)"
|
|
273
280
|
echo -e " ${DIM}--reviewers \"a,b\"${RESET} Request PR reviewers (auto-detected if omitted)"
|
|
274
281
|
echo -e " ${DIM}--labels \"a,b\"${RESET} Add labels to PR (inherited from issue if omitted)"
|
|
@@ -346,11 +353,14 @@ parse_args() {
|
|
|
346
353
|
case "$1" in
|
|
347
354
|
--goal) GOAL="$2"; shift 2 ;;
|
|
348
355
|
--issue) ISSUE_NUMBER="$2"; shift 2 ;;
|
|
356
|
+
--repo) REPO_OVERRIDE="$2"; shift 2 ;;
|
|
357
|
+
--local) NO_GITHUB=true; NO_GITHUB_LABEL=true; shift ;;
|
|
349
358
|
--pipeline|--template) PIPELINE_NAME="$2"; shift 2 ;;
|
|
350
359
|
--test-cmd) TEST_CMD="$2"; shift 2 ;;
|
|
351
360
|
--model) MODEL="$2"; shift 2 ;;
|
|
352
361
|
--agents) AGENTS="$2"; shift 2 ;;
|
|
353
362
|
--skip-gates) SKIP_GATES=true; shift ;;
|
|
363
|
+
--headless) HEADLESS=true; SKIP_GATES=true; shift ;;
|
|
354
364
|
--base) BASE_BRANCH="$2"; shift 2 ;;
|
|
355
365
|
--reviewers) REVIEWERS="$2"; shift 2 ;;
|
|
356
366
|
--labels) LABELS="$2"; shift 2 ;;
|
|
@@ -387,6 +397,20 @@ parse_args() {
|
|
|
387
397
|
PIPELINE_NAME_ARG=""
|
|
388
398
|
parse_args "$@"
|
|
389
399
|
|
|
400
|
+
# ─── Non-Interactive Detection ──────────────────────────────────────────────
|
|
401
|
+
# When stdin is not a terminal (background, pipe, nohup, tmux send-keys),
|
|
402
|
+
# auto-enable headless mode to prevent read prompts from killing the script.
|
|
403
|
+
if [[ ! -t 0 ]]; then
|
|
404
|
+
HEADLESS=true
|
|
405
|
+
if [[ "$SKIP_GATES" != "true" ]]; then
|
|
406
|
+
SKIP_GATES=true
|
|
407
|
+
fi
|
|
408
|
+
fi
|
|
409
|
+
# --worktree implies headless when stdin is not a terminal
|
|
410
|
+
if [[ "$AUTO_WORKTREE" == "true" && "$SKIP_GATES" != "true" && ! -t 0 ]]; then
|
|
411
|
+
SKIP_GATES=true
|
|
412
|
+
fi
|
|
413
|
+
|
|
390
414
|
# ─── Directory Setup ────────────────────────────────────────────────────────
|
|
391
415
|
|
|
392
416
|
setup_dirs() {
|
|
@@ -396,6 +420,7 @@ setup_dirs() {
|
|
|
396
420
|
ARTIFACTS_DIR="$STATE_DIR/pipeline-artifacts"
|
|
397
421
|
TASKS_FILE="$STATE_DIR/pipeline-tasks.md"
|
|
398
422
|
mkdir -p "$STATE_DIR" "$ARTIFACTS_DIR"
|
|
423
|
+
export SHIPWRIGHT_PIPELINE_ID="pipeline-$$-${ISSUE_NUMBER:-0}"
|
|
399
424
|
}
|
|
400
425
|
|
|
401
426
|
# ─── Pipeline Config Loading ───────────────────────────────────────────────
|
|
@@ -404,10 +429,11 @@ find_pipeline_config() {
|
|
|
404
429
|
local name="$1"
|
|
405
430
|
local locations=(
|
|
406
431
|
"$REPO_DIR/templates/pipelines/${name}.json"
|
|
432
|
+
"${PROJECT_ROOT:-}/templates/pipelines/${name}.json"
|
|
407
433
|
"$HOME/.shipwright/pipelines/${name}.json"
|
|
408
434
|
)
|
|
409
435
|
for loc in "${locations[@]}"; do
|
|
410
|
-
if [[ -f "$loc" ]]; then
|
|
436
|
+
if [[ -n "$loc" && -f "$loc" ]]; then
|
|
411
437
|
echo "$loc"
|
|
412
438
|
return 0
|
|
413
439
|
fi
|
|
@@ -1155,6 +1181,21 @@ get_stage_timing() {
|
|
|
1155
1181
|
fi
|
|
1156
1182
|
}
|
|
1157
1183
|
|
|
1184
|
+
# Raw seconds for a stage (for memory baseline updates)
|
|
1185
|
+
get_stage_timing_seconds() {
|
|
1186
|
+
local stage_id="$1"
|
|
1187
|
+
local start_e end_e
|
|
1188
|
+
start_e=$(echo "$STAGE_TIMINGS" | grep "^${stage_id}_start:" | cut -d: -f2 | tail -1 || true)
|
|
1189
|
+
end_e=$(echo "$STAGE_TIMINGS" | grep "^${stage_id}_end:" | cut -d: -f2 | tail -1 || true)
|
|
1190
|
+
if [[ -n "$start_e" && -n "$end_e" ]]; then
|
|
1191
|
+
echo $(( end_e - start_e ))
|
|
1192
|
+
elif [[ -n "$start_e" ]]; then
|
|
1193
|
+
echo $(( $(now_epoch) - start_e ))
|
|
1194
|
+
else
|
|
1195
|
+
echo "0"
|
|
1196
|
+
fi
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1158
1199
|
get_stage_description() {
|
|
1159
1200
|
local stage_id="$1"
|
|
1160
1201
|
|
|
@@ -1250,6 +1291,22 @@ mark_stage_complete() {
|
|
|
1250
1291
|
log_stage "$stage_id" "complete (${timing})"
|
|
1251
1292
|
write_state
|
|
1252
1293
|
|
|
1294
|
+
record_stage_effectiveness "$stage_id" "complete"
|
|
1295
|
+
# Update memory baselines and predictive baselines for stage durations
|
|
1296
|
+
if [[ "$stage_id" == "test" || "$stage_id" == "build" ]]; then
|
|
1297
|
+
local secs
|
|
1298
|
+
secs=$(get_stage_timing_seconds "$stage_id")
|
|
1299
|
+
if [[ -n "$secs" && "$secs" != "0" ]]; then
|
|
1300
|
+
[[ -x "$SCRIPT_DIR/sw-memory.sh" ]] && bash "$SCRIPT_DIR/sw-memory.sh" metric "${stage_id}_duration_s" "$secs" 2>/dev/null || true
|
|
1301
|
+
if [[ -x "$SCRIPT_DIR/sw-predictive.sh" ]]; then
|
|
1302
|
+
local anomaly_sev
|
|
1303
|
+
anomaly_sev=$(bash "$SCRIPT_DIR/sw-predictive.sh" anomaly "$stage_id" "duration_s" "$secs" 2>/dev/null || echo "normal")
|
|
1304
|
+
[[ "$anomaly_sev" == "critical" || "$anomaly_sev" == "warning" ]] && emit_event "pipeline.anomaly" "stage=$stage_id" "metric=duration_s" "value=$secs" "severity=$anomaly_sev" 2>/dev/null || true
|
|
1305
|
+
bash "$SCRIPT_DIR/sw-predictive.sh" baseline "$stage_id" "duration_s" "$secs" 2>/dev/null || true
|
|
1306
|
+
fi
|
|
1307
|
+
fi
|
|
1308
|
+
fi
|
|
1309
|
+
|
|
1253
1310
|
# Update GitHub progress comment
|
|
1254
1311
|
if [[ -n "$ISSUE_NUMBER" ]]; then
|
|
1255
1312
|
local body
|
|
@@ -1346,9 +1403,40 @@ verify_stage_artifacts() {
|
|
|
1346
1403
|
return "$missing"
|
|
1347
1404
|
}
|
|
1348
1405
|
|
|
1406
|
+
# Self-aware pipeline: record stage effectiveness for meta-cognition
|
|
1407
|
+
STAGE_EFFECTIVENESS_FILE="${HOME}/.shipwright/stage-effectiveness.jsonl"
|
|
1408
|
+
record_stage_effectiveness() {
|
|
1409
|
+
local stage_id="$1" outcome="${2:-failed}"
|
|
1410
|
+
mkdir -p "${HOME}/.shipwright"
|
|
1411
|
+
echo "{\"stage\":\"$stage_id\",\"outcome\":\"$outcome\",\"ts\":\"$(now_iso)\"}" >> "${STAGE_EFFECTIVENESS_FILE}"
|
|
1412
|
+
# Keep last 100 entries
|
|
1413
|
+
tail -100 "${STAGE_EFFECTIVENESS_FILE}" > "${STAGE_EFFECTIVENESS_FILE}.tmp" 2>/dev/null && mv "${STAGE_EFFECTIVENESS_FILE}.tmp" "${STAGE_EFFECTIVENESS_FILE}" 2>/dev/null || true
|
|
1414
|
+
}
|
|
1415
|
+
get_stage_self_awareness_hint() {
|
|
1416
|
+
local stage_id="$1"
|
|
1417
|
+
[[ ! -f "$STAGE_EFFECTIVENESS_FILE" ]] && return 0
|
|
1418
|
+
local recent
|
|
1419
|
+
recent=$(grep "\"stage\":\"$stage_id\"" "$STAGE_EFFECTIVENESS_FILE" 2>/dev/null | tail -10 || true)
|
|
1420
|
+
[[ -z "$recent" ]] && return 0
|
|
1421
|
+
local failures=0 total=0
|
|
1422
|
+
while IFS= read -r line; do
|
|
1423
|
+
[[ -z "$line" ]] && continue
|
|
1424
|
+
total=$((total + 1))
|
|
1425
|
+
echo "$line" | grep -q '"outcome":"failed"' && failures=$((failures + 1)) || true
|
|
1426
|
+
done <<< "$recent"
|
|
1427
|
+
if [[ "$total" -ge 3 ]] && [[ $((failures * 100 / total)) -ge 50 ]]; then
|
|
1428
|
+
case "$stage_id" in
|
|
1429
|
+
plan) echo "Recent plan stage failures: consider adding more context or breaking the goal into smaller steps." ;;
|
|
1430
|
+
build) echo "Recent build stage failures: consider adding test expectations or simplifying the change." ;;
|
|
1431
|
+
*) echo "Recent $stage_id failures: review past logs and adjust approach." ;;
|
|
1432
|
+
esac
|
|
1433
|
+
fi
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1349
1436
|
mark_stage_failed() {
|
|
1350
1437
|
local stage_id="$1"
|
|
1351
1438
|
record_stage_end "$stage_id"
|
|
1439
|
+
record_stage_effectiveness "$stage_id" "failed"
|
|
1352
1440
|
set_stage_status "$stage_id" "failed"
|
|
1353
1441
|
local timing
|
|
1354
1442
|
timing=$(get_stage_timing "$stage_id")
|
|
@@ -1779,6 +1867,28 @@ ${memory_summary}
|
|
|
1779
1867
|
fi
|
|
1780
1868
|
fi
|
|
1781
1869
|
|
|
1870
|
+
# Self-aware pipeline: inject hint when plan stage has been failing recently
|
|
1871
|
+
local plan_hint
|
|
1872
|
+
plan_hint=$(get_stage_self_awareness_hint "plan" 2>/dev/null || true)
|
|
1873
|
+
if [[ -n "$plan_hint" ]]; then
|
|
1874
|
+
plan_prompt="${plan_prompt}
|
|
1875
|
+
## Self-Assessment (recent plan stage performance)
|
|
1876
|
+
${plan_hint}
|
|
1877
|
+
"
|
|
1878
|
+
fi
|
|
1879
|
+
|
|
1880
|
+
# Inject cross-pipeline discoveries (from other concurrent/similar pipelines)
|
|
1881
|
+
if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
|
|
1882
|
+
local plan_discoveries
|
|
1883
|
+
plan_discoveries=$("$SCRIPT_DIR/sw-discovery.sh" inject "*.md,*.json" 2>/dev/null | head -20 || true)
|
|
1884
|
+
if [[ -n "$plan_discoveries" ]]; then
|
|
1885
|
+
plan_prompt="${plan_prompt}
|
|
1886
|
+
## Discoveries from Other Pipelines
|
|
1887
|
+
${plan_discoveries}
|
|
1888
|
+
"
|
|
1889
|
+
fi
|
|
1890
|
+
fi
|
|
1891
|
+
|
|
1782
1892
|
# Inject architecture patterns from intelligence layer
|
|
1783
1893
|
local repo_hash_plan
|
|
1784
1894
|
repo_hash_plan=$(echo -n "$PROJECT_ROOT" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
|
|
@@ -2151,6 +2261,12 @@ stage_design() {
|
|
|
2151
2261
|
memory_context=$(bash "$SCRIPT_DIR/sw-memory.sh" inject "design" 2>/dev/null) || true
|
|
2152
2262
|
fi
|
|
2153
2263
|
|
|
2264
|
+
# Inject cross-pipeline discoveries for design stage
|
|
2265
|
+
local design_discoveries=""
|
|
2266
|
+
if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
|
|
2267
|
+
design_discoveries=$("$SCRIPT_DIR/sw-discovery.sh" inject "*.md,*.ts,*.tsx,*.js" 2>/dev/null | head -20 || true)
|
|
2268
|
+
fi
|
|
2269
|
+
|
|
2154
2270
|
# Inject architecture model patterns if available
|
|
2155
2271
|
local arch_context=""
|
|
2156
2272
|
local repo_hash
|
|
@@ -2210,7 +2326,10 @@ ${memory_context}
|
|
|
2210
2326
|
}${arch_context:+
|
|
2211
2327
|
## Architecture Model (from previous designs)
|
|
2212
2328
|
${arch_context}
|
|
2213
|
-
}${design_antipatterns}
|
|
2329
|
+
}${design_antipatterns}${design_discoveries:+
|
|
2330
|
+
## Discoveries from Other Pipelines
|
|
2331
|
+
${design_discoveries}
|
|
2332
|
+
}
|
|
2214
2333
|
## Required Output — Architecture Decision Record
|
|
2215
2334
|
|
|
2216
2335
|
Produce this EXACT format:
|
|
@@ -2334,6 +2453,18 @@ Historical context (lessons from previous pipelines):
|
|
|
2334
2453
|
${memory_context}"
|
|
2335
2454
|
fi
|
|
2336
2455
|
|
|
2456
|
+
# Inject cross-pipeline discoveries for build stage
|
|
2457
|
+
if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
|
|
2458
|
+
local build_discoveries
|
|
2459
|
+
build_discoveries=$("$SCRIPT_DIR/sw-discovery.sh" inject "src/*,*.ts,*.tsx,*.js" 2>/dev/null | head -20 || true)
|
|
2460
|
+
if [[ -n "$build_discoveries" ]]; then
|
|
2461
|
+
enriched_goal="${enriched_goal}
|
|
2462
|
+
|
|
2463
|
+
Discoveries from other pipelines:
|
|
2464
|
+
${build_discoveries}"
|
|
2465
|
+
fi
|
|
2466
|
+
fi
|
|
2467
|
+
|
|
2337
2468
|
# Add task list context
|
|
2338
2469
|
if [[ -s "$TASKS_FILE" ]]; then
|
|
2339
2470
|
enriched_goal="${enriched_goal}
|
|
@@ -2380,6 +2511,19 @@ Coverage baseline: ${coverage_baseline}% — do not decrease coverage."
|
|
|
2380
2511
|
fi
|
|
2381
2512
|
fi
|
|
2382
2513
|
|
|
2514
|
+
# Predictive: inject prevention hints when risk/memory patterns suggest build-stage failures
|
|
2515
|
+
if [[ -x "$SCRIPT_DIR/sw-predictive.sh" ]]; then
|
|
2516
|
+
local issue_json_build="{}"
|
|
2517
|
+
[[ -n "${ISSUE_NUMBER:-}" ]] && issue_json_build=$(jq -n --arg title "${GOAL:-}" --arg num "${ISSUE_NUMBER:-}" '{title: $title, number: $num}')
|
|
2518
|
+
local prevention_text
|
|
2519
|
+
prevention_text=$(bash "$SCRIPT_DIR/sw-predictive.sh" inject-prevention "build" "$issue_json_build" 2>/dev/null || true)
|
|
2520
|
+
if [[ -n "$prevention_text" ]]; then
|
|
2521
|
+
enriched_goal="${enriched_goal}
|
|
2522
|
+
|
|
2523
|
+
${prevention_text}"
|
|
2524
|
+
fi
|
|
2525
|
+
fi
|
|
2526
|
+
|
|
2383
2527
|
loop_args+=("$enriched_goal")
|
|
2384
2528
|
|
|
2385
2529
|
# Build loop args from pipeline config + CLI overrides
|
|
@@ -2432,6 +2576,23 @@ Coverage baseline: ${coverage_baseline}% — do not decrease coverage."
|
|
|
2432
2576
|
build_model="$CLAUDE_MODEL"
|
|
2433
2577
|
fi
|
|
2434
2578
|
|
|
2579
|
+
# Recruit-powered model selection (when no explicit override)
|
|
2580
|
+
if [[ -z "$MODEL" ]] && [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
|
|
2581
|
+
local _recruit_goal="${GOAL:-}"
|
|
2582
|
+
if [[ -n "$_recruit_goal" ]]; then
|
|
2583
|
+
local _recruit_match
|
|
2584
|
+
_recruit_match=$(bash "$SCRIPT_DIR/sw-recruit.sh" match --json "$_recruit_goal" 2>/dev/null) || true
|
|
2585
|
+
if [[ -n "$_recruit_match" ]]; then
|
|
2586
|
+
local _recruit_model
|
|
2587
|
+
_recruit_model=$(echo "$_recruit_match" | jq -r '.model // ""' 2>/dev/null) || true
|
|
2588
|
+
if [[ -n "$_recruit_model" && "$_recruit_model" != "null" && "$_recruit_model" != "" ]]; then
|
|
2589
|
+
info "Recruit recommends model: ${CYAN}${_recruit_model}${RESET} for this task"
|
|
2590
|
+
build_model="$_recruit_model"
|
|
2591
|
+
fi
|
|
2592
|
+
fi
|
|
2593
|
+
fi
|
|
2594
|
+
fi
|
|
2595
|
+
|
|
2435
2596
|
[[ -n "$test_cmd" && "$test_cmd" != "null" ]] && loop_args+=(--test-cmd "$test_cmd")
|
|
2436
2597
|
loop_args+=(--max-iterations "$max_iter")
|
|
2437
2598
|
loop_args+=(--model "$build_model")
|
|
@@ -2488,6 +2649,23 @@ Coverage baseline: ${coverage_baseline}% — do not decrease coverage."
|
|
|
2488
2649
|
}
|
|
2489
2650
|
parse_claude_tokens "$_token_log"
|
|
2490
2651
|
|
|
2652
|
+
# Read accumulated token counts from build loop (written by sw-loop.sh)
|
|
2653
|
+
local _loop_token_file="${PROJECT_ROOT}/.claude/loop-logs/loop-tokens.json"
|
|
2654
|
+
if [[ -f "$_loop_token_file" ]] && command -v jq &>/dev/null; then
|
|
2655
|
+
local _loop_in _loop_out _loop_cost
|
|
2656
|
+
_loop_in=$(jq -r '.input_tokens // 0' "$_loop_token_file" 2>/dev/null || echo "0")
|
|
2657
|
+
_loop_out=$(jq -r '.output_tokens // 0' "$_loop_token_file" 2>/dev/null || echo "0")
|
|
2658
|
+
_loop_cost=$(jq -r '.cost_usd // 0' "$_loop_token_file" 2>/dev/null || echo "0")
|
|
2659
|
+
TOTAL_INPUT_TOKENS=$(( TOTAL_INPUT_TOKENS + ${_loop_in:-0} ))
|
|
2660
|
+
TOTAL_OUTPUT_TOKENS=$(( TOTAL_OUTPUT_TOKENS + ${_loop_out:-0} ))
|
|
2661
|
+
if [[ -n "$_loop_cost" && "$_loop_cost" != "0" && "$_loop_cost" != "null" ]]; then
|
|
2662
|
+
TOTAL_COST_USD="${_loop_cost}"
|
|
2663
|
+
fi
|
|
2664
|
+
if [[ "${_loop_in:-0}" -gt 0 || "${_loop_out:-0}" -gt 0 ]]; then
|
|
2665
|
+
info "Build loop tokens: in=${_loop_in} out=${_loop_out} cost=\$${_loop_cost:-0}"
|
|
2666
|
+
fi
|
|
2667
|
+
fi
|
|
2668
|
+
|
|
2491
2669
|
# Count commits made during build
|
|
2492
2670
|
local commit_count
|
|
2493
2671
|
commit_count=$(git log --oneline "${BASE_BRANCH}..HEAD" 2>/dev/null | wc -l | xargs)
|
|
@@ -2792,6 +2970,22 @@ $(cat "$diff_file")"
|
|
|
2792
2970
|
success "Review clean"
|
|
2793
2971
|
fi
|
|
2794
2972
|
|
|
2973
|
+
# ── Oversight gate: pipeline review/quality stages block on verdict ──
|
|
2974
|
+
if [[ -x "$SCRIPT_DIR/sw-oversight.sh" ]] && [[ "${SKIP_GATES:-false}" != "true" ]]; then
|
|
2975
|
+
local reject_reason=""
|
|
2976
|
+
local _sec_count
|
|
2977
|
+
_sec_count=$(grep -ciE '\*\*\[?Security\]?\*\*' "$review_file" 2>/dev/null || true)
|
|
2978
|
+
_sec_count="${_sec_count:-0}"
|
|
2979
|
+
local _blocking=$((critical_count + _sec_count))
|
|
2980
|
+
[[ "$_blocking" -gt 0 ]] && reject_reason="Review found ${_blocking} critical/security issue(s)"
|
|
2981
|
+
if ! bash "$SCRIPT_DIR/sw-oversight.sh" gate --diff "$diff_file" --description "${GOAL:-Pipeline review}" --reject-if "$reject_reason" >/dev/null 2>&1; then
|
|
2982
|
+
error "Oversight gate rejected — blocking pipeline"
|
|
2983
|
+
emit_event "review.oversight_blocked" "issue=${ISSUE_NUMBER:-0}"
|
|
2984
|
+
log_stage "review" "BLOCKED: oversight gate rejected"
|
|
2985
|
+
return 1
|
|
2986
|
+
fi
|
|
2987
|
+
fi
|
|
2988
|
+
|
|
2795
2989
|
# ── Review Blocking Gate ──
|
|
2796
2990
|
# Block pipeline on critical/security issues unless compound_quality handles them
|
|
2797
2991
|
local security_count
|
|
@@ -3771,6 +3965,8 @@ stage_monitor() {
|
|
|
3771
3965
|
fi
|
|
3772
3966
|
|
|
3773
3967
|
local report_file="$ARTIFACTS_DIR/monitor-report.md"
|
|
3968
|
+
local deploy_log_file="$ARTIFACTS_DIR/deploy-logs.txt"
|
|
3969
|
+
: > "$deploy_log_file"
|
|
3774
3970
|
local total_errors=0
|
|
3775
3971
|
local poll_interval=30 # seconds between polls
|
|
3776
3972
|
local total_polls=$(( (duration_minutes * 60) / poll_interval ))
|
|
@@ -3818,10 +4014,11 @@ stage_monitor() {
|
|
|
3818
4014
|
fi
|
|
3819
4015
|
fi
|
|
3820
4016
|
|
|
3821
|
-
# Log command check
|
|
4017
|
+
# Log command check (accumulate deploy logs for feedback collect)
|
|
3822
4018
|
if [[ -n "$log_cmd" ]]; then
|
|
3823
4019
|
local log_output
|
|
3824
4020
|
log_output=$(bash -c "$log_cmd" 2>/dev/null || true)
|
|
4021
|
+
[[ -n "$log_output" ]] && echo "$log_output" >> "$deploy_log_file"
|
|
3825
4022
|
local error_count=0
|
|
3826
4023
|
if [[ -n "$log_output" ]]; then
|
|
3827
4024
|
error_count=$(echo "$log_output" | grep -cE "$log_pattern" 2>/dev/null || true)
|
|
@@ -3856,13 +4053,24 @@ stage_monitor() {
|
|
|
3856
4053
|
"total_errors=$total_errors" \
|
|
3857
4054
|
"threshold=$error_threshold"
|
|
3858
4055
|
|
|
3859
|
-
#
|
|
3860
|
-
if [[ "$
|
|
4056
|
+
# Feedback loop: collect deploy logs and optionally create issue
|
|
4057
|
+
if [[ -f "$deploy_log_file" ]] && [[ -s "$deploy_log_file" ]] && [[ -x "$SCRIPT_DIR/sw-feedback.sh" ]]; then
|
|
4058
|
+
(cd "$PROJECT_ROOT" && ARTIFACTS_DIR="$ARTIFACTS_DIR" bash "$SCRIPT_DIR/sw-feedback.sh" collect "$deploy_log_file" 2>/dev/null) || true
|
|
4059
|
+
(cd "$PROJECT_ROOT" && ARTIFACTS_DIR="$ARTIFACTS_DIR" bash "$SCRIPT_DIR/sw-feedback.sh" create-issue 2>/dev/null) || true
|
|
4060
|
+
fi
|
|
4061
|
+
|
|
4062
|
+
# Auto-rollback: feedback rollback (GitHub Deployments API) and/or config rollback_cmd
|
|
4063
|
+
if [[ "$auto_rollback" == "true" ]]; then
|
|
3861
4064
|
warn "Auto-rolling back..."
|
|
3862
4065
|
echo "" >> "$report_file"
|
|
3863
4066
|
echo "## Rollback" >> "$report_file"
|
|
3864
4067
|
|
|
3865
|
-
|
|
4068
|
+
# Trigger feedback rollback (calls sw-github-deploy.sh rollback)
|
|
4069
|
+
if [[ -x "$SCRIPT_DIR/sw-feedback.sh" ]]; then
|
|
4070
|
+
(cd "$PROJECT_ROOT" && ARTIFACTS_DIR="$ARTIFACTS_DIR" bash "$SCRIPT_DIR/sw-feedback.sh" rollback production "Monitor threshold exceeded (${total_errors} errors)" >> "$report_file" 2>&1) || true
|
|
4071
|
+
fi
|
|
4072
|
+
|
|
4073
|
+
if [[ -n "$rollback_cmd" ]] && bash -c "$rollback_cmd" >> "$report_file" 2>&1; then
|
|
3866
4074
|
success "Rollback executed"
|
|
3867
4075
|
echo "Rollback: ✅ success" >> "$report_file"
|
|
3868
4076
|
|
|
@@ -7076,6 +7284,12 @@ Focus on fixing the failing tests while keeping all passing tests working."
|
|
|
7076
7284
|
_snap_error="${_snap_error:-}"
|
|
7077
7285
|
pipeline_emit_progress_snapshot "${ISSUE_NUMBER}" "${CURRENT_STAGE_ID:-test}" "${cycle:-0}" "${_diff_count:-0}" "${_snap_files}" "${_snap_error}" 2>/dev/null || true
|
|
7078
7286
|
fi
|
|
7287
|
+
# Record fix outcome when tests pass after a retry with memory injection (pipeline path)
|
|
7288
|
+
if [[ "$cycle" -gt 1 && -n "${last_test_error:-}" ]] && [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
|
|
7289
|
+
local _sig
|
|
7290
|
+
_sig=$(echo "$last_test_error" | head -3 | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
|
|
7291
|
+
[[ -n "$_sig" ]] && bash "$SCRIPT_DIR/sw-memory.sh" fix-outcome "$_sig" "true" "true" 2>/dev/null || true
|
|
7292
|
+
fi
|
|
7079
7293
|
return 0 # Tests passed!
|
|
7080
7294
|
fi
|
|
7081
7295
|
|
|
@@ -7285,7 +7499,9 @@ run_pipeline() {
|
|
|
7285
7499
|
if [[ "$build_gate" == "approve" && "$SKIP_GATES" != "true" ]]; then
|
|
7286
7500
|
show_stage_preview "build"
|
|
7287
7501
|
local answer=""
|
|
7288
|
-
|
|
7502
|
+
if [[ -t 0 ]]; then
|
|
7503
|
+
read -rp " Proceed with build+test (self-healing)? [Y/n] " answer || true
|
|
7504
|
+
fi
|
|
7289
7505
|
if [[ "$answer" =~ ^[Nn] ]]; then
|
|
7290
7506
|
update_status "paused" "build"
|
|
7291
7507
|
info "Pipeline paused. Resume with: ${DIM}shipwright pipeline resume${RESET}"
|
|
@@ -7323,7 +7539,12 @@ run_pipeline() {
|
|
|
7323
7539
|
if [[ "$gate" == "approve" && "$SKIP_GATES" != "true" ]]; then
|
|
7324
7540
|
show_stage_preview "$id"
|
|
7325
7541
|
local answer=""
|
|
7326
|
-
|
|
7542
|
+
if [[ -t 0 ]]; then
|
|
7543
|
+
read -rp " Proceed with ${id}? [Y/n] " answer || true
|
|
7544
|
+
else
|
|
7545
|
+
# Non-interactive: auto-approve (shouldn't reach here if headless detection works)
|
|
7546
|
+
info "Non-interactive mode — auto-approving ${id}"
|
|
7547
|
+
fi
|
|
7327
7548
|
if [[ "$answer" =~ ^[Nn] ]]; then
|
|
7328
7549
|
update_status "paused" "$id"
|
|
7329
7550
|
info "Pipeline paused at ${BOLD}$id${RESET}. Resume with: ${DIM}shipwright pipeline resume${RESET}"
|
|
@@ -7426,11 +7647,29 @@ run_pipeline() {
|
|
|
7426
7647
|
if run_stage_with_retry "$id"; then
|
|
7427
7648
|
mark_stage_complete "$id"
|
|
7428
7649
|
completed=$((completed + 1))
|
|
7650
|
+
# Capture project pattern after intake (for memory context in later stages)
|
|
7651
|
+
if [[ "$id" == "intake" ]] && [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
|
|
7652
|
+
(cd "$REPO_DIR" && bash "$SCRIPT_DIR/sw-memory.sh" pattern "project" "{}" 2>/dev/null) || true
|
|
7653
|
+
fi
|
|
7429
7654
|
local timing stage_dur_s
|
|
7430
7655
|
timing=$(get_stage_timing "$id")
|
|
7431
7656
|
stage_dur_s=$(( $(now_epoch) - stage_start_epoch ))
|
|
7432
7657
|
success "Stage ${BOLD}$id${RESET} complete ${DIM}(${timing})${RESET}"
|
|
7433
7658
|
emit_event "stage.completed" "issue=${ISSUE_NUMBER:-0}" "stage=$id" "duration_s=$stage_dur_s"
|
|
7659
|
+
# Broadcast discovery for cross-pipeline learning
|
|
7660
|
+
if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
|
|
7661
|
+
local _disc_cat _disc_patterns _disc_text
|
|
7662
|
+
_disc_cat="$id"
|
|
7663
|
+
case "$id" in
|
|
7664
|
+
plan) _disc_patterns="*.md"; _disc_text="Plan completed: ${GOAL:-goal}" ;;
|
|
7665
|
+
design) _disc_patterns="*.md,*.ts,*.tsx,*.js"; _disc_text="Design completed for ${GOAL:-goal}" ;;
|
|
7666
|
+
build) _disc_patterns="src/*,*.ts,*.tsx,*.js"; _disc_text="Build completed" ;;
|
|
7667
|
+
test) _disc_patterns="*.test.*,*_test.*"; _disc_text="Tests passed" ;;
|
|
7668
|
+
review) _disc_patterns="*.md,*.ts,*.tsx"; _disc_text="Review completed" ;;
|
|
7669
|
+
*) _disc_patterns="*"; _disc_text="Stage $id completed" ;;
|
|
7670
|
+
esac
|
|
7671
|
+
bash "$SCRIPT_DIR/sw-discovery.sh" broadcast "$_disc_cat" "$_disc_patterns" "$_disc_text" "" 2>/dev/null || true
|
|
7672
|
+
fi
|
|
7434
7673
|
# Log model used for prediction feedback
|
|
7435
7674
|
echo "${id}|${stage_model_used}|true" >> "${ARTIFACTS_DIR}/model-routing.log"
|
|
7436
7675
|
else
|
|
@@ -7613,8 +7852,14 @@ pipeline_cleanup_worktree() {
|
|
|
7613
7852
|
|
|
7614
7853
|
if [[ -n "${ORIGINAL_REPO_DIR:-}" && "$worktree_path" != "$ORIGINAL_REPO_DIR" ]]; then
|
|
7615
7854
|
cd "$ORIGINAL_REPO_DIR" 2>/dev/null || cd /
|
|
7616
|
-
|
|
7617
|
-
|
|
7855
|
+
# Only clean up worktree on success — preserve on failure for inspection
|
|
7856
|
+
if [[ "${PIPELINE_EXIT_CODE:-1}" -eq 0 ]]; then
|
|
7857
|
+
info "Cleaning up worktree: ${DIM}${worktree_path}${RESET}"
|
|
7858
|
+
git worktree remove --force "$worktree_path" 2>/dev/null || true
|
|
7859
|
+
else
|
|
7860
|
+
warn "Pipeline failed — worktree preserved for inspection: ${DIM}${worktree_path}${RESET}"
|
|
7861
|
+
warn "Clean up manually: ${DIM}git worktree remove --force ${worktree_path}${RESET}"
|
|
7862
|
+
fi
|
|
7618
7863
|
fi
|
|
7619
7864
|
}
|
|
7620
7865
|
|
|
@@ -7787,6 +8032,24 @@ run_dry_run() {
|
|
|
7787
8032
|
# ─── Subcommands ────────────────────────────────────────────────────────────
|
|
7788
8033
|
|
|
7789
8034
|
pipeline_start() {
|
|
8035
|
+
# Handle --repo flag: change to directory before running
|
|
8036
|
+
if [[ -n "$REPO_OVERRIDE" ]]; then
|
|
8037
|
+
if [[ ! -d "$REPO_OVERRIDE" ]]; then
|
|
8038
|
+
error "Directory does not exist: $REPO_OVERRIDE"
|
|
8039
|
+
exit 1
|
|
8040
|
+
fi
|
|
8041
|
+
if ! cd "$REPO_OVERRIDE" 2>/dev/null; then
|
|
8042
|
+
error "Cannot cd to: $REPO_OVERRIDE"
|
|
8043
|
+
exit 1
|
|
8044
|
+
fi
|
|
8045
|
+
if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
|
|
8046
|
+
error "Not a git repository: $REPO_OVERRIDE"
|
|
8047
|
+
exit 1
|
|
8048
|
+
fi
|
|
8049
|
+
ORIGINAL_REPO_DIR="$(pwd)"
|
|
8050
|
+
info "Using repository: $ORIGINAL_REPO_DIR"
|
|
8051
|
+
fi
|
|
8052
|
+
|
|
7790
8053
|
if [[ -z "$GOAL" && -z "$ISSUE_NUMBER" ]]; then
|
|
7791
8054
|
error "Must provide --goal or --issue"
|
|
7792
8055
|
echo -e " Example: ${DIM}shipwright pipeline start --goal \"Add JWT auth\"${RESET}"
|
|
@@ -7879,7 +8142,9 @@ pipeline_start() {
|
|
|
7879
8142
|
|
|
7880
8143
|
local gate_count
|
|
7881
8144
|
gate_count=$(jq '[.stages[] | select(.gate == "approve" and .enabled == true)] | length' "$PIPELINE_CONFIG")
|
|
7882
|
-
if [[ "$
|
|
8145
|
+
if [[ "$HEADLESS" == "true" ]]; then
|
|
8146
|
+
echo -e " ${BOLD}Gates:${RESET} ${YELLOW}all auto (headless — non-interactive stdin detected)${RESET}"
|
|
8147
|
+
elif [[ "$SKIP_GATES" == "true" ]]; then
|
|
7883
8148
|
echo -e " ${BOLD}Gates:${RESET} ${YELLOW}all auto (--skip-gates)${RESET}"
|
|
7884
8149
|
else
|
|
7885
8150
|
echo -e " ${BOLD}Gates:${RESET} ${gate_count} approval gate(s)"
|
|
@@ -7931,6 +8196,7 @@ pipeline_start() {
|
|
|
7931
8196
|
|
|
7932
8197
|
run_pipeline
|
|
7933
8198
|
local exit_code=$?
|
|
8199
|
+
PIPELINE_EXIT_CODE="$exit_code"
|
|
7934
8200
|
|
|
7935
8201
|
# Send completion notification + event
|
|
7936
8202
|
local total_dur_s=""
|
|
@@ -7946,9 +8212,15 @@ pipeline_start() {
|
|
|
7946
8212
|
"result=success" \
|
|
7947
8213
|
"duration_s=${total_dur_s:-0}" \
|
|
7948
8214
|
"pr_url=${pr_url:-}" \
|
|
8215
|
+
"agent_id=${PIPELINE_AGENT_ID}" \
|
|
7949
8216
|
"input_tokens=$TOTAL_INPUT_TOKENS" \
|
|
7950
8217
|
"output_tokens=$TOTAL_OUTPUT_TOKENS" \
|
|
7951
8218
|
"self_heal_count=$SELF_HEAL_COUNT"
|
|
8219
|
+
|
|
8220
|
+
# Auto-ingest pipeline outcome into recruit profiles
|
|
8221
|
+
if [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
|
|
8222
|
+
bash "$SCRIPT_DIR/sw-recruit.sh" ingest-pipeline 1 2>/dev/null || true
|
|
8223
|
+
fi
|
|
7952
8224
|
else
|
|
7953
8225
|
notify "Pipeline Failed" "Goal: ${GOAL}\nFailed at: ${CURRENT_STAGE_ID:-unknown}" "error"
|
|
7954
8226
|
emit_event "pipeline.completed" \
|
|
@@ -7956,10 +8228,16 @@ pipeline_start() {
|
|
|
7956
8228
|
"result=failure" \
|
|
7957
8229
|
"duration_s=${total_dur_s:-0}" \
|
|
7958
8230
|
"failed_stage=${CURRENT_STAGE_ID:-unknown}" \
|
|
8231
|
+
"agent_id=${PIPELINE_AGENT_ID}" \
|
|
7959
8232
|
"input_tokens=$TOTAL_INPUT_TOKENS" \
|
|
7960
8233
|
"output_tokens=$TOTAL_OUTPUT_TOKENS" \
|
|
7961
8234
|
"self_heal_count=$SELF_HEAL_COUNT"
|
|
7962
8235
|
|
|
8236
|
+
# Auto-ingest pipeline outcome into recruit profiles
|
|
8237
|
+
if [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
|
|
8238
|
+
bash "$SCRIPT_DIR/sw-recruit.sh" ingest-pipeline 1 2>/dev/null || true
|
|
8239
|
+
fi
|
|
8240
|
+
|
|
7963
8241
|
# Capture failure learnings to memory
|
|
7964
8242
|
if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
|
|
7965
8243
|
bash "$SCRIPT_DIR/sw-memory.sh" capture "$STATE_FILE" "$ARTIFACTS_DIR" 2>/dev/null || true
|
|
@@ -8015,18 +8293,24 @@ pipeline_start() {
|
|
|
8015
8293
|
memory_finalize_pipeline "$STATE_FILE" "$ARTIFACTS_DIR" 2>/dev/null || true
|
|
8016
8294
|
fi
|
|
8017
8295
|
|
|
8018
|
-
# Emit cost event
|
|
8296
|
+
# Emit cost event — prefer actual cost from Claude CLI when available
|
|
8019
8297
|
local model_key="${MODEL:-sonnet}"
|
|
8020
|
-
local
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8298
|
+
local total_cost
|
|
8299
|
+
if [[ -n "${TOTAL_COST_USD:-}" && "${TOTAL_COST_USD}" != "0" && "${TOTAL_COST_USD}" != "null" ]]; then
|
|
8300
|
+
total_cost="${TOTAL_COST_USD}"
|
|
8301
|
+
else
|
|
8302
|
+
# Fallback: estimate from token counts and model rates
|
|
8303
|
+
local input_cost output_cost
|
|
8304
|
+
input_cost=$(awk -v tokens="$TOTAL_INPUT_TOKENS" -v rate="$(echo "$COST_MODEL_RATES" | jq -r ".${model_key}.input // 3")" 'BEGIN{printf "%.4f", (tokens / 1000000) * rate}')
|
|
8305
|
+
output_cost=$(awk -v tokens="$TOTAL_OUTPUT_TOKENS" -v rate="$(echo "$COST_MODEL_RATES" | jq -r ".${model_key}.output // 15")" 'BEGIN{printf "%.4f", (tokens / 1000000) * rate}')
|
|
8306
|
+
total_cost=$(awk -v i="$input_cost" -v o="$output_cost" 'BEGIN{printf "%.4f", i + o}')
|
|
8307
|
+
fi
|
|
8024
8308
|
|
|
8025
8309
|
emit_event "pipeline.cost" \
|
|
8026
8310
|
"input_tokens=$TOTAL_INPUT_TOKENS" \
|
|
8027
8311
|
"output_tokens=$TOTAL_OUTPUT_TOKENS" \
|
|
8028
8312
|
"model=$model_key" \
|
|
8029
|
-
"
|
|
8313
|
+
"cost_usd=$total_cost"
|
|
8030
8314
|
|
|
8031
8315
|
return $exit_code
|
|
8032
8316
|
}
|