shipwright-cli 2.2.0 → 2.2.2

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.
Files changed (120) hide show
  1. package/README.md +15 -16
  2. package/config/policy.schema.json +104 -29
  3. package/docs/AGI-PLATFORM-PLAN.md +11 -7
  4. package/docs/AGI-WHATS-NEXT.md +26 -20
  5. package/docs/README.md +2 -0
  6. package/package.json +1 -1
  7. package/scripts/check-version-consistency.sh +72 -0
  8. package/scripts/lib/daemon-adaptive.sh +610 -0
  9. package/scripts/lib/daemon-dispatch.sh +489 -0
  10. package/scripts/lib/daemon-failure.sh +387 -0
  11. package/scripts/lib/daemon-patrol.sh +1113 -0
  12. package/scripts/lib/daemon-poll.sh +1202 -0
  13. package/scripts/lib/daemon-state.sh +550 -0
  14. package/scripts/lib/daemon-triage.sh +490 -0
  15. package/scripts/lib/helpers.sh +81 -1
  16. package/scripts/lib/pipeline-detection.sh +278 -0
  17. package/scripts/lib/pipeline-github.sh +196 -0
  18. package/scripts/lib/pipeline-intelligence.sh +1706 -0
  19. package/scripts/lib/pipeline-quality-checks.sh +1054 -0
  20. package/scripts/lib/pipeline-quality.sh +11 -0
  21. package/scripts/lib/pipeline-stages.sh +2508 -0
  22. package/scripts/lib/pipeline-state.sh +529 -0
  23. package/scripts/sw +26 -4
  24. package/scripts/sw-activity.sh +1 -1
  25. package/scripts/sw-adaptive.sh +2 -2
  26. package/scripts/sw-adversarial.sh +1 -1
  27. package/scripts/sw-architecture-enforcer.sh +1 -1
  28. package/scripts/sw-auth.sh +1 -1
  29. package/scripts/sw-autonomous.sh +1 -1
  30. package/scripts/sw-changelog.sh +1 -1
  31. package/scripts/sw-checkpoint.sh +1 -1
  32. package/scripts/sw-ci.sh +1 -1
  33. package/scripts/sw-cleanup.sh +1 -1
  34. package/scripts/sw-code-review.sh +1 -1
  35. package/scripts/sw-connect.sh +1 -1
  36. package/scripts/sw-context.sh +1 -1
  37. package/scripts/sw-cost.sh +1 -1
  38. package/scripts/sw-daemon.sh +52 -4816
  39. package/scripts/sw-dashboard.sh +1 -1
  40. package/scripts/sw-db.sh +1 -1
  41. package/scripts/sw-decompose.sh +1 -1
  42. package/scripts/sw-deps.sh +1 -1
  43. package/scripts/sw-developer-simulation.sh +1 -1
  44. package/scripts/sw-discovery.sh +1 -1
  45. package/scripts/sw-doc-fleet.sh +1 -1
  46. package/scripts/sw-docs-agent.sh +1 -1
  47. package/scripts/sw-docs.sh +1 -1
  48. package/scripts/sw-doctor.sh +42 -1
  49. package/scripts/sw-dora.sh +1 -1
  50. package/scripts/sw-durable.sh +1 -1
  51. package/scripts/sw-e2e-orchestrator.sh +1 -1
  52. package/scripts/sw-eventbus.sh +1 -1
  53. package/scripts/sw-feedback.sh +1 -1
  54. package/scripts/sw-fix.sh +1 -1
  55. package/scripts/sw-fleet-discover.sh +1 -1
  56. package/scripts/sw-fleet-viz.sh +3 -3
  57. package/scripts/sw-fleet.sh +1 -1
  58. package/scripts/sw-github-app.sh +1 -1
  59. package/scripts/sw-github-checks.sh +1 -1
  60. package/scripts/sw-github-deploy.sh +1 -1
  61. package/scripts/sw-github-graphql.sh +1 -1
  62. package/scripts/sw-guild.sh +1 -1
  63. package/scripts/sw-heartbeat.sh +1 -1
  64. package/scripts/sw-hygiene.sh +1 -1
  65. package/scripts/sw-incident.sh +1 -1
  66. package/scripts/sw-init.sh +1 -1
  67. package/scripts/sw-instrument.sh +1 -1
  68. package/scripts/sw-intelligence.sh +1 -1
  69. package/scripts/sw-jira.sh +1 -1
  70. package/scripts/sw-launchd.sh +1 -1
  71. package/scripts/sw-linear.sh +1 -1
  72. package/scripts/sw-logs.sh +1 -1
  73. package/scripts/sw-loop.sh +1 -1
  74. package/scripts/sw-memory.sh +1 -1
  75. package/scripts/sw-mission-control.sh +1 -1
  76. package/scripts/sw-model-router.sh +1 -1
  77. package/scripts/sw-otel.sh +4 -4
  78. package/scripts/sw-oversight.sh +1 -1
  79. package/scripts/sw-pipeline-composer.sh +1 -1
  80. package/scripts/sw-pipeline-vitals.sh +1 -1
  81. package/scripts/sw-pipeline.sh +23 -56
  82. package/scripts/sw-pipeline.sh.mock +7 -0
  83. package/scripts/sw-pm.sh +1 -1
  84. package/scripts/sw-pr-lifecycle.sh +1 -1
  85. package/scripts/sw-predictive.sh +1 -1
  86. package/scripts/sw-prep.sh +1 -1
  87. package/scripts/sw-ps.sh +1 -1
  88. package/scripts/sw-public-dashboard.sh +1 -1
  89. package/scripts/sw-quality.sh +1 -1
  90. package/scripts/sw-reaper.sh +1 -1
  91. package/scripts/sw-recruit.sh +9 -1
  92. package/scripts/sw-regression.sh +1 -1
  93. package/scripts/sw-release-manager.sh +1 -1
  94. package/scripts/sw-release.sh +1 -1
  95. package/scripts/sw-remote.sh +1 -1
  96. package/scripts/sw-replay.sh +1 -1
  97. package/scripts/sw-retro.sh +1 -1
  98. package/scripts/sw-scale.sh +8 -5
  99. package/scripts/sw-security-audit.sh +1 -1
  100. package/scripts/sw-self-optimize.sh +158 -7
  101. package/scripts/sw-session.sh +1 -1
  102. package/scripts/sw-setup.sh +1 -1
  103. package/scripts/sw-standup.sh +3 -3
  104. package/scripts/sw-status.sh +1 -1
  105. package/scripts/sw-strategic.sh +1 -1
  106. package/scripts/sw-stream.sh +8 -2
  107. package/scripts/sw-swarm.sh +7 -10
  108. package/scripts/sw-team-stages.sh +1 -1
  109. package/scripts/sw-templates.sh +1 -1
  110. package/scripts/sw-testgen.sh +1 -1
  111. package/scripts/sw-tmux-pipeline.sh +1 -1
  112. package/scripts/sw-tmux.sh +1 -1
  113. package/scripts/sw-trace.sh +1 -1
  114. package/scripts/sw-tracker.sh +24 -6
  115. package/scripts/sw-triage.sh +1 -1
  116. package/scripts/sw-upgrade.sh +1 -1
  117. package/scripts/sw-ux.sh +1 -1
  118. package/scripts/sw-webhook.sh +1 -1
  119. package/scripts/sw-widgets.sh +1 -1
  120. package/scripts/sw-worktree.sh +1 -1
