shipwright-cli 2.4.0 → 3.1.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 +16 -11
- package/completions/_shipwright +248 -94
- package/completions/shipwright.bash +68 -19
- package/completions/shipwright.fish +310 -42
- package/config/decision-tiers.json +55 -0
- package/config/defaults.json +111 -0
- package/config/event-schema.json +218 -0
- package/config/policy.json +21 -18
- package/dashboard/coverage/coverage-summary.json +14 -0
- package/dashboard/public/index.html +1 -1
- package/dashboard/server.ts +306 -17
- package/dashboard/src/components/charts/bar.test.ts +79 -0
- package/dashboard/src/components/charts/donut.test.ts +68 -0
- package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
- package/dashboard/src/components/charts/sparkline.test.ts +125 -0
- package/dashboard/src/core/api.test.ts +309 -0
- package/dashboard/src/core/helpers.test.ts +301 -0
- package/dashboard/src/core/router.test.ts +307 -0
- package/dashboard/src/core/router.ts +7 -0
- package/dashboard/src/core/sse.test.ts +144 -0
- package/dashboard/src/views/metrics.test.ts +186 -0
- package/dashboard/src/views/overview.test.ts +173 -0
- package/dashboard/src/views/pipelines.test.ts +183 -0
- package/dashboard/src/views/team.test.ts +253 -0
- package/dashboard/vitest.config.ts +14 -5
- package/docs/TIPS.md +1 -1
- package/docs/patterns/README.md +1 -1
- package/package.json +7 -9
- package/scripts/adapters/docker-deploy.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +11 -1
- package/scripts/adapters/wezterm-adapter.sh +1 -1
- package/scripts/check-version-consistency.sh +1 -1
- package/scripts/lib/architecture.sh +127 -0
- package/scripts/lib/bootstrap.sh +75 -0
- package/scripts/lib/compat.sh +89 -6
- package/scripts/lib/config.sh +91 -0
- package/scripts/lib/daemon-adaptive.sh +3 -3
- package/scripts/lib/daemon-dispatch.sh +63 -17
- package/scripts/lib/daemon-failure.sh +0 -0
- package/scripts/lib/daemon-health.sh +1 -1
- package/scripts/lib/daemon-patrol.sh +64 -17
- package/scripts/lib/daemon-poll.sh +54 -25
- package/scripts/lib/daemon-state.sh +125 -23
- package/scripts/lib/daemon-triage.sh +31 -9
- package/scripts/lib/decide-autonomy.sh +295 -0
- package/scripts/lib/decide-scoring.sh +228 -0
- package/scripts/lib/decide-signals.sh +462 -0
- package/scripts/lib/fleet-failover.sh +63 -0
- package/scripts/lib/helpers.sh +29 -6
- package/scripts/lib/pipeline-detection.sh +2 -2
- package/scripts/lib/pipeline-github.sh +9 -9
- package/scripts/lib/pipeline-intelligence.sh +105 -38
- package/scripts/lib/pipeline-quality-checks.sh +17 -16
- package/scripts/lib/pipeline-quality.sh +1 -1
- package/scripts/lib/pipeline-stages.sh +440 -59
- package/scripts/lib/pipeline-state.sh +54 -4
- package/scripts/lib/policy.sh +0 -0
- package/scripts/lib/test-helpers.sh +247 -0
- package/scripts/postinstall.mjs +78 -12
- package/scripts/signals/example-collector.sh +36 -0
- package/scripts/sw +17 -7
- package/scripts/sw-activity.sh +1 -11
- package/scripts/sw-adaptive.sh +109 -85
- package/scripts/sw-adversarial.sh +4 -14
- package/scripts/sw-architecture-enforcer.sh +1 -11
- package/scripts/sw-auth.sh +8 -17
- package/scripts/sw-autonomous.sh +111 -49
- package/scripts/sw-changelog.sh +1 -11
- package/scripts/sw-checkpoint.sh +144 -20
- package/scripts/sw-ci.sh +2 -12
- package/scripts/sw-cleanup.sh +13 -17
- package/scripts/sw-code-review.sh +16 -36
- package/scripts/sw-connect.sh +5 -12
- package/scripts/sw-context.sh +9 -26
- package/scripts/sw-cost.sh +17 -18
- package/scripts/sw-daemon.sh +76 -71
- package/scripts/sw-dashboard.sh +57 -17
- package/scripts/sw-db.sh +524 -26
- package/scripts/sw-decide.sh +685 -0
- package/scripts/sw-decompose.sh +1 -11
- package/scripts/sw-deps.sh +15 -25
- package/scripts/sw-developer-simulation.sh +1 -11
- package/scripts/sw-discovery.sh +138 -30
- package/scripts/sw-doc-fleet.sh +7 -17
- package/scripts/sw-docs-agent.sh +6 -16
- package/scripts/sw-docs.sh +4 -12
- package/scripts/sw-doctor.sh +134 -43
- package/scripts/sw-dora.sh +11 -19
- package/scripts/sw-durable.sh +35 -52
- package/scripts/sw-e2e-orchestrator.sh +11 -27
- package/scripts/sw-eventbus.sh +115 -115
- package/scripts/sw-evidence.sh +114 -30
- package/scripts/sw-feedback.sh +3 -13
- package/scripts/sw-fix.sh +2 -20
- package/scripts/sw-fleet-discover.sh +1 -11
- package/scripts/sw-fleet-viz.sh +10 -18
- package/scripts/sw-fleet.sh +13 -17
- package/scripts/sw-github-app.sh +6 -16
- package/scripts/sw-github-checks.sh +1 -11
- package/scripts/sw-github-deploy.sh +1 -11
- package/scripts/sw-github-graphql.sh +2 -12
- package/scripts/sw-guild.sh +1 -11
- package/scripts/sw-heartbeat.sh +49 -12
- package/scripts/sw-hygiene.sh +45 -43
- package/scripts/sw-incident.sh +48 -74
- package/scripts/sw-init.sh +35 -37
- package/scripts/sw-instrument.sh +1 -11
- package/scripts/sw-intelligence.sh +368 -53
- package/scripts/sw-jira.sh +5 -14
- package/scripts/sw-launchd.sh +2 -12
- package/scripts/sw-linear.sh +8 -17
- package/scripts/sw-logs.sh +4 -12
- package/scripts/sw-loop.sh +905 -104
- package/scripts/sw-memory.sh +263 -20
- package/scripts/sw-mission-control.sh +2 -12
- package/scripts/sw-model-router.sh +73 -34
- package/scripts/sw-otel.sh +15 -23
- package/scripts/sw-oversight.sh +1 -11
- package/scripts/sw-patrol-meta.sh +5 -11
- package/scripts/sw-pipeline-composer.sh +7 -17
- package/scripts/sw-pipeline-vitals.sh +1 -11
- package/scripts/sw-pipeline.sh +550 -122
- package/scripts/sw-pm.sh +2 -12
- package/scripts/sw-pr-lifecycle.sh +33 -28
- package/scripts/sw-predictive.sh +16 -22
- package/scripts/sw-prep.sh +6 -16
- package/scripts/sw-ps.sh +1 -11
- package/scripts/sw-public-dashboard.sh +2 -12
- package/scripts/sw-quality.sh +85 -14
- package/scripts/sw-reaper.sh +1 -11
- package/scripts/sw-recruit.sh +15 -25
- package/scripts/sw-regression.sh +11 -21
- package/scripts/sw-release-manager.sh +19 -28
- package/scripts/sw-release.sh +8 -16
- package/scripts/sw-remote.sh +1 -11
- package/scripts/sw-replay.sh +48 -44
- package/scripts/sw-retro.sh +70 -92
- package/scripts/sw-review-rerun.sh +1 -1
- package/scripts/sw-scale.sh +174 -41
- package/scripts/sw-security-audit.sh +12 -22
- package/scripts/sw-self-optimize.sh +239 -23
- package/scripts/sw-session.sh +5 -15
- package/scripts/sw-setup.sh +8 -18
- package/scripts/sw-standup.sh +5 -15
- package/scripts/sw-status.sh +32 -23
- package/scripts/sw-strategic.sh +129 -13
- package/scripts/sw-stream.sh +1 -11
- package/scripts/sw-swarm.sh +76 -36
- package/scripts/sw-team-stages.sh +10 -20
- package/scripts/sw-templates.sh +4 -14
- package/scripts/sw-testgen.sh +3 -13
- package/scripts/sw-tmux-pipeline.sh +1 -19
- package/scripts/sw-tmux-role-color.sh +0 -10
- package/scripts/sw-tmux-status.sh +3 -11
- package/scripts/sw-tmux.sh +2 -20
- package/scripts/sw-trace.sh +1 -19
- package/scripts/sw-tracker-github.sh +0 -10
- package/scripts/sw-tracker-jira.sh +1 -11
- package/scripts/sw-tracker-linear.sh +1 -11
- package/scripts/sw-tracker.sh +7 -24
- package/scripts/sw-triage.sh +29 -39
- package/scripts/sw-upgrade.sh +5 -23
- package/scripts/sw-ux.sh +1 -19
- package/scripts/sw-webhook.sh +18 -32
- package/scripts/sw-widgets.sh +3 -21
- package/scripts/sw-worktree.sh +11 -27
- package/scripts/update-homebrew-sha.sh +73 -0
- package/templates/pipelines/tdd.json +72 -0
- package/scripts/sw-pipeline.sh.mock +0 -7
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="
|
|
14
|
+
VERSION="3.1.0"
|
|
15
15
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
16
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
17
17
|
|
|
@@ -21,6 +21,7 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
21
21
|
# Canonical helpers (colors, output, events)
|
|
22
22
|
# shellcheck source=lib/helpers.sh
|
|
23
23
|
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
24
|
+
[[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
|
|
24
25
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
25
26
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
26
27
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
@@ -30,23 +31,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
|
30
31
|
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
31
32
|
now_epoch() { date +%s; }
|
|
32
33
|
fi
|
|
33
|
-
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
34
|
-
emit_event() {
|
|
35
|
-
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
36
|
-
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
37
|
-
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
38
|
-
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
39
|
-
}
|
|
40
|
-
fi
|
|
41
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
42
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
43
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
44
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
45
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
46
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
47
|
-
DIM="${DIM:-\033[2m}"
|
|
48
|
-
BOLD="${BOLD:-\033[1m}"
|
|
49
|
-
RESET="${RESET:-\033[0m}"
|
|
50
34
|
# Policy + pipeline quality thresholds (config/policy.json via lib/pipeline-quality.sh)
|
|
51
35
|
[[ -f "$SCRIPT_DIR/lib/pipeline-quality.sh" ]] && source "$SCRIPT_DIR/lib/pipeline-quality.sh"
|
|
52
36
|
# shellcheck source=lib/pipeline-state.sh
|
|
@@ -107,6 +91,14 @@ fi
|
|
|
107
91
|
if [[ -f "$SCRIPT_DIR/sw-durable.sh" ]]; then
|
|
108
92
|
source "$SCRIPT_DIR/sw-durable.sh"
|
|
109
93
|
fi
|
|
94
|
+
# shellcheck source=sw-db.sh — for db_save_checkpoint/db_load_checkpoint (durable workflows)
|
|
95
|
+
[[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
|
|
96
|
+
# Ensure DB schema exists so emit_event → db_add_event can write rows (CREATE IF NOT EXISTS is idempotent)
|
|
97
|
+
if type init_schema >/dev/null 2>&1 && type check_sqlite3 >/dev/null 2>&1 && check_sqlite3 2>/dev/null; then
|
|
98
|
+
init_schema 2>/dev/null || true
|
|
99
|
+
fi
|
|
100
|
+
# shellcheck source=sw-cost.sh — for cost_record persistence to costs.json + DB
|
|
101
|
+
[[ -f "$SCRIPT_DIR/sw-cost.sh" ]] && source "$SCRIPT_DIR/sw-cost.sh"
|
|
110
102
|
|
|
111
103
|
# ─── GitHub API Modules (optional) ─────────────────────────────────────────
|
|
112
104
|
# shellcheck source=sw-github-graphql.sh
|
|
@@ -151,6 +143,21 @@ format_duration() {
|
|
|
151
143
|
fi
|
|
152
144
|
}
|
|
153
145
|
|
|
146
|
+
# Rotate event log if needed (standalone mode — daemon has its own rotation in poll loop)
|
|
147
|
+
rotate_event_log_if_needed() {
|
|
148
|
+
local events_file="${EVENTS_FILE:-$HOME/.shipwright/events.jsonl}"
|
|
149
|
+
local max_lines=10000
|
|
150
|
+
[[ ! -f "$events_file" ]] && return
|
|
151
|
+
local lines
|
|
152
|
+
lines=$(wc -l < "$events_file" 2>/dev/null || echo "0")
|
|
153
|
+
if [[ "$lines" -gt "$max_lines" ]]; then
|
|
154
|
+
local tmp="${events_file}.rotating"
|
|
155
|
+
if tail -5000 "$events_file" > "$tmp" 2>/dev/null && mv "$tmp" "$events_file" 2>/dev/null; then
|
|
156
|
+
info "Rotated events.jsonl: ${lines} -> 5000 lines"
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
}
|
|
160
|
+
|
|
154
161
|
_pipeline_compact_goal() {
|
|
155
162
|
local goal="$1"
|
|
156
163
|
local plan_file="${2:-}"
|
|
@@ -199,33 +206,6 @@ load_composed_pipeline() {
|
|
|
199
206
|
return 0
|
|
200
207
|
}
|
|
201
208
|
|
|
202
|
-
# ─── Structured Event Log ──────────────────────────────────────────────────
|
|
203
|
-
# Appends JSON events to ~/.shipwright/events.jsonl for metrics/traceability
|
|
204
|
-
|
|
205
|
-
EVENTS_DIR="${HOME}/.shipwright"
|
|
206
|
-
EVENTS_FILE="${EVENTS_DIR}/events.jsonl"
|
|
207
|
-
|
|
208
|
-
emit_event() {
|
|
209
|
-
local event_type="$1"
|
|
210
|
-
shift
|
|
211
|
-
# Remaining args are key=value pairs
|
|
212
|
-
local json_fields=""
|
|
213
|
-
for kv in "$@"; do
|
|
214
|
-
local key="${kv%%=*}"
|
|
215
|
-
local val="${kv#*=}"
|
|
216
|
-
# Numbers: don't quote; strings: quote
|
|
217
|
-
if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
|
218
|
-
json_fields="${json_fields},\"${key}\":${val}"
|
|
219
|
-
else
|
|
220
|
-
# Escape quotes in value
|
|
221
|
-
val="${val//\"/\\\"}"
|
|
222
|
-
json_fields="${json_fields},\"${key}\":\"${val}\""
|
|
223
|
-
fi
|
|
224
|
-
done
|
|
225
|
-
mkdir -p "$EVENTS_DIR"
|
|
226
|
-
echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
|
|
227
|
-
}
|
|
228
|
-
|
|
229
209
|
# ─── Token / Cost Parsing ─────────────────────────────────────────────────
|
|
230
210
|
parse_claude_tokens() {
|
|
231
211
|
local log_file="$1"
|
|
@@ -237,6 +217,36 @@ parse_claude_tokens() {
|
|
|
237
217
|
TOTAL_OUTPUT_TOKENS=$(( TOTAL_OUTPUT_TOKENS + ${output_tok:-0} ))
|
|
238
218
|
}
|
|
239
219
|
|
|
220
|
+
# Estimate pipeline cost using historical averages from completed pipelines.
|
|
221
|
+
# Falls back to per-stage estimates when no history exists.
|
|
222
|
+
estimate_pipeline_cost() {
|
|
223
|
+
local stages="$1"
|
|
224
|
+
local stage_count
|
|
225
|
+
stage_count=$(echo "$stages" | jq 'length' 2>/dev/null || echo "6")
|
|
226
|
+
[[ ! "$stage_count" =~ ^[0-9]+$ ]] && stage_count=6
|
|
227
|
+
|
|
228
|
+
local events_file="${EVENTS_FILE:-$HOME/.shipwright/events.jsonl}"
|
|
229
|
+
local avg_input=0 avg_output=0
|
|
230
|
+
if [[ -f "$events_file" ]]; then
|
|
231
|
+
local hist
|
|
232
|
+
hist=$(grep '"type":"pipeline.completed"' "$events_file" 2>/dev/null | tail -10)
|
|
233
|
+
if [[ -n "$hist" ]]; then
|
|
234
|
+
avg_input=$(echo "$hist" | jq -s -r '[.[] | .input_tokens // 0 | tonumber] | if length > 0 then (add / length | floor | tostring) else "0" end' 2>/dev/null | head -1)
|
|
235
|
+
avg_output=$(echo "$hist" | jq -s -r '[.[] | .output_tokens // 0 | tonumber] | if length > 0 then (add / length | floor | tostring) else "0" end' 2>/dev/null | head -1)
|
|
236
|
+
fi
|
|
237
|
+
fi
|
|
238
|
+
[[ ! "$avg_input" =~ ^[0-9]+$ ]] && avg_input=0
|
|
239
|
+
[[ ! "$avg_output" =~ ^[0-9]+$ ]] && avg_output=0
|
|
240
|
+
|
|
241
|
+
# Fall back to reasonable per-stage estimates only if no history
|
|
242
|
+
if [[ "$avg_input" -eq 0 ]]; then
|
|
243
|
+
avg_input=$(( stage_count * 8000 )) # More realistic: ~8K input per stage
|
|
244
|
+
avg_output=$(( stage_count * 4000 )) # ~4K output per stage
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
echo "{\"input_tokens\":${avg_input},\"output_tokens\":${avg_output}}"
|
|
248
|
+
}
|
|
249
|
+
|
|
240
250
|
# ─── Defaults ───────────────────────────────────────────────────────────────
|
|
241
251
|
GOAL=""
|
|
242
252
|
ISSUE_NUMBER=""
|
|
@@ -260,6 +270,7 @@ CI_MODE=false
|
|
|
260
270
|
DRY_RUN=false
|
|
261
271
|
IGNORE_BUDGET=false
|
|
262
272
|
COMPLETED_STAGES=""
|
|
273
|
+
RESUME_FROM_CHECKPOINT=false
|
|
263
274
|
MAX_ITERATIONS_OVERRIDE=""
|
|
264
275
|
MAX_RESTARTS_OVERRIDE=""
|
|
265
276
|
FAST_TEST_CMD_OVERRIDE=""
|
|
@@ -285,6 +296,10 @@ GH_AVAILABLE=false
|
|
|
285
296
|
# Timing
|
|
286
297
|
PIPELINE_START_EPOCH=""
|
|
287
298
|
STAGE_TIMINGS=""
|
|
299
|
+
PIPELINE_STAGES_PASSED=""
|
|
300
|
+
PIPELINE_SLOWEST_STAGE=""
|
|
301
|
+
LAST_STAGE_ERROR_CLASS=""
|
|
302
|
+
LAST_STAGE_ERROR=""
|
|
288
303
|
|
|
289
304
|
PROJECT_ROOT=""
|
|
290
305
|
STATE_DIR=""
|
|
@@ -333,6 +348,7 @@ show_help() {
|
|
|
333
348
|
echo -e " ${DIM}--max-iterations <n>${RESET} Override max build loop iterations"
|
|
334
349
|
echo -e " ${DIM}--max-restarts <n>${RESET} Max session restarts in build loop"
|
|
335
350
|
echo -e " ${DIM}--fast-test-cmd <cmd>${RESET} Fast/subset test for build loop"
|
|
351
|
+
echo -e " ${DIM}--tdd${RESET} Test-first: generate tests before implementation"
|
|
336
352
|
echo -e " ${DIM}--completed-stages \"a,b\"${RESET} Skip these stages (CI resume)"
|
|
337
353
|
echo ""
|
|
338
354
|
echo -e "${BOLD}STAGES${RESET} ${DIM}(configurable per pipeline template)${RESET}"
|
|
@@ -413,6 +429,7 @@ parse_args() {
|
|
|
413
429
|
--ignore-budget) IGNORE_BUDGET=true; shift ;;
|
|
414
430
|
--max-iterations) MAX_ITERATIONS_OVERRIDE="$2"; shift 2 ;;
|
|
415
431
|
--completed-stages) COMPLETED_STAGES="$2"; shift 2 ;;
|
|
432
|
+
--resume) RESUME_FROM_CHECKPOINT=true; shift ;;
|
|
416
433
|
--worktree=*) AUTO_WORKTREE=true; WORKTREE_NAME="${1#--worktree=}"; WORKTREE_NAME="${WORKTREE_NAME//[^a-zA-Z0-9_-]/}"; if [[ -z "$WORKTREE_NAME" ]]; then error "Invalid worktree name (alphanumeric, hyphens, underscores only)"; exit 1; fi; shift ;;
|
|
417
434
|
--worktree) AUTO_WORKTREE=true; shift ;;
|
|
418
435
|
--dry-run) DRY_RUN=true; shift ;;
|
|
@@ -427,6 +444,7 @@ parse_args() {
|
|
|
427
444
|
shift 2 ;;
|
|
428
445
|
|
|
429
446
|
--fast-test-cmd) FAST_TEST_CMD_OVERRIDE="$2"; shift 2 ;;
|
|
447
|
+
--tdd) TDD_ENABLED=true; shift ;;
|
|
430
448
|
--help|-h) show_help; exit 0 ;;
|
|
431
449
|
*)
|
|
432
450
|
if [[ -z "$PIPELINE_NAME_ARG" ]]; then
|
|
@@ -487,11 +505,11 @@ find_pipeline_config() {
|
|
|
487
505
|
load_pipeline_config() {
|
|
488
506
|
# Check for intelligence-composed pipeline first
|
|
489
507
|
local composed_pipeline="${ARTIFACTS_DIR}/composed-pipeline.json"
|
|
490
|
-
if [[ -f "$composed_pipeline" ]] && type composer_validate_pipeline
|
|
508
|
+
if [[ -f "$composed_pipeline" ]] && type composer_validate_pipeline >/dev/null 2>&1; then
|
|
491
509
|
# Use composed pipeline if fresh (< 1 hour old)
|
|
492
510
|
local composed_age=99999
|
|
493
511
|
local composed_mtime
|
|
494
|
-
composed_mtime=$(
|
|
512
|
+
composed_mtime=$(file_mtime "$composed_pipeline")
|
|
495
513
|
if [[ "$composed_mtime" -gt 0 ]]; then
|
|
496
514
|
composed_age=$(( $(now_epoch) - composed_mtime ))
|
|
497
515
|
fi
|
|
@@ -513,6 +531,9 @@ load_pipeline_config() {
|
|
|
513
531
|
exit 1
|
|
514
532
|
}
|
|
515
533
|
info "Pipeline: ${BOLD}$PIPELINE_NAME${RESET} ${DIM}($PIPELINE_CONFIG)${RESET}"
|
|
534
|
+
# TDD from template (overridable by --tdd)
|
|
535
|
+
[[ "$(jq -r '.tdd // false' "$PIPELINE_CONFIG" 2>/dev/null)" == "true" ]] && PIPELINE_TDD=true
|
|
536
|
+
return 0
|
|
516
537
|
}
|
|
517
538
|
|
|
518
539
|
CURRENT_STAGE_ID=""
|
|
@@ -522,7 +543,7 @@ SLACK_WEBHOOK=""
|
|
|
522
543
|
NOTIFICATION_ENABLED=false
|
|
523
544
|
|
|
524
545
|
# Self-healing
|
|
525
|
-
BUILD_TEST_RETRIES
|
|
546
|
+
BUILD_TEST_RETRIES=$(_config_get_int "pipeline.build_test_retries" 3 2>/dev/null || echo 3)
|
|
526
547
|
STASHED_CHANGES=false
|
|
527
548
|
SELF_HEAL_COUNT=0
|
|
528
549
|
|
|
@@ -544,7 +565,7 @@ start_heartbeat() {
|
|
|
544
565
|
--stage "${CURRENT_STAGE_ID:-unknown}" \
|
|
545
566
|
--iteration "0" \
|
|
546
567
|
--activity "$(get_stage_description "${CURRENT_STAGE_ID:-}" 2>/dev/null || echo "Running pipeline")" 2>/dev/null || true
|
|
547
|
-
sleep 30
|
|
568
|
+
sleep "$(_config_get_int "pipeline.heartbeat_interval" 30 2>/dev/null || echo 30)"
|
|
548
569
|
done
|
|
549
570
|
) >/dev/null 2>&1 &
|
|
550
571
|
HEARTBEAT_PID=$!
|
|
@@ -574,7 +595,10 @@ ci_push_partial_work() {
|
|
|
574
595
|
fi
|
|
575
596
|
|
|
576
597
|
# Push branch (create if needed, force to overwrite previous WIP)
|
|
577
|
-
git push origin "HEAD:refs/heads/$branch" --force 2>/dev/null
|
|
598
|
+
if ! git push origin "HEAD:refs/heads/$branch" --force 2>/dev/null; then
|
|
599
|
+
warn "git push failed for $branch — remote may be out of sync"
|
|
600
|
+
emit_event "pipeline.push_failed" "branch=$branch"
|
|
601
|
+
fi
|
|
578
602
|
}
|
|
579
603
|
|
|
580
604
|
ci_post_stage_event() {
|
|
@@ -584,7 +608,7 @@ ci_post_stage_event() {
|
|
|
584
608
|
|
|
585
609
|
local stage="$1" status="$2" elapsed="${3:-0s}"
|
|
586
610
|
local comment="<!-- SHIPWRIGHT-STAGE: ${stage}:${status}:${elapsed} -->"
|
|
587
|
-
gh issue comment "$ISSUE_NUMBER" --body "$comment" 2>/dev/null || true
|
|
611
|
+
_timeout "$(_config_get_int "network.gh_timeout" 30 2>/dev/null || echo 30)" gh issue comment "$ISSUE_NUMBER" --body "$comment" 2>/dev/null || true
|
|
588
612
|
}
|
|
589
613
|
|
|
590
614
|
# ─── Signal Handling ───────────────────────────────────────────────────────
|
|
@@ -615,12 +639,20 @@ cleanup_on_exit() {
|
|
|
615
639
|
git stash pop --quiet 2>/dev/null || true
|
|
616
640
|
fi
|
|
617
641
|
|
|
642
|
+
# Release durable pipeline lock
|
|
643
|
+
if [[ -n "${_PIPELINE_LOCK_ID:-}" ]] && type release_lock >/dev/null 2>&1; then
|
|
644
|
+
release_lock "$_PIPELINE_LOCK_ID" 2>/dev/null || true
|
|
645
|
+
fi
|
|
646
|
+
|
|
618
647
|
# Cancel lingering in_progress GitHub Check Runs
|
|
619
648
|
pipeline_cancel_check_runs 2>/dev/null || true
|
|
620
649
|
|
|
621
650
|
# Update GitHub
|
|
622
651
|
if [[ -n "${ISSUE_NUMBER:-}" && "${GH_AVAILABLE:-false}" == "true" ]]; then
|
|
623
|
-
|
|
652
|
+
if ! _timeout "$(_config_get_int "network.gh_timeout" 30 2>/dev/null || echo 30)" gh issue comment "$ISSUE_NUMBER" --body "⏸️ **Pipeline interrupted** at stage: ${CURRENT_STAGE_ID:-unknown}" 2>/dev/null; then
|
|
653
|
+
warn "gh issue comment failed — status update may not have been posted"
|
|
654
|
+
emit_event "pipeline.comment_failed" "issue=$ISSUE_NUMBER"
|
|
655
|
+
fi
|
|
624
656
|
fi
|
|
625
657
|
|
|
626
658
|
exit "$exit_code"
|
|
@@ -641,7 +673,7 @@ preflight_checks() {
|
|
|
641
673
|
local optional_tools=("gh" "claude" "bc" "curl")
|
|
642
674
|
|
|
643
675
|
for tool in "${required_tools[@]}"; do
|
|
644
|
-
if command -v "$tool"
|
|
676
|
+
if command -v "$tool" >/dev/null 2>&1; then
|
|
645
677
|
echo -e " ${GREEN}✓${RESET} $tool"
|
|
646
678
|
else
|
|
647
679
|
echo -e " ${RED}✗${RESET} $tool ${RED}(required)${RESET}"
|
|
@@ -650,7 +682,7 @@ preflight_checks() {
|
|
|
650
682
|
done
|
|
651
683
|
|
|
652
684
|
for tool in "${optional_tools[@]}"; do
|
|
653
|
-
if command -v "$tool"
|
|
685
|
+
if command -v "$tool" >/dev/null 2>&1; then
|
|
654
686
|
echo -e " ${GREEN}✓${RESET} $tool"
|
|
655
687
|
else
|
|
656
688
|
echo -e " ${DIM}○${RESET} $tool ${DIM}(optional — some features disabled)${RESET}"
|
|
@@ -659,7 +691,7 @@ preflight_checks() {
|
|
|
659
691
|
|
|
660
692
|
# 2. Git state
|
|
661
693
|
echo ""
|
|
662
|
-
if git rev-parse --is-inside-work-tree
|
|
694
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
663
695
|
echo -e " ${GREEN}✓${RESET} Inside git repo"
|
|
664
696
|
else
|
|
665
697
|
echo -e " ${RED}✗${RESET} Not inside a git repository"
|
|
@@ -685,7 +717,7 @@ preflight_checks() {
|
|
|
685
717
|
fi
|
|
686
718
|
|
|
687
719
|
# Check if base branch exists
|
|
688
|
-
if git rev-parse --verify "$BASE_BRANCH"
|
|
720
|
+
if git rev-parse --verify "$BASE_BRANCH" >/dev/null 2>&1; then
|
|
689
721
|
echo -e " ${GREEN}✓${RESET} Base branch: $BASE_BRANCH"
|
|
690
722
|
else
|
|
691
723
|
echo -e " ${RED}✗${RESET} Base branch not found: $BASE_BRANCH"
|
|
@@ -693,8 +725,8 @@ preflight_checks() {
|
|
|
693
725
|
fi
|
|
694
726
|
|
|
695
727
|
# 3. GitHub auth (if gh available and not disabled)
|
|
696
|
-
if [[ "$NO_GITHUB" != "true" ]] && command -v gh
|
|
697
|
-
if gh auth status
|
|
728
|
+
if [[ "$NO_GITHUB" != "true" ]] && command -v gh >/dev/null 2>&1; then
|
|
729
|
+
if gh auth status >/dev/null 2>&1; then
|
|
698
730
|
echo -e " ${GREEN}✓${RESET} GitHub authenticated"
|
|
699
731
|
else
|
|
700
732
|
echo -e " ${YELLOW}⚠${RESET} GitHub not authenticated (features disabled)"
|
|
@@ -702,7 +734,7 @@ preflight_checks() {
|
|
|
702
734
|
fi
|
|
703
735
|
|
|
704
736
|
# 4. Claude CLI
|
|
705
|
-
if command -v claude
|
|
737
|
+
if command -v claude >/dev/null 2>&1; then
|
|
706
738
|
echo -e " ${GREEN}✓${RESET} Claude CLI available"
|
|
707
739
|
else
|
|
708
740
|
echo -e " ${RED}✗${RESET} Claude CLI not found — plan/build stages will fail"
|
|
@@ -754,12 +786,12 @@ notify() {
|
|
|
754
786
|
payload=$(jq -n \
|
|
755
787
|
--arg text "${emoji} *${title}*\n${message}" \
|
|
756
788
|
'{text: $text}')
|
|
757
|
-
curl -sf -X POST -H 'Content-Type: application/json' \
|
|
789
|
+
curl -sf --connect-timeout "$(_config_get_int "network.connect_timeout" 10 2>/dev/null || echo 10)" --max-time "$(_config_get_int "network.max_time" 60 2>/dev/null || echo 60)" -X POST -H 'Content-Type: application/json' \
|
|
758
790
|
-d "$payload" "$SLACK_WEBHOOK" >/dev/null 2>&1 || true
|
|
759
791
|
fi
|
|
760
792
|
|
|
761
|
-
# Custom webhook (env var SHIPWRIGHT_WEBHOOK_URL
|
|
762
|
-
local _webhook_url="${SHIPWRIGHT_WEBHOOK_URL
|
|
793
|
+
# Custom webhook (env var SHIPWRIGHT_WEBHOOK_URL)
|
|
794
|
+
local _webhook_url="${SHIPWRIGHT_WEBHOOK_URL:-}"
|
|
763
795
|
if [[ -n "$_webhook_url" ]]; then
|
|
764
796
|
local payload
|
|
765
797
|
payload=$(jq -n \
|
|
@@ -767,7 +799,7 @@ notify() {
|
|
|
767
799
|
--arg level "$level" --arg pipeline "${PIPELINE_NAME:-}" \
|
|
768
800
|
--arg goal "${GOAL:-}" --arg stage "${CURRENT_STAGE_ID:-}" \
|
|
769
801
|
'{title:$title, message:$message, level:$level, pipeline:$pipeline, goal:$goal, stage:$stage}')
|
|
770
|
-
curl -sf -X POST -H 'Content-Type: application/json' \
|
|
802
|
+
curl -sf --connect-timeout 10 --max-time 30 -X POST -H 'Content-Type: application/json' \
|
|
771
803
|
-d "$payload" "$_webhook_url" >/dev/null 2>&1 || true
|
|
772
804
|
fi
|
|
773
805
|
}
|
|
@@ -815,7 +847,7 @@ classify_error() {
|
|
|
815
847
|
elif echo "$log_tail" | grep -qiE 'error\[E[0-9]+\]|error: aborting|FAILED.*compile|build failed|tsc.*error|eslint.*error'; then
|
|
816
848
|
classification="logic"
|
|
817
849
|
# Intelligence fallback: Claude classification for unknown errors
|
|
818
|
-
elif [[ "$classification" == "unknown" ]] && type intelligence_search_memory
|
|
850
|
+
elif [[ "$classification" == "unknown" ]] && type intelligence_search_memory >/dev/null 2>&1 && command -v claude >/dev/null 2>&1; then
|
|
819
851
|
local ai_class
|
|
820
852
|
ai_class=$(claude --print --output-format text -p "Classify this error as exactly one of: infrastructure, configuration, logic, unknown.
|
|
821
853
|
|
|
@@ -882,14 +914,23 @@ run_stage_with_retry() {
|
|
|
882
914
|
return 0
|
|
883
915
|
fi
|
|
884
916
|
|
|
917
|
+
# Capture error_class and error snippet for stage.failed / pipeline.completed events
|
|
918
|
+
local error_class
|
|
919
|
+
error_class=$(classify_error "$stage_id")
|
|
920
|
+
LAST_STAGE_ERROR_CLASS="$error_class"
|
|
921
|
+
LAST_STAGE_ERROR=""
|
|
922
|
+
local _log_file="${ARTIFACTS_DIR}/${stage_id}-results.log"
|
|
923
|
+
[[ ! -f "$_log_file" ]] && _log_file="${ARTIFACTS_DIR}/test-results.log"
|
|
924
|
+
if [[ -f "$_log_file" ]]; then
|
|
925
|
+
LAST_STAGE_ERROR=$(tail -20 "$_log_file" 2>/dev/null | grep -iE 'error|fail|exception|fatal' 2>/dev/null | head -1 | cut -c1-200 || true)
|
|
926
|
+
fi
|
|
927
|
+
|
|
885
928
|
attempt=$((attempt + 1))
|
|
886
929
|
if [[ "$attempt" -gt "$max_retries" ]]; then
|
|
887
930
|
return 1
|
|
888
931
|
fi
|
|
889
932
|
|
|
890
|
-
# Classify
|
|
891
|
-
local error_class
|
|
892
|
-
error_class=$(classify_error "$stage_id")
|
|
933
|
+
# Classify done above; decide whether retry makes sense
|
|
893
934
|
|
|
894
935
|
emit_event "retry.classified" \
|
|
895
936
|
"issue=${ISSUE_NUMBER:-0}" \
|
|
@@ -926,6 +967,15 @@ run_stage_with_retry() {
|
|
|
926
967
|
esac
|
|
927
968
|
prev_error_class="$error_class"
|
|
928
969
|
|
|
970
|
+
if type db_save_reasoning_trace >/dev/null 2>&1; then
|
|
971
|
+
local job_id="${SHIPWRIGHT_PIPELINE_ID:-$$}"
|
|
972
|
+
local error_msg="${LAST_STAGE_ERROR:-$error_class}"
|
|
973
|
+
db_save_reasoning_trace "$job_id" "retry_reasoning" \
|
|
974
|
+
"stage=$stage_id error=$error_msg" \
|
|
975
|
+
"Stage failed, analyzing error pattern before retry" \
|
|
976
|
+
"retry_strategy=self_heal" 0.6 2>/dev/null || true
|
|
977
|
+
fi
|
|
978
|
+
|
|
929
979
|
warn "Stage $stage_id failed (attempt $attempt/$((max_retries + 1)), class: $error_class) — retrying..."
|
|
930
980
|
# Exponential backoff with jitter to avoid thundering herd
|
|
931
981
|
local backoff=$((2 ** attempt))
|
|
@@ -951,9 +1001,9 @@ self_healing_build_test() {
|
|
|
951
1001
|
local prev_fail_count=0 zero_convergence_streak=0
|
|
952
1002
|
|
|
953
1003
|
# Vitals-driven adaptive limit (preferred over static BUILD_TEST_RETRIES)
|
|
954
|
-
if type pipeline_adaptive_limit
|
|
1004
|
+
if type pipeline_adaptive_limit >/dev/null 2>&1; then
|
|
955
1005
|
local _vitals_json=""
|
|
956
|
-
if type pipeline_compute_vitals
|
|
1006
|
+
if type pipeline_compute_vitals >/dev/null 2>&1; then
|
|
957
1007
|
_vitals_json=$(pipeline_compute_vitals "$STATE_FILE" "$ARTIFACTS_DIR" "${ISSUE_NUMBER:-}" 2>/dev/null) || true
|
|
958
1008
|
fi
|
|
959
1009
|
local vitals_limit
|
|
@@ -968,7 +1018,7 @@ self_healing_build_test() {
|
|
|
968
1018
|
"vitals_limit=$vitals_limit"
|
|
969
1019
|
fi
|
|
970
1020
|
# Fallback: intelligence-based adaptive limits
|
|
971
|
-
elif type composer_estimate_iterations
|
|
1021
|
+
elif type composer_estimate_iterations >/dev/null 2>&1; then
|
|
972
1022
|
local estimated
|
|
973
1023
|
estimated=$(composer_estimate_iterations \
|
|
974
1024
|
"${INTELLIGENCE_ANALYSIS:-{}}" \
|
|
@@ -1022,7 +1072,7 @@ self_healing_build_test() {
|
|
|
1022
1072
|
if [[ "$cycle" -gt 1 && -n "$last_test_error" ]]; then
|
|
1023
1073
|
# Query memory for known fixes
|
|
1024
1074
|
local _memory_fix=""
|
|
1025
|
-
if type memory_closed_loop_inject
|
|
1075
|
+
if type memory_closed_loop_inject >/dev/null 2>&1; then
|
|
1026
1076
|
local _error_sig_short
|
|
1027
1077
|
_error_sig_short=$(echo "$last_test_error" | head -3 || echo "")
|
|
1028
1078
|
_memory_fix=$(memory_closed_loop_inject "$_error_sig_short" 2>/dev/null) || true
|
|
@@ -1053,7 +1103,7 @@ Focus on fixing the failing tests while keeping all passing tests working."
|
|
|
1053
1103
|
local timing
|
|
1054
1104
|
timing=$(get_stage_timing "build")
|
|
1055
1105
|
success "Stage ${BOLD}build${RESET} complete ${DIM}(${timing})${RESET}"
|
|
1056
|
-
if type pipeline_emit_progress_snapshot
|
|
1106
|
+
if type pipeline_emit_progress_snapshot >/dev/null 2>&1 && [[ -n "${ISSUE_NUMBER:-}" ]]; then
|
|
1057
1107
|
local _diff_count
|
|
1058
1108
|
_diff_count=$(git diff --stat HEAD~1 2>/dev/null | tail -1 | grep -oE '[0-9]+' | head -1) || true
|
|
1059
1109
|
local _snap_files _snap_error
|
|
@@ -1078,7 +1128,7 @@ Focus on fixing the failing tests while keeping all passing tests working."
|
|
|
1078
1128
|
local timing
|
|
1079
1129
|
timing=$(get_stage_timing "build")
|
|
1080
1130
|
success "Stage ${BOLD}build${RESET} complete ${DIM}(${timing})${RESET}"
|
|
1081
|
-
if type pipeline_emit_progress_snapshot
|
|
1131
|
+
if type pipeline_emit_progress_snapshot >/dev/null 2>&1 && [[ -n "${ISSUE_NUMBER:-}" ]]; then
|
|
1082
1132
|
local _diff_count
|
|
1083
1133
|
_diff_count=$(git diff --stat HEAD~1 2>/dev/null | tail -1 | grep -oE '[0-9]+' | head -1) || true
|
|
1084
1134
|
local _snap_files _snap_error
|
|
@@ -1109,7 +1159,7 @@ Focus on fixing the failing tests while keeping all passing tests working."
|
|
|
1109
1159
|
emit_event "convergence.tests_passed" \
|
|
1110
1160
|
"issue=${ISSUE_NUMBER:-0}" \
|
|
1111
1161
|
"cycle=$cycle"
|
|
1112
|
-
if type pipeline_emit_progress_snapshot
|
|
1162
|
+
if type pipeline_emit_progress_snapshot >/dev/null 2>&1 && [[ -n "${ISSUE_NUMBER:-}" ]]; then
|
|
1113
1163
|
local _diff_count
|
|
1114
1164
|
_diff_count=$(git diff --stat HEAD~1 2>/dev/null | tail -1 | grep -oE '[0-9]+' | head -1) || true
|
|
1115
1165
|
local _snap_files _snap_error
|
|
@@ -1235,6 +1285,9 @@ auto_rebase() {
|
|
|
1235
1285
|
}
|
|
1236
1286
|
|
|
1237
1287
|
run_pipeline() {
|
|
1288
|
+
# Rotate event log if needed (standalone mode)
|
|
1289
|
+
rotate_event_log_if_needed
|
|
1290
|
+
|
|
1238
1291
|
local stages
|
|
1239
1292
|
stages=$(jq -c '.stages[]' "$PIPELINE_CONFIG")
|
|
1240
1293
|
|
|
@@ -1329,6 +1382,10 @@ run_pipeline() {
|
|
|
1329
1382
|
|
|
1330
1383
|
# Self-healing build→test loop: when we hit build, run both together
|
|
1331
1384
|
if [[ "$id" == "build" && "$use_self_healing" == "true" ]]; then
|
|
1385
|
+
# TDD: generate tests before build when enabled
|
|
1386
|
+
if [[ "${TDD_ENABLED:-false}" == "true" || "${PIPELINE_TDD:-}" == "true" ]]; then
|
|
1387
|
+
stage_test_first || true
|
|
1388
|
+
fi
|
|
1332
1389
|
# Gate check for build
|
|
1333
1390
|
local build_gate
|
|
1334
1391
|
build_gate=$(echo "$stage" | jq -r '.gate')
|
|
@@ -1362,6 +1419,11 @@ run_pipeline() {
|
|
|
1362
1419
|
continue
|
|
1363
1420
|
fi
|
|
1364
1421
|
|
|
1422
|
+
# TDD: generate tests before build when enabled (non-self-healing path)
|
|
1423
|
+
if [[ "$id" == "build" && "$use_self_healing" != "true" ]] && [[ "${TDD_ENABLED:-false}" == "true" || "${PIPELINE_TDD:-}" == "true" ]]; then
|
|
1424
|
+
stage_test_first || true
|
|
1425
|
+
fi
|
|
1426
|
+
|
|
1365
1427
|
# Skip test if already handled by self-healing loop
|
|
1366
1428
|
if [[ "$id" == "test" && "$use_self_healing" == "true" ]]; then
|
|
1367
1429
|
stage_status=$(get_stage_status "test")
|
|
@@ -1401,52 +1463,59 @@ run_pipeline() {
|
|
|
1401
1463
|
fi
|
|
1402
1464
|
fi
|
|
1403
1465
|
|
|
1404
|
-
# Intelligence: per-stage model routing
|
|
1405
|
-
|
|
1466
|
+
# Intelligence: per-stage model routing (UCB1 when DB has data, else A/B testing)
|
|
1467
|
+
local recommended_model="" from_ucb1=false
|
|
1468
|
+
if type ucb1_select_model >/dev/null 2>&1; then
|
|
1469
|
+
recommended_model=$(ucb1_select_model "$id" 2>/dev/null || echo "")
|
|
1470
|
+
[[ -n "$recommended_model" ]] && from_ucb1=true
|
|
1471
|
+
fi
|
|
1472
|
+
if [[ -z "$recommended_model" ]] && type intelligence_recommend_model >/dev/null 2>&1; then
|
|
1406
1473
|
local stage_complexity="${INTELLIGENCE_COMPLEXITY:-5}"
|
|
1407
1474
|
local budget_remaining=""
|
|
1408
1475
|
if [[ -x "$SCRIPT_DIR/sw-cost.sh" ]]; then
|
|
1409
1476
|
budget_remaining=$(bash "$SCRIPT_DIR/sw-cost.sh" remaining-budget 2>/dev/null || echo "")
|
|
1410
1477
|
fi
|
|
1411
|
-
local
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1478
|
+
local recommended_json
|
|
1479
|
+
recommended_json=$(intelligence_recommend_model "$id" "$stage_complexity" "$budget_remaining" 2>/dev/null || echo "")
|
|
1480
|
+
recommended_model=$(echo "$recommended_json" | jq -r '.model // empty' 2>/dev/null || echo "")
|
|
1481
|
+
fi
|
|
1482
|
+
if [[ -n "$recommended_model" && "$recommended_model" != "null" ]]; then
|
|
1483
|
+
if [[ "$from_ucb1" == "true" ]]; then
|
|
1484
|
+
# UCB1 already balances exploration/exploitation — use directly
|
|
1485
|
+
export CLAUDE_MODEL="$recommended_model"
|
|
1486
|
+
emit_event "intelligence.model_ucb1" \
|
|
1487
|
+
"issue=${ISSUE_NUMBER:-0}" \
|
|
1488
|
+
"stage=$id" \
|
|
1489
|
+
"model=$recommended_model"
|
|
1490
|
+
else
|
|
1491
|
+
# A/B testing for intelligence recommendation
|
|
1492
|
+
local ab_ratio=20
|
|
1416
1493
|
local daemon_cfg="${PROJECT_ROOT}/.claude/daemon-config.json"
|
|
1417
1494
|
if [[ -f "$daemon_cfg" ]]; then
|
|
1418
1495
|
local cfg_ratio
|
|
1419
1496
|
cfg_ratio=$(jq -r '.intelligence.ab_test_ratio // 0.2' "$daemon_cfg" 2>/dev/null || echo "0.2")
|
|
1420
|
-
# Convert ratio (0.0-1.0) to percentage (0-100)
|
|
1421
1497
|
ab_ratio=$(awk -v r="$cfg_ratio" 'BEGIN{printf "%d", r * 100}' 2>/dev/null || echo "20")
|
|
1422
1498
|
fi
|
|
1423
1499
|
|
|
1424
|
-
# Check if we have enough data points to graduate from A/B testing
|
|
1425
1500
|
local routing_file="${HOME}/.shipwright/optimization/model-routing.json"
|
|
1426
1501
|
local use_recommended=false
|
|
1427
1502
|
local ab_group="control"
|
|
1428
1503
|
|
|
1429
1504
|
if [[ -f "$routing_file" ]]; then
|
|
1430
|
-
local stage_samples
|
|
1431
|
-
stage_samples=$(jq -r --arg s "$id" '.[$s].sonnet_samples // 0' "$routing_file" 2>/dev/null || echo "0")
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
if [[ "$total_samples" -ge 50 ]]; then
|
|
1436
|
-
# Enough data — use optimizer's recommendation as default
|
|
1505
|
+
local stage_samples total_samples
|
|
1506
|
+
stage_samples=$(jq -r --arg s "$id" '.routes[$s].sonnet_samples // .[$s].sonnet_samples // 0' "$routing_file" 2>/dev/null || echo "0")
|
|
1507
|
+
total_samples=$(jq -r --arg s "$id" '((.routes[$s].sonnet_samples // .[$s].sonnet_samples // 0) + (.routes[$s].opus_samples // .[$s].opus_samples // 0))' "$routing_file" 2>/dev/null || echo "0")
|
|
1508
|
+
if [[ "${total_samples:-0}" -ge 50 ]]; then
|
|
1437
1509
|
use_recommended=true
|
|
1438
1510
|
ab_group="graduated"
|
|
1439
1511
|
fi
|
|
1440
1512
|
fi
|
|
1441
1513
|
|
|
1442
1514
|
if [[ "$use_recommended" != "true" ]]; then
|
|
1443
|
-
# A/B test: RANDOM % 100 < ab_ratio → use recommended
|
|
1444
1515
|
local roll=$((RANDOM % 100))
|
|
1445
1516
|
if [[ "$roll" -lt "$ab_ratio" ]]; then
|
|
1446
1517
|
use_recommended=true
|
|
1447
1518
|
ab_group="experiment"
|
|
1448
|
-
else
|
|
1449
|
-
ab_group="control"
|
|
1450
1519
|
fi
|
|
1451
1520
|
fi
|
|
1452
1521
|
|
|
@@ -1475,7 +1544,7 @@ run_pipeline() {
|
|
|
1475
1544
|
emit_event "stage.started" "issue=${ISSUE_NUMBER:-0}" "stage=$id"
|
|
1476
1545
|
|
|
1477
1546
|
# Mark GitHub Check Run as in-progress
|
|
1478
|
-
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update
|
|
1547
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update >/dev/null 2>&1; then
|
|
1479
1548
|
gh_checks_stage_update "$id" "in_progress" "" "Stage $id started" 2>/dev/null || true
|
|
1480
1549
|
fi
|
|
1481
1550
|
|
|
@@ -1491,7 +1560,13 @@ run_pipeline() {
|
|
|
1491
1560
|
timing=$(get_stage_timing "$id")
|
|
1492
1561
|
stage_dur_s=$(( $(now_epoch) - stage_start_epoch ))
|
|
1493
1562
|
success "Stage ${BOLD}$id${RESET} complete ${DIM}(${timing})${RESET}"
|
|
1494
|
-
emit_event "stage.completed" "issue=${ISSUE_NUMBER:-0}" "stage=$id" "duration_s=$stage_dur_s"
|
|
1563
|
+
emit_event "stage.completed" "issue=${ISSUE_NUMBER:-0}" "stage=$id" "duration_s=$stage_dur_s" "result=success"
|
|
1564
|
+
# Emit vitals snapshot on every stage transition (not just build/test)
|
|
1565
|
+
if type pipeline_emit_progress_snapshot >/dev/null 2>&1 && [[ -n "${ISSUE_NUMBER:-}" ]]; then
|
|
1566
|
+
pipeline_emit_progress_snapshot "${ISSUE_NUMBER}" "$id" "0" "0" "0" "" 2>/dev/null || true
|
|
1567
|
+
fi
|
|
1568
|
+
# Record model outcome for UCB1 learning
|
|
1569
|
+
type record_model_outcome >/dev/null 2>&1 && record_model_outcome "$stage_model_used" "$id" 1 "$stage_dur_s" 0 2>/dev/null || true
|
|
1495
1570
|
# Broadcast discovery for cross-pipeline learning
|
|
1496
1571
|
if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
|
|
1497
1572
|
local _disc_cat _disc_patterns _disc_text
|
|
@@ -1514,9 +1589,20 @@ run_pipeline() {
|
|
|
1514
1589
|
stage_dur_s=$(( $(now_epoch) - stage_start_epoch ))
|
|
1515
1590
|
error "Pipeline failed at stage: ${BOLD}$id${RESET}"
|
|
1516
1591
|
update_status "failed" "$id"
|
|
1517
|
-
emit_event "stage.failed"
|
|
1592
|
+
emit_event "stage.failed" \
|
|
1593
|
+
"issue=${ISSUE_NUMBER:-0}" \
|
|
1594
|
+
"stage=$id" \
|
|
1595
|
+
"duration_s=$stage_dur_s" \
|
|
1596
|
+
"error=${LAST_STAGE_ERROR:-unknown}" \
|
|
1597
|
+
"error_class=${LAST_STAGE_ERROR_CLASS:-unknown}"
|
|
1598
|
+
# Emit vitals snapshot on failure too
|
|
1599
|
+
if type pipeline_emit_progress_snapshot >/dev/null 2>&1 && [[ -n "${ISSUE_NUMBER:-}" ]]; then
|
|
1600
|
+
pipeline_emit_progress_snapshot "${ISSUE_NUMBER}" "$id" "0" "0" "0" "${LAST_STAGE_ERROR:-unknown}" 2>/dev/null || true
|
|
1601
|
+
fi
|
|
1518
1602
|
# Log model used for prediction feedback
|
|
1519
1603
|
echo "${id}|${stage_model_used}|false" >> "${ARTIFACTS_DIR}/model-routing.log"
|
|
1604
|
+
# Record model outcome for UCB1 learning
|
|
1605
|
+
type record_model_outcome >/dev/null 2>&1 && record_model_outcome "$stage_model_used" "$id" 0 "$stage_dur_s" 0 2>/dev/null || true
|
|
1520
1606
|
# Cancel any remaining in_progress check runs
|
|
1521
1607
|
pipeline_cancel_check_runs 2>/dev/null || true
|
|
1522
1608
|
return 1
|
|
@@ -1525,6 +1611,11 @@ run_pipeline() {
|
|
|
1525
1611
|
|
|
1526
1612
|
# Pipeline complete!
|
|
1527
1613
|
update_status "complete" ""
|
|
1614
|
+
PIPELINE_STAGES_PASSED="$completed"
|
|
1615
|
+
PIPELINE_SLOWEST_STAGE=""
|
|
1616
|
+
if type get_slowest_stage >/dev/null 2>&1; then
|
|
1617
|
+
PIPELINE_SLOWEST_STAGE=$(get_slowest_stage 2>/dev/null || true)
|
|
1618
|
+
fi
|
|
1528
1619
|
local total_dur=""
|
|
1529
1620
|
if [[ -n "$PIPELINE_START_EPOCH" ]]; then
|
|
1530
1621
|
total_dur=$(format_duration $(( $(now_epoch) - PIPELINE_START_EPOCH )))
|
|
@@ -1568,7 +1659,7 @@ run_pipeline() {
|
|
|
1568
1659
|
pipeline_post_completion_cleanup() {
|
|
1569
1660
|
local cleaned=0
|
|
1570
1661
|
|
|
1571
|
-
# 1. Clear checkpoints (they only matter for resume; pipeline is done)
|
|
1662
|
+
# 1. Clear checkpoints and context files (they only matter for resume; pipeline is done)
|
|
1572
1663
|
if [[ -d "${ARTIFACTS_DIR}/checkpoints" ]]; then
|
|
1573
1664
|
local cp_count=0
|
|
1574
1665
|
local cp_file
|
|
@@ -1577,6 +1668,11 @@ pipeline_post_completion_cleanup() {
|
|
|
1577
1668
|
rm -f "$cp_file"
|
|
1578
1669
|
cp_count=$((cp_count + 1))
|
|
1579
1670
|
done
|
|
1671
|
+
for cp_file in "${ARTIFACTS_DIR}/checkpoints"/*-claude-context.json; do
|
|
1672
|
+
[[ -f "$cp_file" ]] || continue
|
|
1673
|
+
rm -f "$cp_file"
|
|
1674
|
+
cp_count=$((cp_count + 1))
|
|
1675
|
+
done
|
|
1580
1676
|
if [[ "$cp_count" -gt 0 ]]; then
|
|
1581
1677
|
cleaned=$((cleaned + cp_count))
|
|
1582
1678
|
fi
|
|
@@ -1621,7 +1717,7 @@ pipeline_cancel_check_runs() {
|
|
|
1621
1717
|
return
|
|
1622
1718
|
fi
|
|
1623
1719
|
|
|
1624
|
-
if ! type gh_checks_stage_update
|
|
1720
|
+
if ! type gh_checks_stage_update >/dev/null 2>&1; then
|
|
1625
1721
|
return
|
|
1626
1722
|
fi
|
|
1627
1723
|
|
|
@@ -1673,7 +1769,7 @@ pipeline_setup_worktree() {
|
|
|
1673
1769
|
|
|
1674
1770
|
# Store original dir for cleanup, then cd into worktree
|
|
1675
1771
|
ORIGINAL_REPO_DIR="$(pwd)"
|
|
1676
|
-
cd "$worktree_path"
|
|
1772
|
+
cd "$worktree_path" || { error "Failed to cd into worktree: $worktree_path"; return 1; }
|
|
1677
1773
|
CLEANUP_WORKTREE=true
|
|
1678
1774
|
|
|
1679
1775
|
success "Worktree ready: ${CYAN}${worktree_path}${RESET} (branch: ${branch_name})"
|
|
@@ -1807,7 +1903,7 @@ run_dry_run() {
|
|
|
1807
1903
|
local optional_tools=("gh" "claude" "bc")
|
|
1808
1904
|
|
|
1809
1905
|
for tool in "${required_tools[@]}"; do
|
|
1810
|
-
if command -v "$tool"
|
|
1906
|
+
if command -v "$tool" >/dev/null 2>&1; then
|
|
1811
1907
|
echo -e " ${GREEN}✓${RESET} $tool"
|
|
1812
1908
|
else
|
|
1813
1909
|
echo -e " ${RED}✗${RESET} $tool ${RED}(required)${RESET}"
|
|
@@ -1816,7 +1912,7 @@ run_dry_run() {
|
|
|
1816
1912
|
done
|
|
1817
1913
|
|
|
1818
1914
|
for tool in "${optional_tools[@]}"; do
|
|
1819
|
-
if command -v "$tool"
|
|
1915
|
+
if command -v "$tool" >/dev/null 2>&1; then
|
|
1820
1916
|
echo -e " ${GREEN}✓${RESET} $tool"
|
|
1821
1917
|
else
|
|
1822
1918
|
echo -e " ${DIM}○${RESET} $tool"
|
|
@@ -1825,15 +1921,17 @@ run_dry_run() {
|
|
|
1825
1921
|
|
|
1826
1922
|
echo ""
|
|
1827
1923
|
|
|
1828
|
-
# Cost estimation
|
|
1924
|
+
# Cost estimation: use historical averages from past pipelines when available
|
|
1829
1925
|
echo -e "${BLUE}${BOLD}━━━ Estimated Resource Usage ━━━${RESET}"
|
|
1830
1926
|
echo ""
|
|
1831
1927
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1928
|
+
local stages_json
|
|
1929
|
+
stages_json=$(jq '[.stages[] | select(.enabled == true)]' "$PIPELINE_CONFIG" 2>/dev/null || echo "[]")
|
|
1930
|
+
local est
|
|
1931
|
+
est=$(estimate_pipeline_cost "$stages_json")
|
|
1834
1932
|
local input_tokens_estimate output_tokens_estimate
|
|
1835
|
-
input_tokens_estimate=$(
|
|
1836
|
-
output_tokens_estimate=$(
|
|
1933
|
+
input_tokens_estimate=$(echo "$est" | jq -r '.input_tokens // 0')
|
|
1934
|
+
output_tokens_estimate=$(echo "$est" | jq -r '.output_tokens // 0')
|
|
1837
1935
|
|
|
1838
1936
|
# Calculate cost based on selected model
|
|
1839
1937
|
local input_rate output_rate input_cost output_cost total_cost
|
|
@@ -1848,11 +1946,11 @@ run_dry_run() {
|
|
|
1848
1946
|
echo -e " ${BOLD}Estimated Input Tokens:${RESET} ~$input_tokens_estimate"
|
|
1849
1947
|
echo -e " ${BOLD}Estimated Output Tokens:${RESET} ~$output_tokens_estimate"
|
|
1850
1948
|
echo -e " ${BOLD}Model Cost Rate:${RESET} $stage_model"
|
|
1851
|
-
echo -e " ${BOLD}Estimated Cost:${RESET} \$$total_cost USD
|
|
1949
|
+
echo -e " ${BOLD}Estimated Cost:${RESET} \$$total_cost USD"
|
|
1852
1950
|
echo ""
|
|
1853
1951
|
|
|
1854
1952
|
# Validate composed pipeline if intelligence is enabled
|
|
1855
|
-
if [[ -f "$ARTIFACTS_DIR/composed-pipeline.json" ]] && type composer_validate_pipeline
|
|
1953
|
+
if [[ -f "$ARTIFACTS_DIR/composed-pipeline.json" ]] && type composer_validate_pipeline >/dev/null 2>&1; then
|
|
1856
1954
|
echo -e "${BLUE}${BOLD}━━━ Intelligence-Composed Pipeline ━━━${RESET}"
|
|
1857
1955
|
echo ""
|
|
1858
1956
|
|
|
@@ -1877,6 +1975,100 @@ run_dry_run() {
|
|
|
1877
1975
|
return 0
|
|
1878
1976
|
}
|
|
1879
1977
|
|
|
1978
|
+
# ─── Reasoning Trace Generation ──────────────────────────────────────────────
|
|
1979
|
+
# Multi-step autonomous reasoning traces for pipeline start (before stages run)
|
|
1980
|
+
|
|
1981
|
+
generate_reasoning_trace() {
|
|
1982
|
+
local job_id="${SHIPWRIGHT_PIPELINE_ID:-$$}"
|
|
1983
|
+
local issue="${ISSUE_NUMBER:-}"
|
|
1984
|
+
local goal="${GOAL:-}"
|
|
1985
|
+
|
|
1986
|
+
# Step 1: Analyze issue complexity and risk
|
|
1987
|
+
local complexity="medium"
|
|
1988
|
+
local risk_score=50
|
|
1989
|
+
if [[ -n "$issue" ]] && type intelligence_analyze_issue >/dev/null 2>&1; then
|
|
1990
|
+
local issue_json analysis
|
|
1991
|
+
issue_json=$(gh issue view "$issue" --json number,title,body,labels 2>/dev/null || echo "{}")
|
|
1992
|
+
if [[ -n "$issue_json" && "$issue_json" != "{}" ]]; then
|
|
1993
|
+
analysis=$(intelligence_analyze_issue "$issue_json" 2>/dev/null || echo "")
|
|
1994
|
+
if [[ -n "$analysis" ]]; then
|
|
1995
|
+
local comp_num
|
|
1996
|
+
comp_num=$(echo "$analysis" | jq -r '.complexity // 5' 2>/dev/null || echo "5")
|
|
1997
|
+
if [[ "$comp_num" -le 3 ]]; then
|
|
1998
|
+
complexity="low"
|
|
1999
|
+
elif [[ "$comp_num" -le 6 ]]; then
|
|
2000
|
+
complexity="medium"
|
|
2001
|
+
else
|
|
2002
|
+
complexity="high"
|
|
2003
|
+
fi
|
|
2004
|
+
risk_score=$((100 - $(echo "$analysis" | jq -r '.success_probability // 50' 2>/dev/null || echo "50")))
|
|
2005
|
+
fi
|
|
2006
|
+
fi
|
|
2007
|
+
elif [[ -n "$goal" ]]; then
|
|
2008
|
+
issue_json=$(jq -n --arg title "${goal}" --arg body "" '{title: $title, body: $body, labels: []}')
|
|
2009
|
+
if type intelligence_analyze_issue >/dev/null 2>&1; then
|
|
2010
|
+
analysis=$(intelligence_analyze_issue "$issue_json" 2>/dev/null || echo "")
|
|
2011
|
+
if [[ -n "$analysis" ]]; then
|
|
2012
|
+
local comp_num
|
|
2013
|
+
comp_num=$(echo "$analysis" | jq -r '.complexity // 5' 2>/dev/null || echo "5")
|
|
2014
|
+
if [[ "$comp_num" -le 3 ]]; then complexity="low"; elif [[ "$comp_num" -le 6 ]]; then complexity="medium"; else complexity="high"; fi
|
|
2015
|
+
risk_score=$((100 - $(echo "$analysis" | jq -r '.success_probability // 50' 2>/dev/null || echo "50")))
|
|
2016
|
+
fi
|
|
2017
|
+
fi
|
|
2018
|
+
fi
|
|
2019
|
+
|
|
2020
|
+
# Step 2: Query similar past issues
|
|
2021
|
+
local similar_context=""
|
|
2022
|
+
if type memory_semantic_search >/dev/null 2>&1 && [[ -n "$goal" ]]; then
|
|
2023
|
+
similar_context=$(memory_semantic_search "$goal" "" 3 2>/dev/null || echo "")
|
|
2024
|
+
fi
|
|
2025
|
+
|
|
2026
|
+
# Step 3: Select template using Thompson sampling
|
|
2027
|
+
local selected_template="${PIPELINE_TEMPLATE:-}"
|
|
2028
|
+
if [[ -z "$selected_template" ]] && type thompson_select_template >/dev/null 2>&1; then
|
|
2029
|
+
selected_template=$(thompson_select_template "$complexity" 2>/dev/null || echo "standard")
|
|
2030
|
+
fi
|
|
2031
|
+
[[ -z "$selected_template" ]] && selected_template="standard"
|
|
2032
|
+
|
|
2033
|
+
# Step 4: Predict failure modes from memory
|
|
2034
|
+
local failure_predictions=""
|
|
2035
|
+
if type memory_semantic_search >/dev/null 2>&1 && [[ -n "$goal" ]]; then
|
|
2036
|
+
failure_predictions=$(memory_semantic_search "failure error $goal" "" 3 2>/dev/null || echo "")
|
|
2037
|
+
fi
|
|
2038
|
+
|
|
2039
|
+
# Save reasoning traces to DB
|
|
2040
|
+
if type db_save_reasoning_trace >/dev/null 2>&1; then
|
|
2041
|
+
db_save_reasoning_trace "$job_id" "complexity_analysis" \
|
|
2042
|
+
"issue=$issue goal=$goal" \
|
|
2043
|
+
"Analyzed complexity=$complexity risk=$risk_score" \
|
|
2044
|
+
"complexity=$complexity risk_score=$risk_score" 0.7 2>/dev/null || true
|
|
2045
|
+
|
|
2046
|
+
db_save_reasoning_trace "$job_id" "template_selection" \
|
|
2047
|
+
"complexity=$complexity historical_outcomes" \
|
|
2048
|
+
"Thompson sampling over historical success rates" \
|
|
2049
|
+
"template=$selected_template" 0.8 2>/dev/null || true
|
|
2050
|
+
|
|
2051
|
+
if [[ -n "$similar_context" && "$similar_context" != "[]" ]]; then
|
|
2052
|
+
db_save_reasoning_trace "$job_id" "similar_issues" \
|
|
2053
|
+
"$goal" \
|
|
2054
|
+
"Found similar past issues for context injection" \
|
|
2055
|
+
"$similar_context" 0.6 2>/dev/null || true
|
|
2056
|
+
fi
|
|
2057
|
+
|
|
2058
|
+
if [[ -n "$failure_predictions" && "$failure_predictions" != "[]" ]]; then
|
|
2059
|
+
db_save_reasoning_trace "$job_id" "failure_prediction" \
|
|
2060
|
+
"$goal" \
|
|
2061
|
+
"Predicted potential failure modes from history" \
|
|
2062
|
+
"$failure_predictions" 0.5 2>/dev/null || true
|
|
2063
|
+
fi
|
|
2064
|
+
fi
|
|
2065
|
+
|
|
2066
|
+
# Export for use by pipeline stages
|
|
2067
|
+
[[ -n "$selected_template" && -z "${PIPELINE_TEMPLATE:-}" ]] && export PIPELINE_TEMPLATE="$selected_template"
|
|
2068
|
+
|
|
2069
|
+
emit_event "reasoning.trace" "job_id=$job_id" "complexity=$complexity" "risk=$risk_score" "template=${selected_template:-standard}" 2>/dev/null || true
|
|
2070
|
+
}
|
|
2071
|
+
|
|
1880
2072
|
# ─── Subcommands ────────────────────────────────────────────────────────────
|
|
1881
2073
|
|
|
1882
2074
|
pipeline_start() {
|
|
@@ -1898,6 +2090,13 @@ pipeline_start() {
|
|
|
1898
2090
|
info "Using repository: $ORIGINAL_REPO_DIR"
|
|
1899
2091
|
fi
|
|
1900
2092
|
|
|
2093
|
+
# Bootstrap optimization & memory if cold start (before first intelligence use)
|
|
2094
|
+
if [[ -f "$SCRIPT_DIR/lib/bootstrap.sh" ]]; then
|
|
2095
|
+
source "$SCRIPT_DIR/lib/bootstrap.sh"
|
|
2096
|
+
[[ ! -f "$HOME/.shipwright/optimization/iteration-model.json" ]] && bootstrap_optimization 2>/dev/null || true
|
|
2097
|
+
[[ ! -f "$HOME/.shipwright/memory/patterns.json" ]] && bootstrap_memory 2>/dev/null || true
|
|
2098
|
+
fi
|
|
2099
|
+
|
|
1901
2100
|
if [[ -z "$GOAL" && -z "$ISSUE_NUMBER" ]]; then
|
|
1902
2101
|
error "Must provide --goal or --issue"
|
|
1903
2102
|
echo -e " Example: ${DIM}shipwright pipeline start --goal \"Add JWT auth\"${RESET}"
|
|
@@ -1905,7 +2104,7 @@ pipeline_start() {
|
|
|
1905
2104
|
exit 1
|
|
1906
2105
|
fi
|
|
1907
2106
|
|
|
1908
|
-
if ! command -v jq
|
|
2107
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
1909
2108
|
error "jq is required. Install it: brew install jq"
|
|
1910
2109
|
exit 1
|
|
1911
2110
|
fi
|
|
@@ -1923,6 +2122,26 @@ pipeline_start() {
|
|
|
1923
2122
|
|
|
1924
2123
|
setup_dirs
|
|
1925
2124
|
|
|
2125
|
+
# Acquire durable lock to prevent concurrent pipelines on the same issue/goal
|
|
2126
|
+
_PIPELINE_LOCK_ID=""
|
|
2127
|
+
if type acquire_lock >/dev/null 2>&1; then
|
|
2128
|
+
_PIPELINE_LOCK_ID="pipeline-${ISSUE_NUMBER:-goal-$$}"
|
|
2129
|
+
if ! acquire_lock "$_PIPELINE_LOCK_ID" 5 2>/dev/null; then
|
|
2130
|
+
error "Another pipeline is already running for this issue/goal"
|
|
2131
|
+
echo -e " Wait for it to finish, or remove stale lock:"
|
|
2132
|
+
echo -e " ${DIM}rm -rf ~/.shipwright/durable/locks/${_PIPELINE_LOCK_ID}.lock${RESET}"
|
|
2133
|
+
_PIPELINE_LOCK_ID=""
|
|
2134
|
+
exit 1
|
|
2135
|
+
fi
|
|
2136
|
+
fi
|
|
2137
|
+
|
|
2138
|
+
# Generate reasoning trace (complexity analysis, template selection, failure predictions)
|
|
2139
|
+
local user_specified_pipeline="$PIPELINE_NAME"
|
|
2140
|
+
generate_reasoning_trace 2>/dev/null || true
|
|
2141
|
+
if [[ -n "${PIPELINE_TEMPLATE:-}" && "$user_specified_pipeline" == "standard" ]]; then
|
|
2142
|
+
PIPELINE_NAME="$PIPELINE_TEMPLATE"
|
|
2143
|
+
fi
|
|
2144
|
+
|
|
1926
2145
|
# Check for existing pipeline
|
|
1927
2146
|
if [[ -f "$STATE_FILE" ]]; then
|
|
1928
2147
|
local existing_status
|
|
@@ -1942,7 +2161,87 @@ pipeline_start() {
|
|
|
1942
2161
|
gh_init
|
|
1943
2162
|
|
|
1944
2163
|
load_pipeline_config
|
|
1945
|
-
|
|
2164
|
+
|
|
2165
|
+
# Checkpoint resume: when --resume is passed, try DB first, then file-based
|
|
2166
|
+
checkpoint_stage=""
|
|
2167
|
+
checkpoint_iteration=0
|
|
2168
|
+
if $RESUME_FROM_CHECKPOINT && type db_load_checkpoint >/dev/null 2>&1; then
|
|
2169
|
+
local saved_checkpoint
|
|
2170
|
+
saved_checkpoint=$(db_load_checkpoint "pipeline-${SHIPWRIGHT_PIPELINE_ID:-$$}" 2>/dev/null || echo "")
|
|
2171
|
+
if [[ -n "$saved_checkpoint" ]]; then
|
|
2172
|
+
checkpoint_stage=$(echo "$saved_checkpoint" | jq -r '.stage // ""' 2>/dev/null || echo "")
|
|
2173
|
+
if [[ -n "$checkpoint_stage" ]]; then
|
|
2174
|
+
info "Resuming from DB checkpoint: stage=$checkpoint_stage"
|
|
2175
|
+
checkpoint_iteration=$(echo "$saved_checkpoint" | jq -r '.iteration // 0' 2>/dev/null || echo "0")
|
|
2176
|
+
# Build COMPLETED_STAGES: all enabled stages before checkpoint_stage
|
|
2177
|
+
local enabled_list before_list=""
|
|
2178
|
+
enabled_list=$(jq -r '.stages[] | select(.enabled == true) | .id' "$PIPELINE_CONFIG" 2>/dev/null) || true
|
|
2179
|
+
local s
|
|
2180
|
+
while IFS= read -r s; do
|
|
2181
|
+
[[ -z "$s" ]] && continue
|
|
2182
|
+
if [[ "$s" == "$checkpoint_stage" ]]; then
|
|
2183
|
+
break
|
|
2184
|
+
fi
|
|
2185
|
+
[[ -n "$before_list" ]] && before_list="${before_list},${s}" || before_list="$s"
|
|
2186
|
+
done <<< "$enabled_list"
|
|
2187
|
+
if [[ -n "$before_list" ]]; then
|
|
2188
|
+
COMPLETED_STAGES="${before_list}"
|
|
2189
|
+
SELF_HEAL_COUNT="${checkpoint_iteration}"
|
|
2190
|
+
fi
|
|
2191
|
+
fi
|
|
2192
|
+
fi
|
|
2193
|
+
fi
|
|
2194
|
+
if $RESUME_FROM_CHECKPOINT && [[ -z "$checkpoint_stage" ]] && [[ -d "${ARTIFACTS_DIR}/checkpoints" ]]; then
|
|
2195
|
+
local cp_dir="${ARTIFACTS_DIR}/checkpoints"
|
|
2196
|
+
local latest_cp="" latest_mtime=0
|
|
2197
|
+
local f
|
|
2198
|
+
for f in "$cp_dir"/*-checkpoint.json; do
|
|
2199
|
+
[[ -f "$f" ]] || continue
|
|
2200
|
+
local mtime
|
|
2201
|
+
mtime=$(file_mtime "$f" 2>/dev/null || echo "0")
|
|
2202
|
+
if [[ "${mtime:-0}" -gt "$latest_mtime" ]]; then
|
|
2203
|
+
latest_mtime="${mtime}"
|
|
2204
|
+
latest_cp="$f"
|
|
2205
|
+
fi
|
|
2206
|
+
done
|
|
2207
|
+
if [[ -n "$latest_cp" && -x "$SCRIPT_DIR/sw-checkpoint.sh" ]]; then
|
|
2208
|
+
checkpoint_stage="$(basename "$latest_cp" -checkpoint.json)"
|
|
2209
|
+
local cp_json
|
|
2210
|
+
cp_json="$("$SCRIPT_DIR/sw-checkpoint.sh" restore --stage "$checkpoint_stage" 2>/dev/null)" || true
|
|
2211
|
+
if [[ -n "$cp_json" ]] && command -v jq >/dev/null 2>&1; then
|
|
2212
|
+
checkpoint_iteration="$(echo "$cp_json" | jq -r '.iteration // 0' 2>/dev/null)" || checkpoint_iteration=0
|
|
2213
|
+
info "Checkpoint resume: stage=${checkpoint_stage} iteration=${checkpoint_iteration}"
|
|
2214
|
+
# Build COMPLETED_STAGES: all enabled stages before checkpoint_stage
|
|
2215
|
+
local enabled_list before_list=""
|
|
2216
|
+
enabled_list="$(jq -r '.stages[] | select(.enabled == true) | .id' "$PIPELINE_CONFIG" 2>/dev/null)" || true
|
|
2217
|
+
local s
|
|
2218
|
+
while IFS= read -r s; do
|
|
2219
|
+
[[ -z "$s" ]] && continue
|
|
2220
|
+
if [[ "$s" == "$checkpoint_stage" ]]; then
|
|
2221
|
+
break
|
|
2222
|
+
fi
|
|
2223
|
+
[[ -n "$before_list" ]] && before_list="${before_list},${s}" || before_list="$s"
|
|
2224
|
+
done <<< "$enabled_list"
|
|
2225
|
+
if [[ -n "$before_list" ]]; then
|
|
2226
|
+
COMPLETED_STAGES="${before_list}"
|
|
2227
|
+
SELF_HEAL_COUNT="${checkpoint_iteration}"
|
|
2228
|
+
fi
|
|
2229
|
+
fi
|
|
2230
|
+
fi
|
|
2231
|
+
fi
|
|
2232
|
+
|
|
2233
|
+
# Restore from state file if resuming (failed/interrupted pipeline); else initialize fresh
|
|
2234
|
+
if $RESUME_FROM_CHECKPOINT && [[ -f "$STATE_FILE" ]]; then
|
|
2235
|
+
local existing_status
|
|
2236
|
+
existing_status="$(sed -n 's/^status: *//p' "$STATE_FILE" | head -1)"
|
|
2237
|
+
if [[ "$existing_status" == "failed" || "$existing_status" == "interrupted" ]]; then
|
|
2238
|
+
resume_state
|
|
2239
|
+
else
|
|
2240
|
+
initialize_state
|
|
2241
|
+
fi
|
|
2242
|
+
else
|
|
2243
|
+
initialize_state
|
|
2244
|
+
fi
|
|
1946
2245
|
|
|
1947
2246
|
# CI resume: restore branch + goal context when intake is skipped
|
|
1948
2247
|
if [[ -n "${COMPLETED_STAGES:-}" ]] && echo "$COMPLETED_STAGES" | tr ',' '\n' | grep -qx "intake"; then
|
|
@@ -1951,7 +2250,7 @@ pipeline_start() {
|
|
|
1951
2250
|
|
|
1952
2251
|
# Restore GOAL from issue if not already set
|
|
1953
2252
|
if [[ -z "$GOAL" && -n "$ISSUE_NUMBER" ]]; then
|
|
1954
|
-
GOAL=$(gh issue view "$ISSUE_NUMBER" --json title -q .title 2>/dev/null || echo "Issue #${ISSUE_NUMBER}")
|
|
2253
|
+
GOAL=$(_timeout "$(_config_get_int "network.gh_timeout" 30 2>/dev/null || echo 30)" gh issue view "$ISSUE_NUMBER" --json title -q .title 2>/dev/null || echo "Issue #${ISSUE_NUMBER}")
|
|
1955
2254
|
info "CI resume: goal from issue — ${GOAL}"
|
|
1956
2255
|
fi
|
|
1957
2256
|
|
|
@@ -2018,11 +2317,38 @@ pipeline_start() {
|
|
|
2018
2317
|
return $?
|
|
2019
2318
|
fi
|
|
2020
2319
|
|
|
2320
|
+
# Capture predictions for feedback loop (intelligence → actuals → learning)
|
|
2321
|
+
if type intelligence_analyze_issue >/dev/null 2>&1 && (type intelligence_estimate_iterations >/dev/null 2>&1 || type intelligence_predict_cost >/dev/null 2>&1); then
|
|
2322
|
+
local issue_json="${INTELLIGENCE_ANALYSIS:-}"
|
|
2323
|
+
if [[ -z "$issue_json" || "$issue_json" == "{}" ]]; then
|
|
2324
|
+
if [[ -n "$ISSUE_NUMBER" ]]; then
|
|
2325
|
+
issue_json=$(gh issue view "$ISSUE_NUMBER" --json number,title,body,labels 2>/dev/null || echo "{}")
|
|
2326
|
+
else
|
|
2327
|
+
issue_json=$(jq -n --arg title "${GOAL:-untitled}" --arg body "" '{title: $title, body: $body, labels: []}')
|
|
2328
|
+
fi
|
|
2329
|
+
if [[ -n "$issue_json" && "$issue_json" != "{}" ]]; then
|
|
2330
|
+
issue_json=$(intelligence_analyze_issue "$issue_json" 2>/dev/null || echo "{}")
|
|
2331
|
+
fi
|
|
2332
|
+
fi
|
|
2333
|
+
if [[ -n "$issue_json" && "$issue_json" != "{}" ]]; then
|
|
2334
|
+
if type intelligence_estimate_iterations >/dev/null 2>&1; then
|
|
2335
|
+
PREDICTED_ITERATIONS=$(intelligence_estimate_iterations "$issue_json" "" 2>/dev/null || echo "")
|
|
2336
|
+
export PREDICTED_ITERATIONS
|
|
2337
|
+
fi
|
|
2338
|
+
if type intelligence_predict_cost >/dev/null 2>&1; then
|
|
2339
|
+
local cost_json
|
|
2340
|
+
cost_json=$(intelligence_predict_cost "$issue_json" "{}" 2>/dev/null || echo "{}")
|
|
2341
|
+
PREDICTED_COST=$(echo "$cost_json" | jq -r '.estimated_cost_usd // empty' 2>/dev/null || echo "")
|
|
2342
|
+
export PREDICTED_COST
|
|
2343
|
+
fi
|
|
2344
|
+
fi
|
|
2345
|
+
fi
|
|
2346
|
+
|
|
2021
2347
|
# Start background heartbeat writer
|
|
2022
2348
|
start_heartbeat
|
|
2023
2349
|
|
|
2024
2350
|
# Initialize GitHub Check Runs for all pipeline stages
|
|
2025
|
-
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_pipeline_start
|
|
2351
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_pipeline_start >/dev/null 2>&1; then
|
|
2026
2352
|
local head_sha
|
|
2027
2353
|
head_sha=$(git rev-parse HEAD 2>/dev/null || echo "")
|
|
2028
2354
|
if [[ -n "$head_sha" && -n "$REPO_OWNER" && -n "$REPO_NAME" ]]; then
|
|
@@ -2038,12 +2364,20 @@ pipeline_start() {
|
|
|
2038
2364
|
|
|
2039
2365
|
emit_event "pipeline.started" \
|
|
2040
2366
|
"issue=${ISSUE_NUMBER:-0}" \
|
|
2367
|
+
"template=${PIPELINE_NAME}" \
|
|
2368
|
+
"complexity=${INTELLIGENCE_COMPLEXITY:-0}" \
|
|
2369
|
+
"machine=$(hostname 2>/dev/null || echo "unknown")" \
|
|
2041
2370
|
"pipeline=${PIPELINE_NAME}" \
|
|
2042
2371
|
"model=${MODEL:-opus}" \
|
|
2043
2372
|
"goal=${GOAL}"
|
|
2044
2373
|
|
|
2374
|
+
# Record pipeline run in SQLite for dashboard visibility
|
|
2375
|
+
if type add_pipeline_run >/dev/null 2>&1; then
|
|
2376
|
+
add_pipeline_run "${SHIPWRIGHT_PIPELINE_ID}" "${ISSUE_NUMBER:-0}" "${GOAL}" "${BRANCH:-}" "${PIPELINE_NAME}" 2>/dev/null || true
|
|
2377
|
+
fi
|
|
2378
|
+
|
|
2045
2379
|
# Durable WAL: publish pipeline start event
|
|
2046
|
-
if type publish_event
|
|
2380
|
+
if type publish_event >/dev/null 2>&1; then
|
|
2047
2381
|
publish_event "pipeline.started" "{\"issue\":\"${ISSUE_NUMBER:-0}\",\"pipeline\":\"${PIPELINE_NAME}\",\"goal\":\"${GOAL:0:200}\"}" 2>/dev/null || true
|
|
2048
2382
|
fi
|
|
2049
2383
|
|
|
@@ -2051,6 +2385,18 @@ pipeline_start() {
|
|
|
2051
2385
|
local exit_code=$?
|
|
2052
2386
|
PIPELINE_EXIT_CODE="$exit_code"
|
|
2053
2387
|
|
|
2388
|
+
# Compute total cost for pipeline.completed (prefer actual from Claude when available)
|
|
2389
|
+
local model_key="${MODEL:-sonnet}"
|
|
2390
|
+
local total_cost
|
|
2391
|
+
if [[ -n "${TOTAL_COST_USD:-}" && "${TOTAL_COST_USD}" != "0" && "${TOTAL_COST_USD}" != "null" ]]; then
|
|
2392
|
+
total_cost="${TOTAL_COST_USD}"
|
|
2393
|
+
else
|
|
2394
|
+
local input_cost output_cost
|
|
2395
|
+
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}')
|
|
2396
|
+
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}')
|
|
2397
|
+
total_cost=$(awk -v i="$input_cost" -v o="$output_cost" 'BEGIN{printf "%.4f", i + o}')
|
|
2398
|
+
fi
|
|
2399
|
+
|
|
2054
2400
|
# Send completion notification + event
|
|
2055
2401
|
local total_dur_s=""
|
|
2056
2402
|
[[ -n "$PIPELINE_START_EPOCH" ]] && total_dur_s=$(( $(now_epoch) - PIPELINE_START_EPOCH ))
|
|
@@ -2064,28 +2410,69 @@ pipeline_start() {
|
|
|
2064
2410
|
"issue=${ISSUE_NUMBER:-0}" \
|
|
2065
2411
|
"result=success" \
|
|
2066
2412
|
"duration_s=${total_dur_s:-0}" \
|
|
2413
|
+
"iterations=$((SELF_HEAL_COUNT + 1))" \
|
|
2414
|
+
"template=${PIPELINE_NAME}" \
|
|
2415
|
+
"complexity=${INTELLIGENCE_COMPLEXITY:-0}" \
|
|
2416
|
+
"stages_passed=${PIPELINE_STAGES_PASSED:-0}" \
|
|
2417
|
+
"slowest_stage=${PIPELINE_SLOWEST_STAGE:-}" \
|
|
2067
2418
|
"pr_url=${pr_url:-}" \
|
|
2068
2419
|
"agent_id=${PIPELINE_AGENT_ID}" \
|
|
2069
2420
|
"input_tokens=$TOTAL_INPUT_TOKENS" \
|
|
2070
2421
|
"output_tokens=$TOTAL_OUTPUT_TOKENS" \
|
|
2422
|
+
"total_cost=$total_cost" \
|
|
2071
2423
|
"self_heal_count=$SELF_HEAL_COUNT"
|
|
2072
2424
|
|
|
2425
|
+
# Update pipeline run status in SQLite
|
|
2426
|
+
if type update_pipeline_status >/dev/null 2>&1; then
|
|
2427
|
+
update_pipeline_status "${SHIPWRIGHT_PIPELINE_ID}" "completed" "${PIPELINE_SLOWEST_STAGE:-}" "complete" "${total_dur_s:-0}" 2>/dev/null || true
|
|
2428
|
+
fi
|
|
2429
|
+
|
|
2073
2430
|
# Auto-ingest pipeline outcome into recruit profiles
|
|
2074
2431
|
if [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
|
|
2075
2432
|
bash "$SCRIPT_DIR/sw-recruit.sh" ingest-pipeline 1 2>/dev/null || true
|
|
2076
2433
|
fi
|
|
2434
|
+
|
|
2435
|
+
# Capture success patterns to memory (learn what works — parallel the failure path)
|
|
2436
|
+
if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
|
|
2437
|
+
bash "$SCRIPT_DIR/sw-memory.sh" capture "$STATE_FILE" "$ARTIFACTS_DIR" 2>/dev/null || true
|
|
2438
|
+
fi
|
|
2439
|
+
# Update memory baselines with successful run metrics
|
|
2440
|
+
if type memory_update_metrics >/dev/null 2>&1; then
|
|
2441
|
+
memory_update_metrics "build_duration_s" "${total_dur_s:-0}" 2>/dev/null || true
|
|
2442
|
+
memory_update_metrics "total_cost_usd" "${total_cost:-0}" 2>/dev/null || true
|
|
2443
|
+
memory_update_metrics "iterations" "$((SELF_HEAL_COUNT + 1))" 2>/dev/null || true
|
|
2444
|
+
fi
|
|
2445
|
+
|
|
2446
|
+
# Record positive fix outcome if self-healing succeeded
|
|
2447
|
+
if [[ "$SELF_HEAL_COUNT" -gt 0 && -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
|
|
2448
|
+
local _success_sig
|
|
2449
|
+
_success_sig=$(tail -30 "$ARTIFACTS_DIR/test-results.log" 2>/dev/null | head -3 | tr '\n' ' ' | sed 's/^ *//;s/ *$//' || true)
|
|
2450
|
+
if [[ -n "$_success_sig" ]]; then
|
|
2451
|
+
bash "$SCRIPT_DIR/sw-memory.sh" fix-outcome "$_success_sig" "true" "true" 2>/dev/null || true
|
|
2452
|
+
fi
|
|
2453
|
+
fi
|
|
2077
2454
|
else
|
|
2078
2455
|
notify "Pipeline Failed" "Goal: ${GOAL}\nFailed at: ${CURRENT_STAGE_ID:-unknown}" "error"
|
|
2079
2456
|
emit_event "pipeline.completed" \
|
|
2080
2457
|
"issue=${ISSUE_NUMBER:-0}" \
|
|
2081
2458
|
"result=failure" \
|
|
2082
2459
|
"duration_s=${total_dur_s:-0}" \
|
|
2460
|
+
"iterations=$((SELF_HEAL_COUNT + 1))" \
|
|
2461
|
+
"template=${PIPELINE_NAME}" \
|
|
2462
|
+
"complexity=${INTELLIGENCE_COMPLEXITY:-0}" \
|
|
2083
2463
|
"failed_stage=${CURRENT_STAGE_ID:-unknown}" \
|
|
2464
|
+
"error_class=${LAST_STAGE_ERROR_CLASS:-unknown}" \
|
|
2084
2465
|
"agent_id=${PIPELINE_AGENT_ID}" \
|
|
2085
2466
|
"input_tokens=$TOTAL_INPUT_TOKENS" \
|
|
2086
2467
|
"output_tokens=$TOTAL_OUTPUT_TOKENS" \
|
|
2468
|
+
"total_cost=$total_cost" \
|
|
2087
2469
|
"self_heal_count=$SELF_HEAL_COUNT"
|
|
2088
2470
|
|
|
2471
|
+
# Update pipeline run status in SQLite
|
|
2472
|
+
if type update_pipeline_status >/dev/null 2>&1; then
|
|
2473
|
+
update_pipeline_status "${SHIPWRIGHT_PIPELINE_ID}" "failed" "${CURRENT_STAGE_ID:-unknown}" "failed" "${total_dur_s:-0}" 2>/dev/null || true
|
|
2474
|
+
fi
|
|
2475
|
+
|
|
2089
2476
|
# Auto-ingest pipeline outcome into recruit profiles
|
|
2090
2477
|
if [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
|
|
2091
2478
|
bash "$SCRIPT_DIR/sw-recruit.sh" ingest-pipeline 1 2>/dev/null || true
|
|
@@ -2121,7 +2508,7 @@ pipeline_start() {
|
|
|
2121
2508
|
"success=$pipeline_success"
|
|
2122
2509
|
|
|
2123
2510
|
# Close intelligence prediction feedback loop — validate predicted vs actual
|
|
2124
|
-
if type intelligence_validate_prediction
|
|
2511
|
+
if type intelligence_validate_prediction >/dev/null 2>&1 && [[ -n "${ISSUE_NUMBER:-}" ]]; then
|
|
2125
2512
|
intelligence_validate_prediction \
|
|
2126
2513
|
"$ISSUE_NUMBER" \
|
|
2127
2514
|
"${INTELLIGENCE_COMPLEXITY:-0}" \
|
|
@@ -2129,6 +2516,12 @@ pipeline_start() {
|
|
|
2129
2516
|
"$pipeline_success" 2>/dev/null || true
|
|
2130
2517
|
fi
|
|
2131
2518
|
|
|
2519
|
+
# Validate iterations prediction against actuals (cost validation moved below after total_cost is computed)
|
|
2520
|
+
local ACTUAL_ITERATIONS=$((SELF_HEAL_COUNT + 1))
|
|
2521
|
+
if [[ -n "${PREDICTED_ITERATIONS:-}" ]] && type intelligence_validate_prediction >/dev/null 2>&1; then
|
|
2522
|
+
intelligence_validate_prediction "iterations" "$PREDICTED_ITERATIONS" "$ACTUAL_ITERATIONS" 2>/dev/null || true
|
|
2523
|
+
fi
|
|
2524
|
+
|
|
2132
2525
|
# Close predictive anomaly feedback loop — confirm whether flagged anomalies were real
|
|
2133
2526
|
if [[ -x "$SCRIPT_DIR/sw-predictive.sh" ]]; then
|
|
2134
2527
|
local _actual_failure="false"
|
|
@@ -2144,7 +2537,8 @@ pipeline_start() {
|
|
|
2144
2537
|
"issue=${ISSUE_NUMBER:-0}" \
|
|
2145
2538
|
"template=${PIPELINE_NAME}" \
|
|
2146
2539
|
"success=$pipeline_success" \
|
|
2147
|
-
"duration_s=${total_dur_s:-0}"
|
|
2540
|
+
"duration_s=${total_dur_s:-0}" \
|
|
2541
|
+
"complexity=${INTELLIGENCE_COMPLEXITY:-0}"
|
|
2148
2542
|
|
|
2149
2543
|
# Risk prediction vs actual failure
|
|
2150
2544
|
local predicted_risk="${INTELLIGENCE_RISK_SCORE:-0}"
|
|
@@ -2167,20 +2561,26 @@ pipeline_start() {
|
|
|
2167
2561
|
fi
|
|
2168
2562
|
|
|
2169
2563
|
# Record pipeline outcome for model routing feedback loop
|
|
2170
|
-
if type optimize_analyze_outcome
|
|
2564
|
+
if type optimize_analyze_outcome >/dev/null 2>&1; then
|
|
2171
2565
|
optimize_analyze_outcome "$STATE_FILE" 2>/dev/null || true
|
|
2172
|
-
# Tune template weights based on accumulated outcomes
|
|
2173
|
-
if type optimize_tune_templates &>/dev/null 2>&1; then
|
|
2174
|
-
optimize_tune_templates 2>/dev/null || true
|
|
2175
|
-
fi
|
|
2176
2566
|
fi
|
|
2177
2567
|
|
|
2178
|
-
|
|
2568
|
+
# Auto-learn after pipeline completion (non-blocking)
|
|
2569
|
+
if type optimize_tune_templates &>/dev/null; then
|
|
2570
|
+
(
|
|
2571
|
+
optimize_tune_templates 2>/dev/null
|
|
2572
|
+
optimize_learn_iterations 2>/dev/null
|
|
2573
|
+
optimize_route_models 2>/dev/null
|
|
2574
|
+
optimize_learn_risk_keywords 2>/dev/null
|
|
2575
|
+
) &
|
|
2576
|
+
fi
|
|
2577
|
+
|
|
2578
|
+
if type memory_finalize_pipeline >/dev/null 2>&1; then
|
|
2179
2579
|
memory_finalize_pipeline "$STATE_FILE" "$ARTIFACTS_DIR" 2>/dev/null || true
|
|
2180
2580
|
fi
|
|
2181
2581
|
|
|
2182
2582
|
# Broadcast discovery for cross-pipeline learning
|
|
2183
|
-
if type broadcast_discovery
|
|
2583
|
+
if type broadcast_discovery >/dev/null 2>&1; then
|
|
2184
2584
|
local _disc_result="failure"
|
|
2185
2585
|
[[ "$exit_code" -eq 0 ]] && _disc_result="success"
|
|
2186
2586
|
local _disc_files=""
|
|
@@ -2209,6 +2609,34 @@ pipeline_start() {
|
|
|
2209
2609
|
"model=$model_key" \
|
|
2210
2610
|
"cost_usd=$total_cost"
|
|
2211
2611
|
|
|
2612
|
+
# Persist cost entry to costs.json + SQLite (was missing — tokens accumulated but never written)
|
|
2613
|
+
if type cost_record >/dev/null 2>&1; then
|
|
2614
|
+
cost_record "$TOTAL_INPUT_TOKENS" "$TOTAL_OUTPUT_TOKENS" "$model_key" "pipeline" "${ISSUE_NUMBER:-}" 2>/dev/null || true
|
|
2615
|
+
fi
|
|
2616
|
+
|
|
2617
|
+
# Record pipeline outcome for Thompson sampling / outcome-based learning
|
|
2618
|
+
if type db_record_outcome >/dev/null 2>&1; then
|
|
2619
|
+
local _outcome_success=0
|
|
2620
|
+
[[ "$exit_code" -eq 0 ]] && _outcome_success=1
|
|
2621
|
+
local _outcome_complexity="medium"
|
|
2622
|
+
[[ "${INTELLIGENCE_COMPLEXITY:-5}" -le 3 ]] && _outcome_complexity="low"
|
|
2623
|
+
[[ "${INTELLIGENCE_COMPLEXITY:-5}" -ge 7 ]] && _outcome_complexity="high"
|
|
2624
|
+
db_record_outcome \
|
|
2625
|
+
"${SHIPWRIGHT_PIPELINE_ID:-pipeline-$$-${ISSUE_NUMBER:-0}}" \
|
|
2626
|
+
"${ISSUE_NUMBER:-}" \
|
|
2627
|
+
"${PIPELINE_NAME:-standard}" \
|
|
2628
|
+
"$_outcome_success" \
|
|
2629
|
+
"${total_dur_s:-0}" \
|
|
2630
|
+
"${SELF_HEAL_COUNT:-0}" \
|
|
2631
|
+
"${total_cost:-0}" \
|
|
2632
|
+
"$_outcome_complexity" 2>/dev/null || true
|
|
2633
|
+
fi
|
|
2634
|
+
|
|
2635
|
+
# Validate cost prediction against actual (after total_cost is computed)
|
|
2636
|
+
if [[ -n "${PREDICTED_COST:-}" ]] && type intelligence_validate_prediction >/dev/null 2>&1; then
|
|
2637
|
+
intelligence_validate_prediction "cost" "$PREDICTED_COST" "$total_cost" 2>/dev/null || true
|
|
2638
|
+
fi
|
|
2639
|
+
|
|
2212
2640
|
return $exit_code
|
|
2213
2641
|
}
|
|
2214
2642
|
|