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
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
# pipeline-stages.sh — Stage implementations (intake, plan, build, test, review, pr, merge, deploy, validate, monitor) for sw-pipeline.sh
|
|
1
|
+
# pipeline-stages.sh — Stage implementations (intake, plan, build, test, review, compound_quality, pr, merge, deploy, validate, monitor) for sw-pipeline.sh
|
|
2
2
|
# Source from sw-pipeline.sh. Requires all pipeline globals and state/github/detection/quality modules.
|
|
3
3
|
[[ -n "${_PIPELINE_STAGES_LOADED:-}" ]] && return 0
|
|
4
4
|
_PIPELINE_STAGES_LOADED=1
|
|
5
5
|
|
|
6
|
+
# ─── Safe git helpers ────────────────────────────────────────────────────────
|
|
7
|
+
# BASE_BRANCH may not exist locally (e.g. --local mode with no remote).
|
|
8
|
+
# These helpers return empty output instead of crashing under set -euo pipefail.
|
|
9
|
+
_safe_base_log() {
|
|
10
|
+
local branch="${BASE_BRANCH:-main}"
|
|
11
|
+
git rev-parse --verify "$branch" >/dev/null 2>&1 || { echo ""; return 0; }
|
|
12
|
+
git log "$@" "${branch}..HEAD" 2>/dev/null || true
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
_safe_base_diff() {
|
|
16
|
+
local branch="${BASE_BRANCH:-main}"
|
|
17
|
+
git rev-parse --verify "$branch" >/dev/null 2>&1 || { git diff HEAD~5 "$@" 2>/dev/null || true; return 0; }
|
|
18
|
+
git diff "${branch}...HEAD" "$@" 2>/dev/null || true
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
show_stage_preview() {
|
|
7
22
|
local stage_id="$1"
|
|
8
23
|
echo ""
|
|
@@ -12,8 +27,10 @@ show_stage_preview() {
|
|
|
12
27
|
plan) echo -e " Generate plan via Claude, post task checklist to issue" ;;
|
|
13
28
|
design) echo -e " Generate Architecture Decision Record (ADR), evaluate alternatives" ;;
|
|
14
29
|
build) echo -e " Delegate to ${CYAN}shipwright loop${RESET} for autonomous building" ;;
|
|
30
|
+
test_first) echo -e " Generate tests from requirements (TDD mode) before implementation" ;;
|
|
15
31
|
test) echo -e " Run test suite and check coverage" ;;
|
|
16
32
|
review) echo -e " AI code review on the diff, post findings" ;;
|
|
33
|
+
compound_quality) echo -e " Adversarial review, negative tests, e2e, DoD audit" ;;
|
|
17
34
|
pr) echo -e " Create GitHub PR with labels, reviewers, milestone" ;;
|
|
18
35
|
merge) echo -e " Wait for CI checks, merge PR, optionally delete branch" ;;
|
|
19
36
|
deploy) echo -e " Deploy to staging/production with rollback" ;;
|
|
@@ -125,7 +142,7 @@ stage_plan() {
|
|
|
125
142
|
CURRENT_STAGE_ID="plan"
|
|
126
143
|
local plan_file="$ARTIFACTS_DIR/plan.md"
|
|
127
144
|
|
|
128
|
-
if ! command -v claude
|
|
145
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
129
146
|
error "Claude CLI not found — cannot generate plan"
|
|
130
147
|
return 1
|
|
131
148
|
fi
|
|
@@ -138,6 +155,12 @@ stage_plan() {
|
|
|
138
155
|
"$context_script" gather --goal "$GOAL" --stage plan 2>/dev/null || true
|
|
139
156
|
fi
|
|
140
157
|
|
|
158
|
+
# Gather rich architecture context (call-graph, dependencies)
|
|
159
|
+
local arch_context=""
|
|
160
|
+
if type gather_architecture_context &>/dev/null; then
|
|
161
|
+
arch_context=$(gather_architecture_context "${PROJECT_ROOT:-.}" 2>/dev/null || true)
|
|
162
|
+
fi
|
|
163
|
+
|
|
141
164
|
# Build rich prompt with all available context
|
|
142
165
|
local plan_prompt="You are an autonomous development agent. Analyze this codebase and create a detailed implementation plan.
|
|
143
166
|
|
|
@@ -153,6 +176,14 @@ ${ISSUE_BODY}
|
|
|
153
176
|
"
|
|
154
177
|
fi
|
|
155
178
|
|
|
179
|
+
# Inject architecture context (import graph, modules, test map)
|
|
180
|
+
if [[ -n "$arch_context" ]]; then
|
|
181
|
+
plan_prompt="${plan_prompt}
|
|
182
|
+
## Architecture Context
|
|
183
|
+
${arch_context}
|
|
184
|
+
"
|
|
185
|
+
fi
|
|
186
|
+
|
|
156
187
|
# Inject context bundle from context engine (if available)
|
|
157
188
|
local _context_bundle="${ARTIFACTS_DIR}/context-bundle.md"
|
|
158
189
|
if [[ -f "$_context_bundle" ]]; then
|
|
@@ -167,7 +198,7 @@ ${_cb_content}
|
|
|
167
198
|
fi
|
|
168
199
|
|
|
169
200
|
# Inject intelligence memory context for similar past plans
|
|
170
|
-
if type intelligence_search_memory
|
|
201
|
+
if type intelligence_search_memory >/dev/null 2>&1; then
|
|
171
202
|
local plan_memory
|
|
172
203
|
plan_memory=$(intelligence_search_memory "plan stage for ${TASK_TYPE:-feature}: ${GOAL:-}" "${HOME}/.shipwright/memory" 5 2>/dev/null) || true
|
|
173
204
|
if [[ -n "$plan_memory" && "$plan_memory" != *'"results":[]'* && "$plan_memory" != *'"error"'* ]]; then
|
|
@@ -285,10 +316,22 @@ Checklist of completion criteria.
|
|
|
285
316
|
fi
|
|
286
317
|
|
|
287
318
|
local _token_log="${ARTIFACTS_DIR}/.claude-tokens-plan.log"
|
|
288
|
-
claude --print --model "$plan_model" --max-turns 25 \
|
|
319
|
+
claude --print --model "$plan_model" --max-turns 25 --dangerously-skip-permissions \
|
|
289
320
|
"$plan_prompt" < /dev/null > "$plan_file" 2>"$_token_log" || true
|
|
290
321
|
parse_claude_tokens "$_token_log"
|
|
291
322
|
|
|
323
|
+
# Claude may write to disk via tools instead of stdout — rescue those files
|
|
324
|
+
local _plan_rescue
|
|
325
|
+
for _plan_rescue in "${PROJECT_ROOT}/PLAN.md" "${PROJECT_ROOT}/plan.md" \
|
|
326
|
+
"${PROJECT_ROOT}/implementation-plan.md"; do
|
|
327
|
+
if [[ -s "$_plan_rescue" ]] && [[ $(wc -l < "$plan_file" 2>/dev/null | xargs) -lt 10 ]]; then
|
|
328
|
+
info "Plan written to ${_plan_rescue} via tools — adopting as plan artifact"
|
|
329
|
+
cat "$_plan_rescue" >> "$plan_file"
|
|
330
|
+
rm -f "$_plan_rescue"
|
|
331
|
+
break
|
|
332
|
+
fi
|
|
333
|
+
done
|
|
334
|
+
|
|
292
335
|
if [[ ! -s "$plan_file" ]]; then
|
|
293
336
|
error "Plan generation failed — empty output"
|
|
294
337
|
return 1
|
|
@@ -392,7 +435,7 @@ CC_TASKS_EOF
|
|
|
392
435
|
|
|
393
436
|
# ── Plan Validation Gate ──
|
|
394
437
|
# Ask Claude to validate the plan before proceeding
|
|
395
|
-
if command -v claude
|
|
438
|
+
if command -v claude >/dev/null 2>&1 && [[ -s "$plan_file" ]]; then
|
|
396
439
|
local validation_attempts=0
|
|
397
440
|
local max_validation_attempts=2
|
|
398
441
|
local plan_valid=false
|
|
@@ -405,7 +448,7 @@ CC_TASKS_EOF
|
|
|
405
448
|
local validation_extra=""
|
|
406
449
|
|
|
407
450
|
# Inject rejected plan history from memory
|
|
408
|
-
if type intelligence_search_memory
|
|
451
|
+
if type intelligence_search_memory >/dev/null 2>&1; then
|
|
409
452
|
local rejected_plans
|
|
410
453
|
rejected_plans=$(intelligence_search_memory "rejected plan validation failures for: ${GOAL:-}" "${HOME}/.shipwright/memory" 3 2>/dev/null) || true
|
|
411
454
|
if [[ -n "$rejected_plans" ]]; then
|
|
@@ -560,16 +603,22 @@ stage_design() {
|
|
|
560
603
|
return 0
|
|
561
604
|
fi
|
|
562
605
|
|
|
563
|
-
if ! command -v claude
|
|
606
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
564
607
|
error "Claude CLI not found — cannot generate design"
|
|
565
608
|
return 1
|
|
566
609
|
fi
|
|
567
610
|
|
|
568
611
|
info "Generating Architecture Decision Record..."
|
|
569
612
|
|
|
613
|
+
# Gather rich architecture context (call-graph, dependencies)
|
|
614
|
+
local arch_struct_context=""
|
|
615
|
+
if type gather_architecture_context &>/dev/null; then
|
|
616
|
+
arch_struct_context=$(gather_architecture_context "${PROJECT_ROOT:-.}" 2>/dev/null || true)
|
|
617
|
+
fi
|
|
618
|
+
|
|
570
619
|
# Memory integration — inject context if memory system available
|
|
571
620
|
local memory_context=""
|
|
572
|
-
if type intelligence_search_memory
|
|
621
|
+
if type intelligence_search_memory >/dev/null 2>&1; then
|
|
573
622
|
local mem_dir="${HOME}/.shipwright/memory"
|
|
574
623
|
memory_context=$(intelligence_search_memory "design stage architecture patterns for: ${GOAL:-}" "$mem_dir" 5 2>/dev/null) || true
|
|
575
624
|
fi
|
|
@@ -608,7 +657,7 @@ ${arch_layers}}"
|
|
|
608
657
|
|
|
609
658
|
# Inject rejected design approaches and anti-patterns from memory
|
|
610
659
|
local design_antipatterns=""
|
|
611
|
-
if type intelligence_search_memory
|
|
660
|
+
if type intelligence_search_memory >/dev/null 2>&1; then
|
|
612
661
|
local rejected_designs
|
|
613
662
|
rejected_designs=$(intelligence_search_memory "rejected design approaches anti-patterns for: ${GOAL:-}" "${HOME}/.shipwright/memory" 3 2>/dev/null) || true
|
|
614
663
|
if [[ -n "$rejected_designs" ]]; then
|
|
@@ -636,7 +685,10 @@ $(cat "$plan_file")
|
|
|
636
685
|
- Language: ${project_lang}
|
|
637
686
|
- Test command: ${TEST_CMD:-not configured}
|
|
638
687
|
- Task type: ${TASK_TYPE:-feature}
|
|
639
|
-
${
|
|
688
|
+
${arch_struct_context:+
|
|
689
|
+
## Architecture Context (import graph, modules, test map)
|
|
690
|
+
${arch_struct_context}
|
|
691
|
+
}${memory_context:+
|
|
640
692
|
## Historical Context (from memory)
|
|
641
693
|
${memory_context}
|
|
642
694
|
}${arch_context:+
|
|
@@ -684,10 +736,22 @@ Be concrete and specific. Reference actual file paths in the codebase. Consider
|
|
|
684
736
|
fi
|
|
685
737
|
|
|
686
738
|
local _token_log="${ARTIFACTS_DIR}/.claude-tokens-design.log"
|
|
687
|
-
claude --print --model "$design_model" --max-turns 25 \
|
|
739
|
+
claude --print --model "$design_model" --max-turns 25 --dangerously-skip-permissions \
|
|
688
740
|
"$design_prompt" < /dev/null > "$design_file" 2>"$_token_log" || true
|
|
689
741
|
parse_claude_tokens "$_token_log"
|
|
690
742
|
|
|
743
|
+
# Claude may write to disk via tools instead of stdout — rescue those files
|
|
744
|
+
local _design_rescue
|
|
745
|
+
for _design_rescue in "${PROJECT_ROOT}/design-adr.md" "${PROJECT_ROOT}/design.md" \
|
|
746
|
+
"${PROJECT_ROOT}/ADR.md" "${PROJECT_ROOT}/DESIGN.md"; do
|
|
747
|
+
if [[ -s "$_design_rescue" ]] && [[ $(wc -l < "$design_file" 2>/dev/null | xargs) -lt 10 ]]; then
|
|
748
|
+
info "Design written to ${_design_rescue} via tools — adopting as design artifact"
|
|
749
|
+
cat "$_design_rescue" >> "$design_file"
|
|
750
|
+
rm -f "$_design_rescue"
|
|
751
|
+
break
|
|
752
|
+
fi
|
|
753
|
+
done
|
|
754
|
+
|
|
691
755
|
if [[ ! -s "$design_file" ]]; then
|
|
692
756
|
error "Design generation failed — empty output"
|
|
693
757
|
return 1
|
|
@@ -715,7 +779,7 @@ Be concrete and specific. Reference actual file paths in the codebase. Consider
|
|
|
715
779
|
files_to_modify=$(sed -n '/Files to modify/,/^-\|^#\|^$/p' "$design_file" 2>/dev/null | grep -E '^\s*-' | head -20 || true)
|
|
716
780
|
|
|
717
781
|
if [[ -n "$files_to_create" || -n "$files_to_modify" ]]; then
|
|
718
|
-
info "Design scope: ${DIM}$(echo "$files_to_create $files_to_modify" | grep -c '^\s*-' ||
|
|
782
|
+
info "Design scope: ${DIM}$(echo "$files_to_create $files_to_modify" | grep -c '^\s*-' || true) file(s)${RESET}"
|
|
719
783
|
fi
|
|
720
784
|
|
|
721
785
|
# Post design to GitHub issue
|
|
@@ -741,6 +805,117 @@ _Generated by \`shipwright pipeline\` design stage at $(now_iso)_"
|
|
|
741
805
|
log_stage "design" "Generated design.md (${line_count} lines)"
|
|
742
806
|
}
|
|
743
807
|
|
|
808
|
+
# ─── TDD: Generate tests before implementation ─────────────────────────────────
|
|
809
|
+
stage_test_first() {
|
|
810
|
+
CURRENT_STAGE_ID="test_first"
|
|
811
|
+
info "Generating tests from requirements (TDD mode)"
|
|
812
|
+
|
|
813
|
+
local plan_file="${ARTIFACTS_DIR}/plan.md"
|
|
814
|
+
local goal_file="${PROJECT_ROOT}/.claude/goal.md"
|
|
815
|
+
local requirements=""
|
|
816
|
+
if [[ -f "$plan_file" ]]; then
|
|
817
|
+
requirements=$(cat "$plan_file" 2>/dev/null || true)
|
|
818
|
+
elif [[ -f "$goal_file" ]]; then
|
|
819
|
+
requirements=$(cat "$goal_file" 2>/dev/null || true)
|
|
820
|
+
else
|
|
821
|
+
requirements="${GOAL:-}: ${ISSUE_BODY:-}"
|
|
822
|
+
fi
|
|
823
|
+
|
|
824
|
+
local tdd_prompt="You are writing tests BEFORE implementation (TDD).
|
|
825
|
+
|
|
826
|
+
Based on the following plan/requirements, generate test files that define the expected behavior. These tests should FAIL initially (since the implementation doesn't exist yet) but define the correct interface and behavior.
|
|
827
|
+
|
|
828
|
+
Requirements:
|
|
829
|
+
${requirements}
|
|
830
|
+
|
|
831
|
+
Instructions:
|
|
832
|
+
1. Create test files for each component mentioned in the plan
|
|
833
|
+
2. Tests should verify the PUBLIC interface and expected behavior
|
|
834
|
+
3. Include edge cases and error handling tests
|
|
835
|
+
4. Tests should be runnable with the project's test framework
|
|
836
|
+
5. Mark tests that need implementation with clear TODO comments
|
|
837
|
+
6. Do NOT write implementation code — only tests
|
|
838
|
+
|
|
839
|
+
Output format: For each test file, use a fenced code block with the file path as the language identifier (e.g. \`\`\`tests/auth.test.ts):
|
|
840
|
+
\`\`\`path/to/test.test.ts
|
|
841
|
+
// file content
|
|
842
|
+
\`\`\`
|
|
843
|
+
|
|
844
|
+
Create files in the appropriate project directories (e.g. tests/, __tests__/, src/**/*.test.ts) per project convention."
|
|
845
|
+
|
|
846
|
+
local model="${CLAUDE_MODEL:-${MODEL:-sonnet}}"
|
|
847
|
+
[[ -z "$model" || "$model" == "null" ]] && model="sonnet"
|
|
848
|
+
|
|
849
|
+
local output=""
|
|
850
|
+
output=$(echo "$tdd_prompt" | timeout 120 claude --print --model "$model" 2>/dev/null) || {
|
|
851
|
+
warn "TDD test generation failed, falling back to standard build"
|
|
852
|
+
return 1
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
# Parse output: extract fenced code blocks and write to files
|
|
856
|
+
local wrote_any=false
|
|
857
|
+
local block_path="" in_block=false block_content=""
|
|
858
|
+
while IFS= read -r line; do
|
|
859
|
+
if [[ "$line" =~ ^\`\`\`([a-zA-Z0-9_/\.\-]+)$ ]]; then
|
|
860
|
+
if [[ -n "$block_path" && -n "$block_content" ]]; then
|
|
861
|
+
local out_file="${PROJECT_ROOT}/${block_path}"
|
|
862
|
+
local out_dir
|
|
863
|
+
out_dir=$(dirname "$out_file")
|
|
864
|
+
mkdir -p "$out_dir" 2>/dev/null || true
|
|
865
|
+
if echo "$block_content" > "$out_file" 2>/dev/null; then
|
|
866
|
+
wrote_any=true
|
|
867
|
+
info " Wrote: $block_path"
|
|
868
|
+
fi
|
|
869
|
+
fi
|
|
870
|
+
block_path="${BASH_REMATCH[1]}"
|
|
871
|
+
block_content=""
|
|
872
|
+
in_block=true
|
|
873
|
+
elif [[ "$line" == "\`\`\`" && "$in_block" == "true" ]]; then
|
|
874
|
+
if [[ -n "$block_path" && -n "$block_content" ]]; then
|
|
875
|
+
local out_file="${PROJECT_ROOT}/${block_path}"
|
|
876
|
+
local out_dir
|
|
877
|
+
out_dir=$(dirname "$out_file")
|
|
878
|
+
mkdir -p "$out_dir" 2>/dev/null || true
|
|
879
|
+
if echo "$block_content" > "$out_file" 2>/dev/null; then
|
|
880
|
+
wrote_any=true
|
|
881
|
+
info " Wrote: $block_path"
|
|
882
|
+
fi
|
|
883
|
+
fi
|
|
884
|
+
block_path=""
|
|
885
|
+
block_content=""
|
|
886
|
+
in_block=false
|
|
887
|
+
elif [[ "$in_block" == "true" && -n "$block_path" ]]; then
|
|
888
|
+
[[ -n "$block_content" ]] && block_content="${block_content}"$'\n'
|
|
889
|
+
block_content="${block_content}${line}"
|
|
890
|
+
fi
|
|
891
|
+
done <<< "$output"
|
|
892
|
+
|
|
893
|
+
# Flush last block if unclosed
|
|
894
|
+
if [[ -n "$block_path" && -n "$block_content" ]]; then
|
|
895
|
+
local out_file="${PROJECT_ROOT}/${block_path}"
|
|
896
|
+
local out_dir
|
|
897
|
+
out_dir=$(dirname "$out_file")
|
|
898
|
+
mkdir -p "$out_dir" 2>/dev/null || true
|
|
899
|
+
if echo "$block_content" > "$out_file" 2>/dev/null; then
|
|
900
|
+
wrote_any=true
|
|
901
|
+
info " Wrote: $block_path"
|
|
902
|
+
fi
|
|
903
|
+
fi
|
|
904
|
+
|
|
905
|
+
if [[ "$wrote_any" == "true" ]]; then
|
|
906
|
+
if (cd "$PROJECT_ROOT" && git diff --name-only 2>/dev/null | grep -qE 'test|spec'); then
|
|
907
|
+
git add -A 2>/dev/null || true
|
|
908
|
+
git commit -m "test: TDD - define expected behavior before implementation" 2>/dev/null || true
|
|
909
|
+
emit_event "tdd.tests_generated" "{\"stage\":\"test_first\"}"
|
|
910
|
+
fi
|
|
911
|
+
success "TDD tests generated"
|
|
912
|
+
else
|
|
913
|
+
warn "No test files extracted from TDD output — check format"
|
|
914
|
+
fi
|
|
915
|
+
|
|
916
|
+
return 0
|
|
917
|
+
}
|
|
918
|
+
|
|
744
919
|
stage_build() {
|
|
745
920
|
local plan_file="$ARTIFACTS_DIR/plan.md"
|
|
746
921
|
local design_file="$ARTIFACTS_DIR/design.md"
|
|
@@ -749,7 +924,7 @@ stage_build() {
|
|
|
749
924
|
|
|
750
925
|
# Memory integration — inject context if memory system available
|
|
751
926
|
local memory_context=""
|
|
752
|
-
if type intelligence_search_memory
|
|
927
|
+
if type intelligence_search_memory >/dev/null 2>&1; then
|
|
753
928
|
local mem_dir="${HOME}/.shipwright/memory"
|
|
754
929
|
memory_context=$(intelligence_search_memory "build stage for: ${GOAL:-}" "$mem_dir" 5 2>/dev/null) || true
|
|
755
930
|
fi
|
|
@@ -761,6 +936,13 @@ stage_build() {
|
|
|
761
936
|
local enriched_goal
|
|
762
937
|
enriched_goal=$(_pipeline_compact_goal "$GOAL" "$plan_file" "$design_file")
|
|
763
938
|
|
|
939
|
+
# TDD: when test_first ran, tell build to make existing tests pass
|
|
940
|
+
if [[ "${TDD_ENABLED:-false}" == "true" || "${PIPELINE_TDD:-}" == "true" ]]; then
|
|
941
|
+
enriched_goal="${enriched_goal}
|
|
942
|
+
|
|
943
|
+
IMPORTANT (TDD mode): Test files already exist and define the expected behavior. Write implementation code to make ALL tests pass. Do not delete or modify the test files."
|
|
944
|
+
fi
|
|
945
|
+
|
|
764
946
|
# Inject memory context
|
|
765
947
|
if [[ -n "$memory_context" ]]; then
|
|
766
948
|
enriched_goal="${enriched_goal}
|
|
@@ -790,7 +972,7 @@ $(cat "$TASKS_FILE")"
|
|
|
790
972
|
fi
|
|
791
973
|
|
|
792
974
|
# Inject file hotspots from GitHub intelligence
|
|
793
|
-
if [[ "${NO_GITHUB:-}" != "true" ]] && type gh_file_change_frequency
|
|
975
|
+
if [[ "${NO_GITHUB:-}" != "true" ]] && type gh_file_change_frequency >/dev/null 2>&1; then
|
|
794
976
|
local build_hotspots
|
|
795
977
|
build_hotspots=$(gh_file_change_frequency 2>/dev/null | head -5 || true)
|
|
796
978
|
if [[ -n "$build_hotspots" ]]; then
|
|
@@ -802,7 +984,7 @@ ${build_hotspots}"
|
|
|
802
984
|
fi
|
|
803
985
|
|
|
804
986
|
# Inject security alerts context
|
|
805
|
-
if [[ "${NO_GITHUB:-}" != "true" ]] && type gh_security_alerts
|
|
987
|
+
if [[ "${NO_GITHUB:-}" != "true" ]] && type gh_security_alerts >/dev/null 2>&1; then
|
|
806
988
|
local build_alerts
|
|
807
989
|
build_alerts=$(gh_security_alerts 2>/dev/null | head -3 || true)
|
|
808
990
|
if [[ -n "$build_alerts" ]]; then
|
|
@@ -930,8 +1112,14 @@ ${prevention_text}"
|
|
|
930
1112
|
# Definition of Done: use plan-extracted DoD if available
|
|
931
1113
|
[[ -s "$dod_file" ]] && loop_args+=(--definition-of-done "$dod_file")
|
|
932
1114
|
|
|
933
|
-
#
|
|
934
|
-
[[ "${
|
|
1115
|
+
# Checkpoint resume: when pipeline resumed from build-stage checkpoint, pass --resume to loop
|
|
1116
|
+
if [[ "${RESUME_FROM_CHECKPOINT:-false}" == "true" && "${checkpoint_stage:-}" == "build" ]]; then
|
|
1117
|
+
loop_args+=(--resume)
|
|
1118
|
+
fi
|
|
1119
|
+
|
|
1120
|
+
# Skip permissions — pipeline runs headlessly (claude -p) and has no terminal
|
|
1121
|
+
# for interactive permission prompts. Without this flag, agents can't write files.
|
|
1122
|
+
loop_args+=(--skip-permissions)
|
|
935
1123
|
|
|
936
1124
|
info "Starting build loop: ${DIM}shipwright loop${RESET} (max ${max_iter} iterations, ${agents} agent(s))"
|
|
937
1125
|
|
|
@@ -967,7 +1155,7 @@ ${prevention_text}"
|
|
|
967
1155
|
|
|
968
1156
|
# Read accumulated token counts from build loop (written by sw-loop.sh)
|
|
969
1157
|
local _loop_token_file="${PROJECT_ROOT}/.claude/loop-logs/loop-tokens.json"
|
|
970
|
-
if [[ -f "$_loop_token_file" ]] && command -v jq
|
|
1158
|
+
if [[ -f "$_loop_token_file" ]] && command -v jq >/dev/null 2>&1; then
|
|
971
1159
|
local _loop_in _loop_out _loop_cost
|
|
972
1160
|
_loop_in=$(jq -r '.input_tokens // 0' "$_loop_token_file" 2>/dev/null || echo "0")
|
|
973
1161
|
_loop_out=$(jq -r '.output_tokens // 0' "$_loop_token_file" 2>/dev/null || echo "0")
|
|
@@ -984,13 +1172,13 @@ ${prevention_text}"
|
|
|
984
1172
|
|
|
985
1173
|
# Count commits made during build
|
|
986
1174
|
local commit_count
|
|
987
|
-
commit_count=$(
|
|
1175
|
+
commit_count=$(_safe_base_log --oneline | wc -l | xargs)
|
|
988
1176
|
info "Build produced ${BOLD}$commit_count${RESET} commit(s)"
|
|
989
1177
|
|
|
990
1178
|
# Commit quality evaluation when intelligence is enabled
|
|
991
|
-
if type intelligence_search_memory
|
|
1179
|
+
if type intelligence_search_memory >/dev/null 2>&1 && command -v claude >/dev/null 2>&1 && [[ "${commit_count:-0}" -gt 0 ]]; then
|
|
992
1180
|
local commit_msgs
|
|
993
|
-
commit_msgs=$(
|
|
1181
|
+
commit_msgs=$(_safe_base_log --format="%s" | head -20)
|
|
994
1182
|
local quality_score
|
|
995
1183
|
quality_score=$(claude --print --output-format text -p "Rate the quality of these git commit messages on a scale of 0-100. Consider: focus (one thing per commit), clarity (describes the why), atomicity (small logical units). Reply with ONLY a number 0-100.
|
|
996
1184
|
|
|
@@ -1086,6 +1274,14 @@ ${log_excerpt}
|
|
|
1086
1274
|
fi
|
|
1087
1275
|
fi
|
|
1088
1276
|
|
|
1277
|
+
# Emit test.completed with coverage for adaptive learning
|
|
1278
|
+
if [[ -n "$coverage" ]]; then
|
|
1279
|
+
emit_event "test.completed" \
|
|
1280
|
+
"issue=${ISSUE_NUMBER:-0}" \
|
|
1281
|
+
"stage=test" \
|
|
1282
|
+
"coverage=$coverage"
|
|
1283
|
+
fi
|
|
1284
|
+
|
|
1089
1285
|
# Post test results to GitHub
|
|
1090
1286
|
if [[ -n "$ISSUE_NUMBER" ]]; then
|
|
1091
1287
|
local test_summary
|
|
@@ -1121,27 +1317,26 @@ stage_review() {
|
|
|
1121
1317
|
local diff_file="$ARTIFACTS_DIR/review-diff.patch"
|
|
1122
1318
|
local review_file="$ARTIFACTS_DIR/review.md"
|
|
1123
1319
|
|
|
1124
|
-
|
|
1125
|
-
git diff HEAD~5 > "$diff_file" 2>/dev/null || true
|
|
1320
|
+
_safe_base_diff > "$diff_file" 2>/dev/null || true
|
|
1126
1321
|
|
|
1127
1322
|
if [[ ! -s "$diff_file" ]]; then
|
|
1128
1323
|
warn "No diff found — skipping review"
|
|
1129
1324
|
return 0
|
|
1130
1325
|
fi
|
|
1131
1326
|
|
|
1132
|
-
if ! command -v claude
|
|
1327
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
1133
1328
|
warn "Claude CLI not found — skipping AI review"
|
|
1134
1329
|
return 0
|
|
1135
1330
|
fi
|
|
1136
1331
|
|
|
1137
1332
|
local diff_stats
|
|
1138
|
-
diff_stats=$(
|
|
1333
|
+
diff_stats=$(_safe_base_diff --stat | tail -1 || echo "")
|
|
1139
1334
|
info "Running AI code review... ${DIM}($diff_stats)${RESET}"
|
|
1140
1335
|
|
|
1141
1336
|
# Semantic risk scoring when intelligence is enabled
|
|
1142
|
-
if type intelligence_search_memory
|
|
1337
|
+
if type intelligence_search_memory >/dev/null 2>&1 && command -v claude >/dev/null 2>&1; then
|
|
1143
1338
|
local diff_files
|
|
1144
|
-
diff_files=$(
|
|
1339
|
+
diff_files=$(_safe_base_diff --name-only || true)
|
|
1145
1340
|
local risk_score="low"
|
|
1146
1341
|
# Fast heuristic: flag high-risk file patterns
|
|
1147
1342
|
if echo "$diff_files" | grep -qiE 'migration|schema|auth|crypto|security|password|token|secret|\.env'; then
|
|
@@ -1185,7 +1380,7 @@ If no issues are found, write: \"Review clean — no issues found.\"
|
|
|
1185
1380
|
"
|
|
1186
1381
|
|
|
1187
1382
|
# Inject previous review findings and anti-patterns from memory
|
|
1188
|
-
if type intelligence_search_memory
|
|
1383
|
+
if type intelligence_search_memory >/dev/null 2>&1; then
|
|
1189
1384
|
local review_memory
|
|
1190
1385
|
review_memory=$(intelligence_search_memory "code review findings anti-patterns for: ${GOAL:-}" "${HOME}/.shipwright/memory" 5 2>/dev/null) || true
|
|
1191
1386
|
if [[ -n "$review_memory" ]]; then
|
|
@@ -1211,7 +1406,7 @@ ${conventions}
|
|
|
1211
1406
|
fi
|
|
1212
1407
|
|
|
1213
1408
|
# Inject CODEOWNERS focus areas for review
|
|
1214
|
-
if [[ "${NO_GITHUB:-}" != "true" ]] && type gh_codeowners
|
|
1409
|
+
if [[ "${NO_GITHUB:-}" != "true" ]] && type gh_codeowners >/dev/null 2>&1; then
|
|
1215
1410
|
local review_owners
|
|
1216
1411
|
review_owners=$(gh_codeowners 2>/dev/null | head -10 || true)
|
|
1217
1412
|
if [[ -n "$review_owners" ]]; then
|
|
@@ -1235,11 +1430,9 @@ $(cat "$dod_file")
|
|
|
1235
1430
|
## Diff to Review
|
|
1236
1431
|
$(cat "$diff_file")"
|
|
1237
1432
|
|
|
1238
|
-
#
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
review_args+=(--dangerously-skip-permissions)
|
|
1242
|
-
fi
|
|
1433
|
+
# Skip permissions — pipeline runs headlessly (claude -p) and has no terminal
|
|
1434
|
+
# for interactive permission prompts. Same rationale as build stage (line ~1083).
|
|
1435
|
+
local review_args=(--print --model "$review_model" --max-turns 25 --dangerously-skip-permissions)
|
|
1243
1436
|
|
|
1244
1437
|
claude "${review_args[@]}" "$review_prompt" < /dev/null > "$review_file" 2>"${ARTIFACTS_DIR}/.claude-tokens-review.log" || true
|
|
1245
1438
|
parse_claude_tokens "${ARTIFACTS_DIR}/.claude-tokens-review.log"
|
|
@@ -1384,15 +1577,143 @@ ${review_summary}
|
|
|
1384
1577
|
log_stage "review" "AI review complete ($total_issues issues: $critical_count critical, $bug_count bugs, $warning_count suggestions)"
|
|
1385
1578
|
}
|
|
1386
1579
|
|
|
1580
|
+
# ─── Compound Quality (fallback) ────────────────────────────────────────────
|
|
1581
|
+
# Basic implementation: adversarial review, negative testing, e2e checks, DoD audit.
|
|
1582
|
+
# If pipeline-intelligence.sh was sourced first, its enhanced version takes priority.
|
|
1583
|
+
if ! type stage_compound_quality >/dev/null 2>&1; then
|
|
1584
|
+
stage_compound_quality() {
|
|
1585
|
+
CURRENT_STAGE_ID="compound_quality"
|
|
1586
|
+
|
|
1587
|
+
# Read stage config from pipeline template
|
|
1588
|
+
local cfg
|
|
1589
|
+
cfg=$(jq -r '.stages[] | select(.id == "compound_quality") | .config // {}' "$PIPELINE_CONFIG" 2>/dev/null) || cfg="{}"
|
|
1590
|
+
|
|
1591
|
+
local do_adversarial do_negative do_e2e do_dod max_cycles blocking
|
|
1592
|
+
do_adversarial=$(echo "$cfg" | jq -r '.adversarial // false')
|
|
1593
|
+
do_negative=$(echo "$cfg" | jq -r '.negative // false')
|
|
1594
|
+
do_e2e=$(echo "$cfg" | jq -r '.e2e // false')
|
|
1595
|
+
do_dod=$(echo "$cfg" | jq -r '.dod_audit // false')
|
|
1596
|
+
max_cycles=$(echo "$cfg" | jq -r '.max_cycles // 1')
|
|
1597
|
+
blocking=$(echo "$cfg" | jq -r '.compound_quality_blocking // false')
|
|
1598
|
+
|
|
1599
|
+
local pass_count=0 fail_count=0 total=0
|
|
1600
|
+
local compound_log="$ARTIFACTS_DIR/compound-quality.log"
|
|
1601
|
+
: > "$compound_log"
|
|
1602
|
+
|
|
1603
|
+
# ── Adversarial review ──
|
|
1604
|
+
if [[ "$do_adversarial" == "true" ]]; then
|
|
1605
|
+
total=$((total + 1))
|
|
1606
|
+
info "Running adversarial review..."
|
|
1607
|
+
if [[ -x "$SCRIPT_DIR/sw-adversarial.sh" ]]; then
|
|
1608
|
+
if bash "$SCRIPT_DIR/sw-adversarial.sh" --repo "${REPO_DIR:-.}" >> "$compound_log" 2>&1; then
|
|
1609
|
+
pass_count=$((pass_count + 1))
|
|
1610
|
+
success "Adversarial review passed"
|
|
1611
|
+
else
|
|
1612
|
+
fail_count=$((fail_count + 1))
|
|
1613
|
+
warn "Adversarial review found issues"
|
|
1614
|
+
fi
|
|
1615
|
+
else
|
|
1616
|
+
warn "sw-adversarial.sh not found, skipping"
|
|
1617
|
+
fi
|
|
1618
|
+
fi
|
|
1619
|
+
|
|
1620
|
+
# ── Negative / edge-case testing ──
|
|
1621
|
+
if [[ "$do_negative" == "true" ]]; then
|
|
1622
|
+
total=$((total + 1))
|
|
1623
|
+
info "Running negative test pass..."
|
|
1624
|
+
if [[ -n "${TEST_CMD:-}" ]]; then
|
|
1625
|
+
if eval "$TEST_CMD" >> "$compound_log" 2>&1; then
|
|
1626
|
+
pass_count=$((pass_count + 1))
|
|
1627
|
+
success "Negative test pass passed"
|
|
1628
|
+
else
|
|
1629
|
+
fail_count=$((fail_count + 1))
|
|
1630
|
+
warn "Negative test pass found failures"
|
|
1631
|
+
fi
|
|
1632
|
+
else
|
|
1633
|
+
pass_count=$((pass_count + 1))
|
|
1634
|
+
info "No test command configured, skipping negative tests"
|
|
1635
|
+
fi
|
|
1636
|
+
fi
|
|
1637
|
+
|
|
1638
|
+
# ── E2E checks ──
|
|
1639
|
+
if [[ "$do_e2e" == "true" ]]; then
|
|
1640
|
+
total=$((total + 1))
|
|
1641
|
+
info "Running e2e checks..."
|
|
1642
|
+
if [[ -x "$SCRIPT_DIR/sw-e2e-orchestrator.sh" ]]; then
|
|
1643
|
+
if bash "$SCRIPT_DIR/sw-e2e-orchestrator.sh" run >> "$compound_log" 2>&1; then
|
|
1644
|
+
pass_count=$((pass_count + 1))
|
|
1645
|
+
success "E2E checks passed"
|
|
1646
|
+
else
|
|
1647
|
+
fail_count=$((fail_count + 1))
|
|
1648
|
+
warn "E2E checks found issues"
|
|
1649
|
+
fi
|
|
1650
|
+
else
|
|
1651
|
+
pass_count=$((pass_count + 1))
|
|
1652
|
+
info "sw-e2e-orchestrator.sh not found, skipping e2e"
|
|
1653
|
+
fi
|
|
1654
|
+
fi
|
|
1655
|
+
|
|
1656
|
+
# ── Definition of Done audit ──
|
|
1657
|
+
if [[ "$do_dod" == "true" ]]; then
|
|
1658
|
+
total=$((total + 1))
|
|
1659
|
+
info "Running definition-of-done audit..."
|
|
1660
|
+
if [[ -x "$SCRIPT_DIR/sw-quality.sh" ]]; then
|
|
1661
|
+
if bash "$SCRIPT_DIR/sw-quality.sh" validate >> "$compound_log" 2>&1; then
|
|
1662
|
+
pass_count=$((pass_count + 1))
|
|
1663
|
+
success "DoD audit passed"
|
|
1664
|
+
else
|
|
1665
|
+
fail_count=$((fail_count + 1))
|
|
1666
|
+
warn "DoD audit found gaps"
|
|
1667
|
+
fi
|
|
1668
|
+
else
|
|
1669
|
+
pass_count=$((pass_count + 1))
|
|
1670
|
+
info "sw-quality.sh not found, skipping DoD audit"
|
|
1671
|
+
fi
|
|
1672
|
+
fi
|
|
1673
|
+
|
|
1674
|
+
# ── Summary ──
|
|
1675
|
+
log_stage "compound_quality" "Compound quality: $pass_count/$total checks passed, $fail_count failed"
|
|
1676
|
+
|
|
1677
|
+
if [[ "$fail_count" -gt 0 && "$blocking" == "true" ]]; then
|
|
1678
|
+
error "Compound quality gate failed: $fail_count of $total checks failed"
|
|
1679
|
+
return 1
|
|
1680
|
+
fi
|
|
1681
|
+
|
|
1682
|
+
return 0
|
|
1683
|
+
}
|
|
1684
|
+
fi # end fallback stage_compound_quality
|
|
1685
|
+
|
|
1387
1686
|
stage_pr() {
|
|
1388
1687
|
CURRENT_STAGE_ID="pr"
|
|
1389
1688
|
local plan_file="$ARTIFACTS_DIR/plan.md"
|
|
1390
1689
|
local test_log="$ARTIFACTS_DIR/test-results.log"
|
|
1391
1690
|
local review_file="$ARTIFACTS_DIR/review.md"
|
|
1392
1691
|
|
|
1692
|
+
# ── Skip PR in local/no-github mode ──
|
|
1693
|
+
if [[ "${NO_GITHUB:-false}" == "true" || "${SHIPWRIGHT_LOCAL:-}" == "1" || "${LOCAL_MODE:-false}" == "true" ]]; then
|
|
1694
|
+
info "Skipping PR stage — running in local/no-github mode"
|
|
1695
|
+
# Save a PR draft locally for reference
|
|
1696
|
+
local branch_name
|
|
1697
|
+
branch_name=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
1698
|
+
local commit_count
|
|
1699
|
+
commit_count=$(_safe_base_log --oneline | wc -l | xargs)
|
|
1700
|
+
{
|
|
1701
|
+
echo "# PR Draft (local mode)"
|
|
1702
|
+
echo ""
|
|
1703
|
+
echo "**Branch:** ${branch_name}"
|
|
1704
|
+
echo "**Commits:** ${commit_count:-0}"
|
|
1705
|
+
echo "**Goal:** ${GOAL:-N/A}"
|
|
1706
|
+
echo ""
|
|
1707
|
+
echo "## Changes"
|
|
1708
|
+
_safe_base_diff --stat || true
|
|
1709
|
+
} > ".claude/pr-draft.md" 2>/dev/null || true
|
|
1710
|
+
emit_event "pr.skipped" "issue=${ISSUE_NUMBER:-0}" "reason=local_mode"
|
|
1711
|
+
return 0
|
|
1712
|
+
fi
|
|
1713
|
+
|
|
1393
1714
|
# ── PR Hygiene Checks (informational) ──
|
|
1394
1715
|
local hygiene_commit_count
|
|
1395
|
-
hygiene_commit_count=$(
|
|
1716
|
+
hygiene_commit_count=$(_safe_base_log --oneline | wc -l | xargs)
|
|
1396
1717
|
hygiene_commit_count="${hygiene_commit_count:-0}"
|
|
1397
1718
|
|
|
1398
1719
|
if [[ "$hygiene_commit_count" -gt 20 ]]; then
|
|
@@ -1401,7 +1722,7 @@ stage_pr() {
|
|
|
1401
1722
|
|
|
1402
1723
|
# Check for WIP/fixup/squash commits (expanded patterns)
|
|
1403
1724
|
local wip_commits
|
|
1404
|
-
wip_commits=$(
|
|
1725
|
+
wip_commits=$(_safe_base_log --oneline | grep -ciE '^[0-9a-f]+ (WIP|fixup!|squash!|TODO|HACK|TEMP|BROKEN|wip[:-]|temp[:-]|broken[:-]|do not merge)' || true)
|
|
1405
1726
|
wip_commits="${wip_commits:-0}"
|
|
1406
1727
|
if [[ "$wip_commits" -gt 0 ]]; then
|
|
1407
1728
|
warn "Branch has ${wip_commits} WIP/fixup/squash/temp commit(s) — consider cleaning up"
|
|
@@ -1409,7 +1730,7 @@ stage_pr() {
|
|
|
1409
1730
|
|
|
1410
1731
|
# ── PR Quality Gate: reject PRs with no real code changes ──
|
|
1411
1732
|
local real_files
|
|
1412
|
-
real_files=$(
|
|
1733
|
+
real_files=$(_safe_base_diff --name-only | grep -v '^\.claude/' | grep -v '^\.github/' || true)
|
|
1413
1734
|
if [[ -z "$real_files" ]]; then
|
|
1414
1735
|
error "No real code changes detected — only pipeline artifacts (.claude/ logs)."
|
|
1415
1736
|
error "The build agent did not produce meaningful changes. Skipping PR creation."
|
|
@@ -1448,7 +1769,7 @@ stage_pr() {
|
|
|
1448
1769
|
|
|
1449
1770
|
# ── Developer Simulation (pre-PR review) ──
|
|
1450
1771
|
local simulation_summary=""
|
|
1451
|
-
if type simulation_review
|
|
1772
|
+
if type simulation_review >/dev/null 2>&1; then
|
|
1452
1773
|
local sim_enabled
|
|
1453
1774
|
sim_enabled=$(jq -r '.intelligence.simulation_enabled // false' "$PIPELINE_CONFIG" 2>/dev/null || echo "false")
|
|
1454
1775
|
# Also check daemon-config
|
|
@@ -1459,7 +1780,7 @@ stage_pr() {
|
|
|
1459
1780
|
if [[ "$sim_enabled" == "true" ]]; then
|
|
1460
1781
|
info "Running developer simulation review..."
|
|
1461
1782
|
local diff_for_sim
|
|
1462
|
-
diff_for_sim=$(
|
|
1783
|
+
diff_for_sim=$(_safe_base_diff || true)
|
|
1463
1784
|
if [[ -n "$diff_for_sim" ]]; then
|
|
1464
1785
|
local sim_result
|
|
1465
1786
|
sim_result=$(simulation_review "$diff_for_sim" "${GOAL:-}" 2>/dev/null || echo "")
|
|
@@ -1479,7 +1800,7 @@ stage_pr() {
|
|
|
1479
1800
|
|
|
1480
1801
|
# ── Architecture Validation (pre-PR check) ──
|
|
1481
1802
|
local arch_summary=""
|
|
1482
|
-
if type architecture_validate_changes
|
|
1803
|
+
if type architecture_validate_changes >/dev/null 2>&1; then
|
|
1483
1804
|
local arch_enabled
|
|
1484
1805
|
arch_enabled=$(jq -r '.intelligence.architecture_enabled // false' "$PIPELINE_CONFIG" 2>/dev/null || echo "false")
|
|
1485
1806
|
local daemon_cfg=".claude/daemon-config.json"
|
|
@@ -1489,7 +1810,7 @@ stage_pr() {
|
|
|
1489
1810
|
if [[ "$arch_enabled" == "true" ]]; then
|
|
1490
1811
|
info "Validating architecture..."
|
|
1491
1812
|
local diff_for_arch
|
|
1492
|
-
diff_for_arch=$(
|
|
1813
|
+
diff_for_arch=$(_safe_base_diff || true)
|
|
1493
1814
|
if [[ -n "$diff_for_arch" ]]; then
|
|
1494
1815
|
local arch_result
|
|
1495
1816
|
arch_result=$(architecture_validate_changes "$diff_for_arch" "" 2>/dev/null || echo "")
|
|
@@ -1513,10 +1834,10 @@ stage_pr() {
|
|
|
1513
1834
|
|
|
1514
1835
|
# Pre-PR diff gate — verify meaningful code changes exist (not just bookkeeping)
|
|
1515
1836
|
local real_changes
|
|
1516
|
-
real_changes=$(
|
|
1837
|
+
real_changes=$(_safe_base_diff --name-only \
|
|
1517
1838
|
-- . ':!.claude/loop-state.md' ':!.claude/pipeline-state.md' \
|
|
1518
1839
|
':!.claude/pipeline-artifacts/*' ':!**/progress.md' \
|
|
1519
|
-
':!**/error-summary.json'
|
|
1840
|
+
':!**/error-summary.json' | wc -l | xargs || echo "0")
|
|
1520
1841
|
if [[ "${real_changes:-0}" -eq 0 ]]; then
|
|
1521
1842
|
error "No meaningful code changes detected — only bookkeeping files modified"
|
|
1522
1843
|
error "Refusing to create PR with zero real changes"
|
|
@@ -1571,10 +1892,10 @@ stage_pr() {
|
|
|
1571
1892
|
[[ -n "${GITHUB_ISSUE:-}" ]] && closes_line="Closes ${GITHUB_ISSUE}"
|
|
1572
1893
|
|
|
1573
1894
|
local diff_stats
|
|
1574
|
-
diff_stats=$(
|
|
1895
|
+
diff_stats=$(_safe_base_diff --stat | tail -1 || echo "")
|
|
1575
1896
|
|
|
1576
1897
|
local commit_count
|
|
1577
|
-
commit_count=$(
|
|
1898
|
+
commit_count=$(_safe_base_log --oneline | wc -l | xargs)
|
|
1578
1899
|
|
|
1579
1900
|
local total_dur=""
|
|
1580
1901
|
if [[ -n "$PIPELINE_START_EPOCH" ]]; then
|
|
@@ -1614,6 +1935,65 @@ Generated by \`shipwright pipeline\`
|
|
|
1614
1935
|
EOF
|
|
1615
1936
|
)"
|
|
1616
1937
|
|
|
1938
|
+
# Verify required evidence before PR (merge policy enforcement)
|
|
1939
|
+
local risk_tier
|
|
1940
|
+
risk_tier="low"
|
|
1941
|
+
if [[ -f "$REPO_DIR/config/policy.json" ]]; then
|
|
1942
|
+
local changed_files
|
|
1943
|
+
changed_files=$(_safe_base_diff --name-only || true)
|
|
1944
|
+
if [[ -n "$changed_files" ]]; then
|
|
1945
|
+
local policy_file="$REPO_DIR/config/policy.json"
|
|
1946
|
+
check_tier_match() {
|
|
1947
|
+
local tier="$1"
|
|
1948
|
+
local patterns
|
|
1949
|
+
patterns=$(jq -r ".riskTierRules.${tier}[]? // empty" "$policy_file" 2>/dev/null)
|
|
1950
|
+
[[ -z "$patterns" ]] && return 1
|
|
1951
|
+
while IFS= read -r pattern; do
|
|
1952
|
+
[[ -z "$pattern" ]] && continue
|
|
1953
|
+
local regex
|
|
1954
|
+
regex=$(echo "$pattern" | sed 's/\./\\./g; s/\*\*/DOUBLESTAR/g; s/\*/[^\/]*/g; s/DOUBLESTAR/.*/g')
|
|
1955
|
+
while IFS= read -r file; do
|
|
1956
|
+
[[ -z "$file" ]] && continue
|
|
1957
|
+
if echo "$file" | grep -qE "^${regex}$"; then
|
|
1958
|
+
return 0
|
|
1959
|
+
fi
|
|
1960
|
+
done <<< "$changed_files"
|
|
1961
|
+
done <<< "$patterns"
|
|
1962
|
+
return 1
|
|
1963
|
+
}
|
|
1964
|
+
check_tier_match "critical" && risk_tier="critical"
|
|
1965
|
+
check_tier_match "high" && [[ "$risk_tier" != "critical" ]] && risk_tier="high"
|
|
1966
|
+
check_tier_match "medium" && [[ "$risk_tier" != "critical" && "$risk_tier" != "high" ]] && risk_tier="medium"
|
|
1967
|
+
fi
|
|
1968
|
+
fi
|
|
1969
|
+
|
|
1970
|
+
local required_evidence
|
|
1971
|
+
required_evidence=$(jq -r ".mergePolicy.\"$risk_tier\".requiredEvidence // [] | .[]" "$REPO_DIR/config/policy.json" 2>/dev/null)
|
|
1972
|
+
|
|
1973
|
+
if [[ -n "$required_evidence" ]]; then
|
|
1974
|
+
local evidence_dir="$REPO_DIR/.claude/evidence"
|
|
1975
|
+
local missing_evidence=()
|
|
1976
|
+
while IFS= read -r etype; do
|
|
1977
|
+
[[ -z "$etype" ]] && continue
|
|
1978
|
+
local has_evidence=false
|
|
1979
|
+
for f in "$evidence_dir"/*"$etype"*; do
|
|
1980
|
+
[[ -f "$f" ]] && has_evidence=true && break
|
|
1981
|
+
done
|
|
1982
|
+
[[ "$has_evidence" != "true" ]] && missing_evidence+=("$etype")
|
|
1983
|
+
done <<< "$required_evidence"
|
|
1984
|
+
|
|
1985
|
+
if [[ ${#missing_evidence[@]} -gt 0 ]]; then
|
|
1986
|
+
warn "Missing required evidence for $risk_tier tier: ${missing_evidence[*]}"
|
|
1987
|
+
emit_event "evidence.missing" "{\"tier\":\"$risk_tier\",\"missing\":\"${missing_evidence[*]}\"}"
|
|
1988
|
+
# Collect missing evidence
|
|
1989
|
+
if [[ -x "$SCRIPT_DIR/sw-evidence.sh" ]]; then
|
|
1990
|
+
for etype in "${missing_evidence[@]}"; do
|
|
1991
|
+
(cd "$REPO_DIR" && bash "$SCRIPT_DIR/sw-evidence.sh" capture "$etype" 2>/dev/null) || warn "Failed to collect $etype evidence"
|
|
1992
|
+
done
|
|
1993
|
+
fi
|
|
1994
|
+
fi
|
|
1995
|
+
fi
|
|
1996
|
+
|
|
1617
1997
|
# Build gh pr create args
|
|
1618
1998
|
local pr_args=(--title "$pr_title" --body "$pr_body" --base "$BASE_BRANCH")
|
|
1619
1999
|
|
|
@@ -1687,12 +2067,12 @@ EOF
|
|
|
1687
2067
|
local reviewer_assigned=false
|
|
1688
2068
|
|
|
1689
2069
|
# Try CODEOWNERS-based routing via GraphQL API
|
|
1690
|
-
if type gh_codeowners
|
|
2070
|
+
if type gh_codeowners >/dev/null 2>&1 && [[ -n "$REPO_OWNER" && -n "$REPO_NAME" ]]; then
|
|
1691
2071
|
local codeowners_json
|
|
1692
2072
|
codeowners_json=$(gh_codeowners "$REPO_OWNER" "$REPO_NAME" 2>/dev/null || echo "[]")
|
|
1693
2073
|
if [[ "$codeowners_json" != "[]" && -n "$codeowners_json" ]]; then
|
|
1694
2074
|
local changed_files
|
|
1695
|
-
changed_files=$(
|
|
2075
|
+
changed_files=$(_safe_base_diff --name-only || true)
|
|
1696
2076
|
if [[ -n "$changed_files" ]]; then
|
|
1697
2077
|
local co_reviewers
|
|
1698
2078
|
co_reviewers=$(echo "$codeowners_json" | jq -r '.[].owners[]' 2>/dev/null | sort -u | head -3 || true)
|
|
@@ -1710,7 +2090,7 @@ EOF
|
|
|
1710
2090
|
fi
|
|
1711
2091
|
|
|
1712
2092
|
# Fallback: contributor-based routing via GraphQL API
|
|
1713
|
-
if [[ "$reviewer_assigned" != "true" ]] && type gh_contributors
|
|
2093
|
+
if [[ "$reviewer_assigned" != "true" ]] && type gh_contributors >/dev/null 2>&1 && [[ -n "$REPO_OWNER" && -n "$REPO_NAME" ]]; then
|
|
1714
2094
|
local contributors_json
|
|
1715
2095
|
contributors_json=$(gh_contributors "$REPO_OWNER" "$REPO_NAME" 2>/dev/null || echo "[]")
|
|
1716
2096
|
local top_contributor
|
|
@@ -1766,13 +2146,14 @@ stage_merge() {
|
|
|
1766
2146
|
local merge_diff_file="${ARTIFACTS_DIR}/review-diff.patch"
|
|
1767
2147
|
local merge_review_file="${ARTIFACTS_DIR}/review.md"
|
|
1768
2148
|
if [[ ! -s "$merge_diff_file" ]]; then
|
|
1769
|
-
|
|
1770
|
-
git diff HEAD~5 > "$merge_diff_file" 2>/dev/null || true
|
|
2149
|
+
_safe_base_diff > "$merge_diff_file" 2>/dev/null || true
|
|
1771
2150
|
fi
|
|
1772
2151
|
if [[ -s "$merge_diff_file" ]]; then
|
|
1773
2152
|
local _merge_critical _merge_sec _merge_blocking _merge_reject
|
|
1774
|
-
_merge_critical=$(grep -ciE '\*\*\[?Critical\]?\*\*' "$merge_review_file" 2>/dev/null ||
|
|
1775
|
-
|
|
2153
|
+
_merge_critical=$(grep -ciE '\*\*\[?Critical\]?\*\*' "$merge_review_file" 2>/dev/null || true)
|
|
2154
|
+
_merge_critical="${_merge_critical:-0}"
|
|
2155
|
+
_merge_sec=$(grep -ciE '\*\*\[?Security\]?\*\*' "$merge_review_file" 2>/dev/null || true)
|
|
2156
|
+
_merge_sec="${_merge_sec:-0}"
|
|
1776
2157
|
_merge_blocking=$((${_merge_critical:-0} + ${_merge_sec:-0}))
|
|
1777
2158
|
[[ "$_merge_blocking" -gt 0 ]] && _merge_reject="Review found ${_merge_blocking} critical/security issue(s)"
|
|
1778
2159
|
if ! bash "$SCRIPT_DIR/sw-oversight.sh" gate --diff "$merge_diff_file" --description "${GOAL:-Pipeline merge}" --reject-if "${_merge_reject:-}" >/dev/null 2>&1; then
|
|
@@ -1816,7 +2197,7 @@ stage_merge() {
|
|
|
1816
2197
|
fi
|
|
1817
2198
|
|
|
1818
2199
|
# ── Branch Protection Check ──
|
|
1819
|
-
if type gh_branch_protection
|
|
2200
|
+
if type gh_branch_protection >/dev/null 2>&1 && [[ -n "$REPO_OWNER" && -n "$REPO_NAME" ]]; then
|
|
1820
2201
|
local protection_json
|
|
1821
2202
|
protection_json=$(gh_branch_protection "$REPO_OWNER" "$REPO_NAME" "${BASE_BRANCH:-main}" 2>/dev/null || echo '{"protected": false}')
|
|
1822
2203
|
local is_protected
|
|
@@ -1912,13 +2293,13 @@ stage_merge() {
|
|
|
1912
2293
|
check_status=$(gh pr checks "$pr_number" --json 'bucket,name' --jq '[.[] | .bucket] | unique | sort' 2>/dev/null || echo '["pending"]')
|
|
1913
2294
|
|
|
1914
2295
|
# If all checks passed (only "pass" in buckets)
|
|
1915
|
-
if echo "$check_status" | jq -e '. == ["pass"]'
|
|
2296
|
+
if echo "$check_status" | jq -e '. == ["pass"]' >/dev/null 2>&1; then
|
|
1916
2297
|
success "All CI checks passed"
|
|
1917
2298
|
break
|
|
1918
2299
|
fi
|
|
1919
2300
|
|
|
1920
2301
|
# If any check failed
|
|
1921
|
-
if echo "$check_status" | jq -e 'any(. == "fail")'
|
|
2302
|
+
if echo "$check_status" | jq -e 'any(. == "fail")' >/dev/null 2>&1; then
|
|
1922
2303
|
error "CI checks failed — aborting merge"
|
|
1923
2304
|
return 1
|
|
1924
2305
|
fi
|
|
@@ -2018,7 +2399,7 @@ stage_deploy() {
|
|
|
2018
2399
|
# Create GitHub deployment tracking
|
|
2019
2400
|
local gh_deploy_env="production"
|
|
2020
2401
|
[[ -n "$staging_cmd" && -z "$prod_cmd" ]] && gh_deploy_env="staging"
|
|
2021
|
-
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_deploy_pipeline_start
|
|
2402
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_deploy_pipeline_start >/dev/null 2>&1; then
|
|
2022
2403
|
if [[ -n "$REPO_OWNER" && -n "$REPO_NAME" ]]; then
|
|
2023
2404
|
gh_deploy_pipeline_start "$REPO_OWNER" "$REPO_NAME" "${GIT_BRANCH:-HEAD}" "$gh_deploy_env" 2>/dev/null || true
|
|
2024
2405
|
info "GitHub Deployment: tracking as $gh_deploy_env"
|
|
@@ -2151,7 +2532,7 @@ stage_deploy() {
|
|
|
2151
2532
|
error "Staging deploy failed"
|
|
2152
2533
|
[[ -n "$ISSUE_NUMBER" ]] && gh_comment_issue "$ISSUE_NUMBER" "Staging deploy failed"
|
|
2153
2534
|
# Mark GitHub deployment as failed
|
|
2154
|
-
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_deploy_pipeline_complete
|
|
2535
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_deploy_pipeline_complete >/dev/null 2>&1; then
|
|
2155
2536
|
gh_deploy_pipeline_complete "$REPO_OWNER" "$REPO_NAME" "$gh_deploy_env" false "Staging deploy failed" 2>/dev/null || true
|
|
2156
2537
|
fi
|
|
2157
2538
|
return 1
|
|
@@ -2169,7 +2550,7 @@ stage_deploy() {
|
|
|
2169
2550
|
fi
|
|
2170
2551
|
[[ -n "$ISSUE_NUMBER" ]] && gh_comment_issue "$ISSUE_NUMBER" "Production deploy failed — rollback ${rollback_cmd:+attempted}"
|
|
2171
2552
|
# Mark GitHub deployment as failed
|
|
2172
|
-
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_deploy_pipeline_complete
|
|
2553
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_deploy_pipeline_complete >/dev/null 2>&1; then
|
|
2173
2554
|
gh_deploy_pipeline_complete "$REPO_OWNER" "$REPO_NAME" "$gh_deploy_env" false "Production deploy failed" 2>/dev/null || true
|
|
2174
2555
|
fi
|
|
2175
2556
|
return 1
|
|
@@ -2184,7 +2565,7 @@ stage_deploy() {
|
|
|
2184
2565
|
fi
|
|
2185
2566
|
|
|
2186
2567
|
# Mark GitHub deployment as successful
|
|
2187
|
-
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_deploy_pipeline_complete
|
|
2568
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_deploy_pipeline_complete >/dev/null 2>&1; then
|
|
2188
2569
|
if [[ -n "$REPO_OWNER" && -n "$REPO_NAME" ]]; then
|
|
2189
2570
|
gh_deploy_pipeline_complete "$REPO_OWNER" "$REPO_NAME" "$gh_deploy_env" true "" 2>/dev/null || true
|
|
2190
2571
|
fi
|