@@ -0,0 +1,529 @@
1
+ # pipeline-state.sh — Pipeline state management (for sw-pipeline.sh)
2
+ # Source from sw-pipeline.sh. Requires SCRIPT_DIR, ARTIFACTS_DIR, and helpers.
3
+ [[ -n "${_PIPELINE_STATE_LOADED:-}" ]] && return 0
4
+ _PIPELINE_STATE_LOADED=1
5
+
6
+ save_artifact() {
7
+ local name="$1" content="$2"
8
+ mkdir -p "$ARTIFACTS_DIR" 2>/dev/null || true
9
+ echo "$content" > "$ARTIFACTS_DIR/$name"
10
+ }
11
+
12
+ get_stage_status() {
13
+ local stage_id="$1"
14
+ echo "$STAGE_STATUSES" | grep "^${stage_id}:" | cut -d: -f2 | tail -1 || true
15
+ }
16
+
17
+ set_stage_status() {
18
+ local stage_id="$1" status="$2"
19
+ STAGE_STATUSES=$(echo "$STAGE_STATUSES" | grep -v "^${stage_id}:" || true)
20
+ STAGE_STATUSES="${STAGE_STATUSES}
21
+ ${stage_id}:${status}"
22
+ }
23
+
24
+ # Per-stage timing
25
+ record_stage_start() {
26
+ local stage_id="$1"
27
+ STAGE_TIMINGS="${STAGE_TIMINGS}
28
+ ${stage_id}_start:$(now_epoch)"
29
+ }
30
+
31
+ record_stage_end() {
32
+ local stage_id="$1"
33
+ STAGE_TIMINGS="${STAGE_TIMINGS}
34
+ ${stage_id}_end:$(now_epoch)"
35
+ }
36
+
37
+ get_stage_timing() {
38
+ local stage_id="$1"
39
+ local start_e end_e
40
+ start_e=$(echo "$STAGE_TIMINGS" | grep "^${stage_id}_start:" | cut -d: -f2 | tail -1 || true)
41
+ end_e=$(echo "$STAGE_TIMINGS" | grep "^${stage_id}_end:" | cut -d: -f2 | tail -1 || true)
42
+ if [[ -n "$start_e" && -n "$end_e" ]]; then
43
+ format_duration $(( end_e - start_e ))
44
+ elif [[ -n "$start_e" ]]; then
45
+ format_duration $(( $(now_epoch) - start_e ))
46
+ else
47
+ echo ""
48
+ fi
49
+ }
50
+
51
+ # Raw seconds for a stage (for memory baseline updates)
52
+ get_stage_timing_seconds() {
53
+ local stage_id="$1"
54
+ local start_e end_e
55
+ start_e=$(echo "$STAGE_TIMINGS" | grep "^${stage_id}_start:" | cut -d: -f2 | tail -1 || true)
56
+ end_e=$(echo "$STAGE_TIMINGS" | grep "^${stage_id}_end:" | cut -d: -f2 | tail -1 || true)
57
+ if [[ -n "$start_e" && -n "$end_e" ]]; then
58
+ echo $(( end_e - start_e ))
59
+ elif [[ -n "$start_e" ]]; then
60
+ echo $(( $(now_epoch) - start_e ))
61
+ else
62
+ echo "0"
63
+ fi
64
+ }
65
+
66
+ get_stage_description() {
67
+ local stage_id="$1"
68
+
69
+ # Try to generate dynamic description from pipeline config
70
+ if [[ -n "${PIPELINE_CONFIG:-}" && -f "${PIPELINE_CONFIG:-/dev/null}" ]]; then
71
+ local stage_cfg
72
+ stage_cfg=$(jq -c --arg id "$stage_id" '.stages[] | select(.id == $id) | .config // {}' "$PIPELINE_CONFIG" 2>/dev/null || echo "{}")
73
+ case "$stage_id" in
74
+ test)
75
+ local cfg_test_cmd cfg_cov_min
76
+ cfg_test_cmd=$(echo "$stage_cfg" | jq -r '.test_cmd // empty' 2>/dev/null || true)
77
+ cfg_cov_min=$(echo "$stage_cfg" | jq -r '.coverage_min // empty' 2>/dev/null || true)
78
+ if [[ -n "$cfg_test_cmd" ]]; then
79
+ echo "Running ${cfg_test_cmd}${cfg_cov_min:+ with ${cfg_cov_min}% coverage gate}"
80
+ return
81
+ fi
82
+ ;;
83
+ build)
84
+ local cfg_max_iter cfg_model
85
+ cfg_max_iter=$(echo "$stage_cfg" | jq -r '.max_iterations // empty' 2>/dev/null || true)
86
+ cfg_model=$(jq -r '.defaults.model // empty' "$PIPELINE_CONFIG" 2>/dev/null || true)
87
+ if [[ -n "$cfg_max_iter" ]]; then
88
+ echo "Building with ${cfg_max_iter} max iterations${cfg_model:+ using ${cfg_model}}"
89
+ return
90
+ fi
91
+ ;;
92
+ monitor)
93
+ local cfg_dur cfg_thresh
94
+ cfg_dur=$(echo "$stage_cfg" | jq -r '.duration_minutes // empty' 2>/dev/null || true)
95
+ cfg_thresh=$(echo "$stage_cfg" | jq -r '.error_threshold // empty' 2>/dev/null || true)
96
+ if [[ -n "$cfg_dur" ]]; then
97
+ echo "Monitoring for ${cfg_dur}m${cfg_thresh:+ (threshold: ${cfg_thresh} errors)}"
98
+ return
99
+ fi
100
+ ;;
101
+ esac
102
+ fi
103
+
104
+ # Static fallback descriptions
105
+ case "$stage_id" in
106
+ intake) echo "Extracting requirements and auto-detecting project setup" ;;
107
+ plan) echo "Creating implementation plan with architecture decisions" ;;
108
+ design) echo "Designing interfaces, data models, and API contracts" ;;
109
+ build) echo "Writing production code with self-healing iteration" ;;
110
+ test) echo "Running test suite and validating coverage" ;;
111
+ review) echo "Code quality, security audit, performance review" ;;
112
+ compound_quality) echo "Adversarial testing, E2E validation, DoD checklist" ;;
113
+ pr) echo "Creating pull request with CI integration" ;;
114
+ merge) echo "Merging PR with branch cleanup" ;;
115
+ deploy) echo "Deploying to staging/production" ;;
116
+ validate) echo "Smoke tests and health checks post-deploy" ;;
117
+ monitor) echo "Production monitoring with auto-rollback" ;;
118
+ *) echo "" ;;
119
+ esac
120
+ }
121
+
122
+ # Build inline stage progress string (e.g. "intake:complete plan:running test:pending")
123
+ build_stage_progress() {
124
+ local progress=""
125
+ local stages
126
+ stages=$(jq -c '.stages[]' "$PIPELINE_CONFIG" 2>/dev/null) || return 0
127
+ while IFS= read -r -u 3 stage; do
128
+ local id enabled
129
+ id=$(echo "$stage" | jq -r '.id')
130
+ enabled=$(echo "$stage" | jq -r '.enabled')
131
+ [[ "$enabled" != "true" ]] && continue
132
+ local sstatus
133
+ sstatus=$(get_stage_status "$id")
134
+ sstatus="${sstatus:-pending}"
135
+ if [[ -n "$progress" ]]; then
136
+ progress="${progress} ${id}:${sstatus}"
137
+ else
138
+ progress="${id}:${sstatus}"
139
+ fi
140
+ done 3<<< "$stages"
141
+ echo "$progress"
142
+ }
143
+
144
+ update_status() {
145
+ local status="$1" stage="$2"
146
+ PIPELINE_STATUS="$status"
147
+ CURRENT_STAGE="$stage"
148
+ UPDATED_AT="$(now_iso)"
149
+ write_state
150
+ }
151
+
152
+ mark_stage_complete() {
153
+ local stage_id="$1"
154
+ record_stage_end "$stage_id"
155
+ set_stage_status "$stage_id" "complete"
156
+ local timing
157
+ timing=$(get_stage_timing "$stage_id")
158
+ log_stage "$stage_id" "complete (${timing})"
159
+ write_state
160
+
161
+ record_stage_effectiveness "$stage_id" "complete"
162
+ # Update memory baselines and predictive baselines for stage durations
163
+ if [[ "$stage_id" == "test" || "$stage_id" == "build" ]]; then
164
+ local secs
165
+ secs=$(get_stage_timing_seconds "$stage_id")
166
+ if [[ -n "$secs" && "$secs" != "0" ]]; then
167
+ [[ -x "$SCRIPT_DIR/sw-memory.sh" ]] && bash "$SCRIPT_DIR/sw-memory.sh" metric "${stage_id}_duration_s" "$secs" 2>/dev/null || true
168
+ if [[ -x "$SCRIPT_DIR/sw-predictive.sh" ]]; then
169
+ local anomaly_sev
170
+ anomaly_sev=$(bash "$SCRIPT_DIR/sw-predictive.sh" anomaly "$stage_id" "duration_s" "$secs" 2>/dev/null || echo "normal")
171
+ [[ "$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
172
+ bash "$SCRIPT_DIR/sw-predictive.sh" baseline "$stage_id" "duration_s" "$secs" 2>/dev/null || true
173
+ fi
174
+ fi
175
+ fi
176
+
177
+ # Update GitHub progress comment
178
+ if [[ -n "$ISSUE_NUMBER" ]]; then
179
+ local body
180
+ body=$(gh_build_progress_body)
181
+ gh_update_progress "$body"
182
+
183
+ # Notify tracker (Linear/Jira) of stage completion
184
+ local stage_desc
185
+ stage_desc=$(get_stage_description "$stage_id")
186
+ "$SCRIPT_DIR/sw-tracker.sh" notify "stage_complete" "$ISSUE_NUMBER" \
187
+ "${stage_id}|${timing}|${stage_desc}" 2>/dev/null || true
188
+
189
+ # Post structured stage event for CI sweep/retry intelligence
190
+ ci_post_stage_event "$stage_id" "complete" "$timing"
191
+ fi
192
+
193
+ # Update GitHub Check Run for this stage
194
+ if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update &>/dev/null 2>&1; then
195
+ gh_checks_stage_update "$stage_id" "completed" "success" "Stage $stage_id: ${timing}" 2>/dev/null || true
196
+ fi
197
+
198
+ # Persist artifacts to feature branch after expensive stages
199
+ case "$stage_id" in
200
+ plan) persist_artifacts "plan" "plan.md" "dod.md" "context-bundle.md" ;;
201
+ design) persist_artifacts "design" "design.md" ;;
202
+ esac
203
+
204
+ # Automatic checkpoint at every stage boundary (for crash recovery)
205
+ if [[ -x "$SCRIPT_DIR/sw-checkpoint.sh" ]]; then
206
+ local _cp_sha _cp_files
207
+ _cp_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
208
+ _cp_files=$(git diff --name-only HEAD~1 HEAD 2>/dev/null | head -20 | tr '\n' ',' || true)
209
+ bash "$SCRIPT_DIR/sw-checkpoint.sh" save \
210
+ --stage "$stage_id" \
211
+ --iteration "${SELF_HEAL_COUNT:-0}" \
212
+ --git-sha "$_cp_sha" \
213
+ --files-modified "${_cp_files:-}" \
214
+ --tests-passing "${TEST_PASSED:-false}" 2>/dev/null || true
215
+ fi
216
+
217
+ # Durable WAL: publish stage completion event
218
+ if type publish_event &>/dev/null 2>&1; then
219
+ publish_event "stage.complete" "{\"stage\":\"${stage_id}\",\"issue\":\"${ISSUE_NUMBER:-0}\",\"timing\":\"${timing}\"}" 2>/dev/null || true
220
+ fi
221
+ }
222
+
223
+ persist_artifacts() {
224
+ # Commit and push pipeline artifacts to the feature branch mid-pipeline.
225
+ # Only runs in CI — local runs skip. Non-fatal: logs failure but never crashes.
226
+ [[ "${CI_MODE:-false}" != "true" ]] && return 0
227
+ [[ -z "${ISSUE_NUMBER:-}" ]] && return 0
228
+ [[ -z "${ARTIFACTS_DIR:-}" ]] && return 0
229
+
230
+ local stage="${1:-unknown}"
231
+ shift
232
+ local files=("$@")
233
+
234
+ # Collect files that actually exist
235
+ local to_add=()
236
+ for f in "${files[@]}"; do
237
+ local path="${ARTIFACTS_DIR}/${f}"
238
+ if [[ -f "$path" && -s "$path" ]]; then
239
+ to_add+=("$path")
240
+ fi
241
+ done
242
+
243
+ if [[ ${#to_add[@]} -eq 0 ]]; then
244
+ warn "persist_artifacts($stage): no artifact files found — skipping"
245
+ return 0
246
+ fi
247
+
248
+ info "Persisting ${#to_add[@]} artifact(s) after stage ${stage}..."
249
+
250
+ (
251
+ git add "${to_add[@]}" 2>/dev/null || true
252
+ if ! git diff --cached --quiet 2>/dev/null; then
253
+ git commit -m "chore: persist ${stage} artifacts for #${ISSUE_NUMBER} [skip ci]" --no-verify 2>/dev/null || true
254
+ local branch="shipwright/issue-${ISSUE_NUMBER}"
255
+ git push origin "HEAD:refs/heads/$branch" --force 2>/dev/null || true
256
+ emit_event "artifacts.persisted" "issue=${ISSUE_NUMBER}" "stage=$stage" "file_count=${#to_add[@]}"
257
+ fi
258
+ ) 2>/dev/null || {
259
+ warn "persist_artifacts($stage): push failed — non-fatal, continuing"
260
+ emit_event "artifacts.persist_failed" "issue=${ISSUE_NUMBER}" "stage=$stage"
261
+ }
262
+
263
+ return 0
264
+ }
265
+
266
+ verify_stage_artifacts() {
267
+ # Check that required artifacts exist and are non-empty for a given stage.
268
+ # Returns 0 if all artifacts are present, 1 if any are missing.
269
+ local stage_id="$1"
270
+ [[ -z "${ARTIFACTS_DIR:-}" ]] && return 0
271
+
272
+ local required=()
273
+ case "$stage_id" in
274
+ plan) required=("plan.md") ;;
275
+ design) required=("design.md" "plan.md") ;;
276
+ *) return 0 ;; # No artifact check needed
277
+ esac
278
+
279
+ local missing=0
280
+ for f in "${required[@]}"; do
281
+ local path="${ARTIFACTS_DIR}/${f}"
282
+ if [[ ! -f "$path" || ! -s "$path" ]]; then
283
+ warn "verify_stage_artifacts($stage_id): missing or empty: $f"
284
+ missing=1
285
+ fi
286
+ done
287
+
288
+ return "$missing"
289
+ }
290
+
291
+ # Self-aware pipeline: record stage effectiveness for meta-cognition
292
+ STAGE_EFFECTIVENESS_FILE="${HOME}/.shipwright/stage-effectiveness.jsonl"
293
+ record_stage_effectiveness() {
294
+ local stage_id="$1" outcome="${2:-failed}"
295
+ mkdir -p "${HOME}/.shipwright"
296
+ echo "{\"stage\":\"$stage_id\",\"outcome\":\"$outcome\",\"ts\":\"$(now_iso)\"}" >> "${STAGE_EFFECTIVENESS_FILE}"
297
+ # Keep last 100 entries
298
+ tail -100 "${STAGE_EFFECTIVENESS_FILE}" > "${STAGE_EFFECTIVENESS_FILE}.tmp" 2>/dev/null && mv "${STAGE_EFFECTIVENESS_FILE}.tmp" "${STAGE_EFFECTIVENESS_FILE}" 2>/dev/null || true
299
+ }
300
+ get_stage_self_awareness_hint() {
301
+ local stage_id="$1"
302
+ [[ ! -f "$STAGE_EFFECTIVENESS_FILE" ]] && return 0
303
+ local recent
304
+ recent=$(grep "\"stage\":\"$stage_id\"" "$STAGE_EFFECTIVENESS_FILE" 2>/dev/null | tail -10 || true)
305
+ [[ -z "$recent" ]] && return 0
306
+ local failures=0 total=0
307
+ while IFS= read -r line; do
308
+ [[ -z "$line" ]] && continue
309
+ total=$((total + 1))
310
+ echo "$line" | grep -q '"outcome":"failed"' && failures=$((failures + 1)) || true
311
+ done <<< "$recent"
312
+ if [[ "$total" -ge 3 ]] && [[ $((failures * 100 / total)) -ge 50 ]]; then
313
+ case "$stage_id" in
314
+ plan) echo "Recent plan stage failures: consider adding more context or breaking the goal into smaller steps." ;;
315
+ build) echo "Recent build stage failures: consider adding test expectations or simplifying the change." ;;
316
+ *) echo "Recent $stage_id failures: review past logs and adjust approach." ;;
317
+ esac
318
+ fi
319
+ }
320
+
321
+ mark_stage_failed() {
322
+ local stage_id="$1"
323
+ record_stage_end "$stage_id"
324
+ record_stage_effectiveness "$stage_id" "failed"
325
+ set_stage_status "$stage_id" "failed"
326
+ local timing
327
+ timing=$(get_stage_timing "$stage_id")
328
+ log_stage "$stage_id" "failed (${timing})"
329
+ write_state
330
+
331
+ # Update GitHub progress + comment failure
332
+ if [[ -n "$ISSUE_NUMBER" ]]; then
333
+ local body
334
+ body=$(gh_build_progress_body)
335
+ gh_update_progress "$body"
336
+ gh_comment_issue "$ISSUE_NUMBER" "❌ Pipeline failed at stage **${stage_id}** after ${timing}.
337
+
338
+ \`\`\`
339
+ $(tail -5 "$ARTIFACTS_DIR/${stage_id}"*.log 2>/dev/null || echo 'No log available')
340
+ \`\`\`"
341
+
342
+ # Notify tracker (Linear/Jira) of stage failure
343
+ local error_context
344
+ error_context=$(tail -5 "$ARTIFACTS_DIR/${stage_id}"*.log 2>/dev/null || echo "No log")
345
+ "$SCRIPT_DIR/sw-tracker.sh" notify "stage_failed" "$ISSUE_NUMBER" \
346
+ "${stage_id}|${error_context}" 2>/dev/null || true
347
+
348
+ # Post structured stage event for CI sweep/retry intelligence
349
+ ci_post_stage_event "$stage_id" "failed" "$timing"
350
+ fi
351
+
352
+ # Update GitHub Check Run for this stage
353
+ if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update &>/dev/null 2>&1; then
354
+ local fail_summary
355
+ fail_summary=$(tail -3 "$ARTIFACTS_DIR/${stage_id}"*.log 2>/dev/null | head -c 500 || echo "Stage $stage_id failed")
356
+ gh_checks_stage_update "$stage_id" "completed" "failure" "$fail_summary" 2>/dev/null || true
357
+ fi
358
+
359
+ # Save checkpoint on failure too (for crash recovery / resume)
360
+ if [[ -x "$SCRIPT_DIR/sw-checkpoint.sh" ]]; then
361
+ local _cp_sha
362
+ _cp_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
363
+ bash "$SCRIPT_DIR/sw-checkpoint.sh" save \
364
+ --stage "$stage_id" \
365
+ --iteration "${SELF_HEAL_COUNT:-0}" \
366
+ --git-sha "$_cp_sha" \
367
+ --tests-passing "false" 2>/dev/null || true
368
+ fi
369
+
370
+ # Durable WAL: publish stage failure event
371
+ if type publish_event &>/dev/null 2>&1; then
372
+ publish_event "stage.failed" "{\"stage\":\"${stage_id}\",\"issue\":\"${ISSUE_NUMBER:-0}\",\"timing\":\"${timing}\"}" 2>/dev/null || true
373
+ fi
374
+ }
375
+
376
+ log_stage() {
377
+ local stage_id="$1" message="$2"
378
+ local timestamp
379
+ timestamp=$(date +"%H:%M:%S")
380
+ LOG_ENTRIES="${LOG_ENTRIES}
381
+ ### ${stage_id} (${timestamp})
382
+ ${message}
383
+ "
384
+ }
385
+
386
+ initialize_state() {
387
+ PIPELINE_STATUS="running"
388
+ PIPELINE_START_EPOCH="$(now_epoch)"
389
+ STARTED_AT="$(now_iso)"
390
+ UPDATED_AT="$(now_iso)"
391
+ STAGE_STATUSES=""
392
+ STAGE_TIMINGS=""
393
+ LOG_ENTRIES=""
394
+ # Clear per-run tracking files
395
+ rm -f "$ARTIFACTS_DIR/model-routing.log" "$ARTIFACTS_DIR/.plan-failure-sig.txt"
396
+ write_state
397
+ }
398
+
399
+ write_state() {
400
+ [[ -z "${STATE_FILE:-}" || -z "${ARTIFACTS_DIR:-}" ]] && return 0
401
+ mkdir -p "$(dirname "$STATE_FILE")" 2>/dev/null || true
402
+ local stages_yaml=""
403
+ while IFS=: read -r sid sstatus; do
404
+ [[ -z "$sid" ]] && continue
405
+ stages_yaml="${stages_yaml} ${sid}: ${sstatus}
406
+ "
407
+ done <<< "$STAGE_STATUSES"
408
+
409
+ local total_dur=""
410
+ if [[ -n "$PIPELINE_START_EPOCH" ]]; then
411
+ total_dur=$(format_duration $(( $(now_epoch) - PIPELINE_START_EPOCH )))
412
+ fi
413
+
414
+ # Stage description and progress for dashboard enrichment
415
+ local cur_stage_desc=""
416
+ if [[ -n "${CURRENT_STAGE:-}" ]]; then
417
+ cur_stage_desc=$(get_stage_description "$CURRENT_STAGE")
418
+ fi
419
+ local stage_progress=""
420
+ if [[ -n "${PIPELINE_CONFIG:-}" && -f "${PIPELINE_CONFIG:-/dev/null}" ]]; then
421
+ stage_progress=$(build_stage_progress)
422
+ fi
423
+
424
+ cat > "$STATE_FILE" <<'_SW_STATE_END_'
425
+ ---
426
+ _SW_STATE_END_
427
+ # Write state with printf to avoid heredoc delimiter injection
428
+ {
429
+ printf 'pipeline: %s\n' "$PIPELINE_NAME"
430
+ printf 'goal: "%s"\n' "$GOAL"
431
+ printf 'status: %s\n' "$PIPELINE_STATUS"
432
+ printf 'issue: "%s"\n' "${GITHUB_ISSUE:-}"
433
+ printf 'branch: "%s"\n' "${GIT_BRANCH:-}"
434
+ printf 'template: "%s"\n' "${TASK_TYPE:+$(template_for_type "$TASK_TYPE")}"
435
+ printf 'current_stage: %s\n' "$CURRENT_STAGE"
436
+ printf 'current_stage_description: "%s"\n' "${cur_stage_desc}"
437
+ printf 'stage_progress: "%s"\n' "${stage_progress}"
438
+ printf 'started_at: %s\n' "${STARTED_AT:-$(now_iso)}"
439
+ printf 'updated_at: %s\n' "$(now_iso)"
440
+ printf 'elapsed: %s\n' "${total_dur:-0s}"
441
+ printf 'pr_number: %s\n' "${PR_NUMBER:-}"
442
+ printf 'progress_comment_id: %s\n' "${PROGRESS_COMMENT_ID:-}"
443
+ printf 'stages:\n'
444
+ printf '%s' "${stages_yaml}"
445
+ printf -- '---\n\n'
446
+ printf '## Log\n'
447
+ printf '%s\n' "$LOG_ENTRIES"
448
+ } >> "$STATE_FILE"
449
+ }
450
+
451
+ resume_state() {
452
+ if [[ ! -f "$STATE_FILE" ]]; then
453
+ error "No pipeline state found at $STATE_FILE"
454
+ echo -e " Start a new pipeline: ${DIM}shipwright pipeline start --goal \"...\"${RESET}"
455
+ exit 1
456
+ fi
457
+
458
+ info "Resuming pipeline from $STATE_FILE"
459
+
460
+ local in_frontmatter=false
461
+ while IFS= read -r line; do
462
+ if [[ "$line" == "---" ]]; then
463
+ if $in_frontmatter; then break; else in_frontmatter=true; continue; fi
464
+ fi
465
+ if $in_frontmatter; then
466
+ case "$line" in
467
+ pipeline:*) PIPELINE_NAME="$(echo "${line#pipeline:}" | xargs)" ;;
468
+ goal:*) GOAL="$(echo "${line#goal:}" | sed 's/^ *"//;s/" *$//')" ;;
469
+ status:*) PIPELINE_STATUS="$(echo "${line#status:}" | xargs)" ;;
470
+ issue:*) GITHUB_ISSUE="$(echo "${line#issue:}" | sed 's/^ *"//;s/" *$//')" ;;
471
+ branch:*) GIT_BRANCH="$(echo "${line#branch:}" | sed 's/^ *"//;s/" *$//')" ;;
472
+ current_stage:*) CURRENT_STAGE="$(echo "${line#current_stage:}" | xargs)" ;;
473
+ current_stage_description:*) ;; # computed field — skip on resume
474
+ stage_progress:*) ;; # computed field — skip on resume
475
+ started_at:*) STARTED_AT="$(echo "${line#started_at:}" | xargs)" ;;
476
+ pr_number:*) PR_NUMBER="$(echo "${line#pr_number:}" | xargs)" ;;
477
+ progress_comment_id:*) PROGRESS_COMMENT_ID="$(echo "${line#progress_comment_id:}" | xargs)" ;;
478
+ " "*)
479
+ local trimmed
480
+ trimmed="$(echo "$line" | xargs)"
481
+ if [[ "$trimmed" == *":"* ]]; then
482
+ local sid="${trimmed%%:*}"
483
+ local sst="${trimmed#*: }"
484
+ [[ -n "$sid" && "$sid" != "stages" ]] && STAGE_STATUSES="${STAGE_STATUSES}
485
+ ${sid}:${sst}"
486
+ fi
487
+ ;;
488
+ esac
489
+ fi
490
+ done < "$STATE_FILE"
491
+
492
+ LOG_ENTRIES="$(sed -n '/^## Log$/,$ { /^## Log$/d; p; }' "$STATE_FILE" 2>/dev/null || true)"
493
+
494
+ if [[ -n "$GITHUB_ISSUE" && "$GITHUB_ISSUE" =~ ^#([0-9]+)$ ]]; then
495
+ ISSUE_NUMBER="${BASH_REMATCH[1]}"
496
+ fi
497
+
498
+ if [[ -z "$GOAL" ]]; then
499
+ error "Could not parse goal from state file."
500
+ exit 1
501
+ fi
502
+
503
+ if [[ "$PIPELINE_STATUS" == "complete" ]]; then
504
+ warn "Pipeline already completed. Start a new one."
505
+ exit 0
506
+ fi
507
+
508
+ if [[ "$PIPELINE_STATUS" == "aborted" ]]; then
509
+ warn "Pipeline was aborted. Start a new one or edit the state file."
510
+ exit 0
511
+ fi
512
+
513
+ if [[ "$PIPELINE_STATUS" == "interrupted" ]]; then
514
+ info "Resuming from interruption..."
515
+ fi
516
+
517
+ if [[ -n "$GIT_BRANCH" ]]; then
518
+ git checkout "$GIT_BRANCH" 2>/dev/null || true
519
+ fi
520
+
521
+ PIPELINE_START_EPOCH="$(now_epoch)"
522
+ gh_init
523
+ load_pipeline_config
524
+ PIPELINE_STATUS="running"
525
+ success "Resumed pipeline: ${BOLD}$PIPELINE_NAME${RESET} — stage: $CURRENT_STAGE"
526
+ }
527
+
528
+ # ─── Task Type Detection ───────────────────────────────────────────────────
529
+
package/scripts/sw CHANGED
@@ -5,7 +5,7 @@
5
5
  # ╚═══════════════════════════════════════════════════════════════════════════╝
6
6
  set -euo pipefail
7
7
 
8
- VERSION="2.2.0"
8
+ VERSION="2.2.2"
9
9
 
10
10
  # Resolve symlinks (required for npm global install where bin/ symlinks to node_modules/)
11
11
  SOURCE="${BASH_SOURCE[0]}"
@@ -173,7 +173,9 @@ show_help() {
173
173
  echo -e " ${CYAN}init${RESET} ${BOLD}Quick tmux setup${RESET} — one command, no prompts"
174
174
  echo -e " ${CYAN}setup${RESET} ${BOLD}Guided setup${RESET} — prerequisites, init, doctor, quick start"
175
175
  echo -e " ${CYAN}help${RESET} Show this help message"
176
- echo -e " ${CYAN}version${RESET} Show version"
176
+ echo -e " ${CYAN}version${RESET} [show] Show version"
177
+ echo -e " ${CYAN}version bump${RESET} <x.y.z> Bump version everywhere (scripts, README, package.json)"
178
+ echo -e " ${CYAN}version check${RESET} Verify version consistency (CI / before release)"
177
179
  echo -e " ${CYAN}hello${RESET} Say hello world"
178
180
  echo ""
179
181
  echo -e "${BOLD}CONTINUOUS LOOP${RESET} ${DIM}(autonomous agent operation)${RESET}"
@@ -303,7 +305,8 @@ route_release() {
303
305
  release-manager) exec "$SCRIPT_DIR/sw-release-manager.sh" "$@" ;;
304
306
  changelog) exec "$SCRIPT_DIR/sw-changelog.sh" "$@" ;;
305
307
  deploy|deploys) exec "$SCRIPT_DIR/sw-github-deploy.sh" "$@" ;;
306
- help|*) echo "Usage: shipwright release {release|release-manager|changelog|deploy}"; exit 1 ;;
308
+ build) exec "$SCRIPT_DIR/build-release.sh" "$@" ;;
309
+ help|*) echo "Usage: shipwright release {release|release-manager|changelog|deploy|build}"; exit 1 ;;
307
310
  esac
308
311
  }
309
312
 
@@ -645,7 +648,26 @@ main() {
645
648
  help|--help|-h)
646
649
  show_help
647
650
  ;;
648
- version|--version|-v)
651
+ version)
652
+ case "${1:-show}" in
653
+ bump)
654
+ shift
655
+ if [[ -z "${1:-}" ]]; then
656
+ error "Usage: shipwright version bump <x.y.z>"
657
+ echo -e " ${DIM}Example: shipwright version bump 2.3.0${RESET}"
658
+ exit 1
659
+ fi
660
+ exec "$SCRIPT_DIR/update-version.sh" "$@"
661
+ ;;
662
+ check)
663
+ exec "$SCRIPT_DIR/check-version-consistency.sh" "$@"
664
+ ;;
665
+ show|*)
666
+ show_version
667
+ ;;
668
+ esac
669
+ ;;
670
+ --version|-v)
649
671
  show_version
650
672
  ;;
651
673
  hello)
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -521,7 +521,7 @@ cmd_profile() {
521
521
  local model_val
522
522
  model_val=$(get_model "build" "opus")
523
523
  local model_samples
524
- model_samples=$(jq -s "map(select(.model != null and .type == \"pipeline_complete\")) | length" "$EVENTS_FILE" 2>/dev/null || echo "0")
524
+ model_samples=$(jq -s "map(select(.model != null and .type == \"pipeline.completed\")) | length" "$EVENTS_FILE" 2>/dev/null || echo "0")
525
525
  local model_conf
526
526
  model_conf=$(confidence_level "$model_samples")
527
527
  printf "%-25s %-15s %-15s %-12s %-10s\n" "model" "$model_val" "opus" "$model_samples" "$model_conf"
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -7,7 +7,7 @@
7
7
  set -euo pipefail
8
8
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
9
 
10
- VERSION="2.2.0"
10
+ VERSION="2.2.2"
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
13
 
@@ -7,7 +7,7 @@
7
7
  set -euo pipefail
8
8
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
9
 
10
- VERSION="2.2.0"
10
+ VERSION="2.2.2"
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
13
 
@@ -8,7 +8,7 @@
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
11
- VERSION="2.2.0"
11
+ VERSION="2.2.2"
12
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
13
 
14
14
  # ─── Cross-platform compatibility ──────────────────────────────────────────