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.
- package/README.md +15 -16
- package/config/policy.schema.json +104 -29
- package/docs/AGI-PLATFORM-PLAN.md +11 -7
- package/docs/AGI-WHATS-NEXT.md +26 -20
- package/docs/README.md +2 -0
- package/package.json +1 -1
- package/scripts/check-version-consistency.sh +72 -0
- package/scripts/lib/daemon-adaptive.sh +610 -0
- package/scripts/lib/daemon-dispatch.sh +489 -0
- package/scripts/lib/daemon-failure.sh +387 -0
- package/scripts/lib/daemon-patrol.sh +1113 -0
- package/scripts/lib/daemon-poll.sh +1202 -0
- package/scripts/lib/daemon-state.sh +550 -0
- package/scripts/lib/daemon-triage.sh +490 -0
- package/scripts/lib/helpers.sh +81 -1
- package/scripts/lib/pipeline-detection.sh +278 -0
- package/scripts/lib/pipeline-github.sh +196 -0
- package/scripts/lib/pipeline-intelligence.sh +1706 -0
- package/scripts/lib/pipeline-quality-checks.sh +1054 -0
- package/scripts/lib/pipeline-quality.sh +11 -0
- package/scripts/lib/pipeline-stages.sh +2508 -0
- package/scripts/lib/pipeline-state.sh +529 -0
- package/scripts/sw +26 -4
- package/scripts/sw-activity.sh +1 -1
- package/scripts/sw-adaptive.sh +2 -2
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +1 -1
- package/scripts/sw-autonomous.sh +1 -1
- package/scripts/sw-changelog.sh +1 -1
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +1 -1
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +1 -1
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +1 -1
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +52 -4816
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +1 -1
- package/scripts/sw-decompose.sh +1 -1
- package/scripts/sw-deps.sh +1 -1
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +1 -1
- package/scripts/sw-doc-fleet.sh +1 -1
- package/scripts/sw-docs-agent.sh +1 -1
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +42 -1
- package/scripts/sw-dora.sh +1 -1
- package/scripts/sw-durable.sh +1 -1
- package/scripts/sw-e2e-orchestrator.sh +1 -1
- package/scripts/sw-eventbus.sh +1 -1
- package/scripts/sw-feedback.sh +1 -1
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +1 -1
- package/scripts/sw-fleet-viz.sh +3 -3
- package/scripts/sw-fleet.sh +1 -1
- package/scripts/sw-github-app.sh +1 -1
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +1 -1
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +1 -1
- package/scripts/sw-incident.sh +1 -1
- package/scripts/sw-init.sh +1 -1
- package/scripts/sw-instrument.sh +1 -1
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +1 -1
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +1 -1
- package/scripts/sw-memory.sh +1 -1
- package/scripts/sw-mission-control.sh +1 -1
- package/scripts/sw-model-router.sh +1 -1
- package/scripts/sw-otel.sh +4 -4
- package/scripts/sw-oversight.sh +1 -1
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +23 -56
- package/scripts/sw-pipeline.sh.mock +7 -0
- package/scripts/sw-pm.sh +1 -1
- package/scripts/sw-pr-lifecycle.sh +1 -1
- package/scripts/sw-predictive.sh +1 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +1 -1
- package/scripts/sw-quality.sh +1 -1
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +9 -1
- package/scripts/sw-regression.sh +1 -1
- package/scripts/sw-release-manager.sh +1 -1
- package/scripts/sw-release.sh +1 -1
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +1 -1
- package/scripts/sw-retro.sh +1 -1
- package/scripts/sw-scale.sh +8 -5
- package/scripts/sw-security-audit.sh +1 -1
- package/scripts/sw-self-optimize.sh +158 -7
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +3 -3
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +1 -1
- package/scripts/sw-stream.sh +8 -2
- package/scripts/sw-swarm.sh +7 -10
- package/scripts/sw-team-stages.sh +1 -1
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +1 -1
- package/scripts/sw-tmux-pipeline.sh +1 -1
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +1 -1
- package/scripts/sw-tracker.sh +24 -6
- package/scripts/sw-triage.sh +1 -1
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +1 -1
- package/scripts/sw-webhook.sh +1 -1
- package/scripts/sw-widgets.sh +1 -1
- package/scripts/sw-worktree.sh +1 -1
|
@@ -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.
|
|
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}
|
|
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
|
-
|
|
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
|
|
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)
|
package/scripts/sw-activity.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="2.2.
|
|
9
|
+
VERSION="2.2.2"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
package/scripts/sw-adaptive.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="2.2.
|
|
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 == \"
|
|
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"
|
package/scripts/sw-auth.sh
CHANGED
package/scripts/sw-autonomous.sh
CHANGED
package/scripts/sw-changelog.sh
CHANGED
package/scripts/sw-checkpoint.sh
CHANGED
|
@@ -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.
|
|
11
|
+
VERSION="2.2.2"
|
|
12
12
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
13
|
|
|
14
14
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|