shipwright-cli 3.1.0 → 3.3.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/.claude/agents/code-reviewer.md +2 -0
- package/.claude/agents/devops-engineer.md +2 -0
- package/.claude/agents/doc-fleet-agent.md +2 -0
- package/.claude/agents/pipeline-agent.md +2 -0
- package/.claude/agents/shell-script-specialist.md +2 -0
- package/.claude/agents/test-specialist.md +2 -0
- package/.claude/hooks/agent-crash-capture.sh +32 -0
- package/.claude/hooks/post-tool-use.sh +3 -2
- package/.claude/hooks/pre-tool-use.sh +35 -3
- package/README.md +22 -8
- package/claude-code/hooks/config-change.sh +18 -0
- package/claude-code/hooks/instructions-reloaded.sh +7 -0
- package/claude-code/hooks/worktree-create.sh +25 -0
- package/claude-code/hooks/worktree-remove.sh +20 -0
- package/config/code-constitution.json +130 -0
- package/config/defaults.json +25 -2
- package/config/policy.json +1 -1
- package/dashboard/middleware/auth.ts +134 -0
- package/dashboard/middleware/constants.ts +21 -0
- package/dashboard/public/index.html +8 -6
- package/dashboard/public/styles.css +176 -97
- package/dashboard/routes/auth.ts +38 -0
- package/dashboard/server.ts +117 -25
- package/dashboard/services/config.ts +26 -0
- package/dashboard/services/db.ts +118 -0
- package/dashboard/src/canvas/pixel-agent.ts +298 -0
- package/dashboard/src/canvas/pixel-sprites.ts +440 -0
- package/dashboard/src/canvas/shipyard-effects.ts +367 -0
- package/dashboard/src/canvas/shipyard-scene.ts +616 -0
- package/dashboard/src/canvas/submarine-layout.ts +267 -0
- package/dashboard/src/components/header.ts +8 -7
- package/dashboard/src/core/api.ts +5 -0
- package/dashboard/src/core/router.ts +1 -0
- package/dashboard/src/design/submarine-theme.ts +253 -0
- package/dashboard/src/main.ts +2 -0
- package/dashboard/src/types/api.ts +12 -1
- package/dashboard/src/views/activity.ts +2 -1
- package/dashboard/src/views/metrics.ts +69 -1
- package/dashboard/src/views/shipyard.ts +39 -0
- package/dashboard/types/index.ts +166 -0
- package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
- package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
- package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
- package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
- package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
- package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
- package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
- package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
- package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
- package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
- package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
- package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
- package/docs/research/RESEARCH_INDEX.md +439 -0
- package/docs/research/RESEARCH_SOURCES.md +440 -0
- package/docs/research/RESEARCH_SUMMARY.txt +275 -0
- package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
- package/package.json +2 -2
- package/scripts/lib/adaptive-model.sh +427 -0
- package/scripts/lib/adaptive-timeout.sh +316 -0
- package/scripts/lib/audit-trail.sh +309 -0
- package/scripts/lib/auto-recovery.sh +471 -0
- package/scripts/lib/bandit-selector.sh +431 -0
- package/scripts/lib/bootstrap.sh +104 -2
- package/scripts/lib/causal-graph.sh +455 -0
- package/scripts/lib/compat.sh +126 -0
- package/scripts/lib/compound-audit.sh +337 -0
- package/scripts/lib/constitutional.sh +454 -0
- package/scripts/lib/context-budget.sh +359 -0
- package/scripts/lib/convergence.sh +594 -0
- package/scripts/lib/cost-optimizer.sh +634 -0
- package/scripts/lib/daemon-adaptive.sh +14 -2
- package/scripts/lib/daemon-dispatch.sh +106 -17
- package/scripts/lib/daemon-failure.sh +34 -4
- package/scripts/lib/daemon-patrol.sh +25 -4
- package/scripts/lib/daemon-poll-github.sh +361 -0
- package/scripts/lib/daemon-poll-health.sh +299 -0
- package/scripts/lib/daemon-poll.sh +27 -611
- package/scripts/lib/daemon-state.sh +119 -66
- package/scripts/lib/daemon-triage.sh +10 -0
- package/scripts/lib/dod-scorecard.sh +442 -0
- package/scripts/lib/error-actionability.sh +300 -0
- package/scripts/lib/formal-spec.sh +461 -0
- package/scripts/lib/helpers.sh +180 -5
- package/scripts/lib/intent-analysis.sh +409 -0
- package/scripts/lib/loop-convergence.sh +350 -0
- package/scripts/lib/loop-iteration.sh +682 -0
- package/scripts/lib/loop-progress.sh +48 -0
- package/scripts/lib/loop-restart.sh +185 -0
- package/scripts/lib/memory-effectiveness.sh +506 -0
- package/scripts/lib/mutation-executor.sh +352 -0
- package/scripts/lib/outcome-feedback.sh +521 -0
- package/scripts/lib/pipeline-cli.sh +336 -0
- package/scripts/lib/pipeline-commands.sh +1216 -0
- package/scripts/lib/pipeline-detection.sh +101 -3
- package/scripts/lib/pipeline-execution.sh +897 -0
- package/scripts/lib/pipeline-github.sh +28 -3
- package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
- package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
- package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
- package/scripts/lib/pipeline-intelligence.sh +104 -1138
- package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
- package/scripts/lib/pipeline-quality-checks.sh +17 -711
- package/scripts/lib/pipeline-quality-gates.sh +563 -0
- package/scripts/lib/pipeline-stages-build.sh +730 -0
- package/scripts/lib/pipeline-stages-delivery.sh +965 -0
- package/scripts/lib/pipeline-stages-intake.sh +1133 -0
- package/scripts/lib/pipeline-stages-monitor.sh +407 -0
- package/scripts/lib/pipeline-stages-review.sh +1022 -0
- package/scripts/lib/pipeline-stages.sh +161 -2901
- package/scripts/lib/pipeline-state.sh +36 -5
- package/scripts/lib/pipeline-util.sh +487 -0
- package/scripts/lib/policy-learner.sh +438 -0
- package/scripts/lib/process-reward.sh +493 -0
- package/scripts/lib/project-detect.sh +649 -0
- package/scripts/lib/quality-profile.sh +334 -0
- package/scripts/lib/recruit-commands.sh +885 -0
- package/scripts/lib/recruit-learning.sh +739 -0
- package/scripts/lib/recruit-roles.sh +648 -0
- package/scripts/lib/reward-aggregator.sh +458 -0
- package/scripts/lib/rl-optimizer.sh +362 -0
- package/scripts/lib/root-cause.sh +427 -0
- package/scripts/lib/scope-enforcement.sh +445 -0
- package/scripts/lib/session-restart.sh +493 -0
- package/scripts/lib/skill-memory.sh +300 -0
- package/scripts/lib/skill-registry.sh +775 -0
- package/scripts/lib/spec-driven.sh +476 -0
- package/scripts/lib/test-helpers.sh +18 -7
- package/scripts/lib/test-holdout.sh +429 -0
- package/scripts/lib/test-optimizer.sh +511 -0
- package/scripts/shipwright-file-suggest.sh +45 -0
- package/scripts/skills/adversarial-quality.md +61 -0
- package/scripts/skills/api-design.md +44 -0
- package/scripts/skills/architecture-design.md +50 -0
- package/scripts/skills/brainstorming.md +43 -0
- package/scripts/skills/data-pipeline.md +44 -0
- package/scripts/skills/deploy-safety.md +64 -0
- package/scripts/skills/documentation.md +38 -0
- package/scripts/skills/frontend-design.md +45 -0
- package/scripts/skills/generated/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
- package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
- package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
- package/scripts/skills/generated/cli-version-management.md +29 -0
- package/scripts/skills/generated/collection-system-validation.md +99 -0
- package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
- package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
- package/scripts/skills/generated/test-parallelization-detection.md +65 -0
- package/scripts/skills/observability.md +79 -0
- package/scripts/skills/performance.md +48 -0
- package/scripts/skills/pr-quality.md +49 -0
- package/scripts/skills/product-thinking.md +43 -0
- package/scripts/skills/security-audit.md +49 -0
- package/scripts/skills/systematic-debugging.md +40 -0
- package/scripts/skills/testing-strategy.md +47 -0
- package/scripts/skills/two-stage-review.md +52 -0
- package/scripts/skills/validation-thoroughness.md +55 -0
- package/scripts/sw +9 -3
- package/scripts/sw-activity.sh +9 -8
- package/scripts/sw-adaptive.sh +8 -7
- package/scripts/sw-adversarial.sh +2 -1
- package/scripts/sw-architecture-enforcer.sh +3 -1
- package/scripts/sw-auth.sh +12 -2
- package/scripts/sw-autonomous.sh +5 -1
- package/scripts/sw-changelog.sh +4 -1
- package/scripts/sw-checkpoint.sh +2 -1
- package/scripts/sw-ci.sh +15 -6
- package/scripts/sw-cleanup.sh +4 -26
- package/scripts/sw-code-review.sh +45 -20
- package/scripts/sw-connect.sh +2 -1
- package/scripts/sw-context.sh +2 -1
- package/scripts/sw-cost.sh +107 -5
- package/scripts/sw-daemon.sh +71 -11
- package/scripts/sw-dashboard.sh +3 -1
- package/scripts/sw-db.sh +71 -20
- package/scripts/sw-decide.sh +8 -2
- package/scripts/sw-decompose.sh +360 -17
- package/scripts/sw-deps.sh +4 -1
- package/scripts/sw-developer-simulation.sh +4 -1
- package/scripts/sw-discovery.sh +378 -5
- package/scripts/sw-doc-fleet.sh +4 -1
- package/scripts/sw-docs-agent.sh +3 -1
- package/scripts/sw-docs.sh +2 -1
- package/scripts/sw-doctor.sh +453 -2
- package/scripts/sw-dora.sh +4 -1
- package/scripts/sw-durable.sh +12 -7
- package/scripts/sw-e2e-orchestrator.sh +17 -16
- package/scripts/sw-eventbus.sh +13 -4
- package/scripts/sw-evidence.sh +364 -12
- package/scripts/sw-feedback.sh +550 -9
- package/scripts/sw-fix.sh +20 -1
- package/scripts/sw-fleet-discover.sh +6 -2
- package/scripts/sw-fleet-viz.sh +9 -4
- package/scripts/sw-fleet.sh +5 -1
- package/scripts/sw-github-app.sh +18 -4
- package/scripts/sw-github-checks.sh +3 -2
- package/scripts/sw-github-deploy.sh +3 -2
- package/scripts/sw-github-graphql.sh +18 -7
- package/scripts/sw-guild.sh +5 -1
- package/scripts/sw-heartbeat.sh +5 -30
- package/scripts/sw-hello.sh +67 -0
- package/scripts/sw-hygiene.sh +10 -3
- package/scripts/sw-incident.sh +273 -5
- package/scripts/sw-init.sh +18 -2
- package/scripts/sw-instrument.sh +10 -2
- package/scripts/sw-intelligence.sh +44 -7
- package/scripts/sw-jira.sh +5 -1
- package/scripts/sw-launchd.sh +2 -1
- package/scripts/sw-linear.sh +4 -1
- package/scripts/sw-logs.sh +4 -1
- package/scripts/sw-loop.sh +436 -1076
- package/scripts/sw-memory.sh +357 -3
- package/scripts/sw-mission-control.sh +6 -1
- package/scripts/sw-model-router.sh +483 -27
- package/scripts/sw-otel.sh +15 -4
- package/scripts/sw-oversight.sh +14 -5
- package/scripts/sw-patrol-meta.sh +334 -0
- package/scripts/sw-pipeline-composer.sh +7 -1
- package/scripts/sw-pipeline-vitals.sh +12 -6
- package/scripts/sw-pipeline.sh +54 -2653
- package/scripts/sw-pm.sh +16 -8
- package/scripts/sw-pr-lifecycle.sh +2 -1
- package/scripts/sw-predictive.sh +17 -5
- package/scripts/sw-prep.sh +185 -2
- package/scripts/sw-ps.sh +5 -25
- package/scripts/sw-public-dashboard.sh +17 -4
- package/scripts/sw-quality.sh +14 -6
- package/scripts/sw-reaper.sh +8 -25
- package/scripts/sw-recruit.sh +156 -2303
- package/scripts/sw-regression.sh +19 -12
- package/scripts/sw-release-manager.sh +3 -1
- package/scripts/sw-release.sh +4 -1
- package/scripts/sw-remote.sh +3 -1
- package/scripts/sw-replay.sh +7 -1
- package/scripts/sw-retro.sh +158 -1
- package/scripts/sw-review-rerun.sh +3 -1
- package/scripts/sw-scale.sh +14 -5
- package/scripts/sw-security-audit.sh +6 -1
- package/scripts/sw-self-optimize.sh +173 -6
- package/scripts/sw-session.sh +9 -3
- package/scripts/sw-setup.sh +3 -1
- package/scripts/sw-stall-detector.sh +406 -0
- package/scripts/sw-standup.sh +15 -7
- package/scripts/sw-status.sh +3 -1
- package/scripts/sw-strategic.sh +14 -6
- package/scripts/sw-stream.sh +13 -4
- package/scripts/sw-swarm.sh +20 -7
- package/scripts/sw-team-stages.sh +13 -6
- package/scripts/sw-templates.sh +7 -31
- package/scripts/sw-testgen.sh +17 -6
- package/scripts/sw-tmux-pipeline.sh +4 -1
- package/scripts/sw-tmux-role-color.sh +2 -0
- package/scripts/sw-tmux-status.sh +1 -1
- package/scripts/sw-tmux.sh +37 -1
- package/scripts/sw-trace.sh +3 -1
- package/scripts/sw-tracker-github.sh +3 -0
- package/scripts/sw-tracker-jira.sh +3 -0
- package/scripts/sw-tracker-linear.sh +3 -0
- package/scripts/sw-tracker.sh +3 -1
- package/scripts/sw-triage.sh +3 -2
- package/scripts/sw-upgrade.sh +3 -1
- package/scripts/sw-ux.sh +5 -2
- package/scripts/sw-webhook.sh +5 -2
- package/scripts/sw-widgets.sh +9 -4
- package/scripts/sw-worktree.sh +15 -3
- package/scripts/test-skill-injection.sh +1233 -0
- package/templates/pipelines/autonomous.json +27 -3
- package/templates/pipelines/cost-aware.json +34 -8
- package/templates/pipelines/deployed.json +12 -0
- package/templates/pipelines/enterprise.json +12 -0
- package/templates/pipelines/fast.json +6 -0
- package/templates/pipelines/full.json +27 -3
- package/templates/pipelines/hotfix.json +6 -0
- package/templates/pipelines/standard.json +12 -0
- package/templates/pipelines/tdd.json +12 -0
|
@@ -0,0 +1,1022 @@
|
|
|
1
|
+
# pipeline-stages-review.sh — review, compound_quality, audit stages
|
|
2
|
+
# Source from pipeline-stages.sh. Requires all pipeline globals and dependencies.
|
|
3
|
+
[[ -n "${_PIPELINE_STAGES_REVIEW_LOADED:-}" ]] && return 0
|
|
4
|
+
_PIPELINE_STAGES_REVIEW_LOADED=1
|
|
5
|
+
|
|
6
|
+
stage_review() {
|
|
7
|
+
CURRENT_STAGE_ID="review"
|
|
8
|
+
# Consume retry context if this is a retry attempt
|
|
9
|
+
local _retry_ctx="${ARTIFACTS_DIR}/.retry-context-review.md"
|
|
10
|
+
if [[ -s "$_retry_ctx" ]]; then
|
|
11
|
+
local _review_retry_hints
|
|
12
|
+
_review_retry_hints=$(cat "$_retry_ctx" 2>/dev/null || true)
|
|
13
|
+
rm -f "$_retry_ctx"
|
|
14
|
+
fi
|
|
15
|
+
local diff_file="$ARTIFACTS_DIR/review-diff.patch"
|
|
16
|
+
local review_file="$ARTIFACTS_DIR/review.md"
|
|
17
|
+
|
|
18
|
+
_safe_base_diff > "$diff_file" 2>/dev/null || true
|
|
19
|
+
|
|
20
|
+
if [[ ! -s "$diff_file" ]]; then
|
|
21
|
+
warn "No diff found — skipping review"
|
|
22
|
+
return 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
26
|
+
warn "Claude CLI not found — skipping AI review"
|
|
27
|
+
return 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
local diff_stats
|
|
31
|
+
diff_stats=$(_safe_base_diff --stat | tail -1 || echo "")
|
|
32
|
+
info "Running AI code review... ${DIM}($diff_stats)${RESET}"
|
|
33
|
+
|
|
34
|
+
# Semantic risk scoring when intelligence is enabled
|
|
35
|
+
if type intelligence_search_memory >/dev/null 2>&1 && command -v claude >/dev/null 2>&1; then
|
|
36
|
+
local diff_files
|
|
37
|
+
diff_files=$(_safe_base_diff --name-only || true)
|
|
38
|
+
local risk_score="low"
|
|
39
|
+
# Fast heuristic: flag high-risk file patterns
|
|
40
|
+
if echo "$diff_files" | grep -qiE 'migration|schema|auth|crypto|security|password|token|secret|\.env'; then
|
|
41
|
+
risk_score="high"
|
|
42
|
+
elif echo "$diff_files" | grep -qiE 'api|route|controller|middleware|hook'; then
|
|
43
|
+
risk_score="medium"
|
|
44
|
+
fi
|
|
45
|
+
emit_event "review.risk_assessed" \
|
|
46
|
+
"issue=${ISSUE_NUMBER:-0}" \
|
|
47
|
+
"risk=$risk_score" \
|
|
48
|
+
"files_changed=$(echo "$diff_files" | wc -l | xargs)"
|
|
49
|
+
if [[ "$risk_score" == "high" ]]; then
|
|
50
|
+
warn "High-risk changes detected (DB schema, auth, crypto, or secrets)"
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
local review_model="${MODEL:-opus}"
|
|
55
|
+
# Intelligence model routing (when no explicit CLI --model override)
|
|
56
|
+
if [[ -z "$MODEL" && -n "${CLAUDE_MODEL:-}" ]]; then
|
|
57
|
+
review_model="$CLAUDE_MODEL"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Build adversarial review prompt with project context
|
|
61
|
+
local review_prompt="You are a SKEPTICAL senior engineer reviewing code for production.
|
|
62
|
+
Your job is to FIND PROBLEMS, not confirm quality.
|
|
63
|
+
|
|
64
|
+
For each issue found, use this format:
|
|
65
|
+
- **[SEVERITY]** file:line — description
|
|
66
|
+
|
|
67
|
+
Severity levels: Critical, Bug, Security, Warning, Suggestion
|
|
68
|
+
|
|
69
|
+
Mandatory requirements:
|
|
70
|
+
1. Find at least 3 issues (or explain why code is exceptional)
|
|
71
|
+
2. Check EVERY acceptance criterion — mark PASS/FAIL with evidence
|
|
72
|
+
3. Flag every scope creep (unplanned files)
|
|
73
|
+
4. Verify all never-ship rules are not violated
|
|
74
|
+
5. Assess all always-require rules are met
|
|
75
|
+
|
|
76
|
+
Focus areas:
|
|
77
|
+
1. Logic bugs and edge cases
|
|
78
|
+
2. Security vulnerabilities (injection, XSS, auth bypass, etc.)
|
|
79
|
+
3. Error handling gaps
|
|
80
|
+
4. Performance issues
|
|
81
|
+
5. Missing validation
|
|
82
|
+
6. Project convention violations (see standards below)
|
|
83
|
+
7. Architectural constraint violations
|
|
84
|
+
8. Data validation and sanitization gaps
|
|
85
|
+
|
|
86
|
+
Be thorough and adversarial. Only accept exceptional code without issues.
|
|
87
|
+
"
|
|
88
|
+
|
|
89
|
+
# Dark factory: inject spec compliance check into review
|
|
90
|
+
local spec_file="${ARTIFACTS_DIR}/spec.json"
|
|
91
|
+
if [[ -f "$spec_file" ]] && type spec_diff >/dev/null 2>&1; then
|
|
92
|
+
SPEC_DIR="${ARTIFACTS_DIR}/specs"
|
|
93
|
+
local compliance_report
|
|
94
|
+
compliance_report=$(spec_diff "$spec_file" "${PROJECT_ROOT:-.}" 2>/dev/null || true)
|
|
95
|
+
if [[ -n "$compliance_report" && -f "$compliance_report" ]]; then
|
|
96
|
+
local verdict
|
|
97
|
+
verdict=$(jq -r '.verdict // "unknown"' "$compliance_report" 2>/dev/null || echo "unknown")
|
|
98
|
+
if [[ "$verdict" == "review_needed" ]]; then
|
|
99
|
+
local unmod_files
|
|
100
|
+
unmod_files=$(jq -r '.file_coverage.unmodified_files[]?' "$compliance_report" 2>/dev/null | head -5 || true)
|
|
101
|
+
review_prompt+="
|
|
102
|
+
## Spec Compliance Warning
|
|
103
|
+
The specification expected changes to files that were NOT modified:
|
|
104
|
+
${unmod_files}
|
|
105
|
+
Check if these files should have been changed to meet the spec goals.
|
|
106
|
+
"
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# Inject quality profile standards (never-ship, always-require, focus areas)
|
|
112
|
+
local quality_profile="${PROJECT_ROOT}/.claude/quality-profile.json"
|
|
113
|
+
if [[ -f "$quality_profile" ]]; then
|
|
114
|
+
local never_ship_rules always_require_rules focus_areas learned_rules
|
|
115
|
+
|
|
116
|
+
never_ship_rules=$(jq -r '.quality.never_ship[]? // empty' "$quality_profile" 2>/dev/null | sed 's/^/ - /')
|
|
117
|
+
if [[ -n "$never_ship_rules" ]]; then
|
|
118
|
+
review_prompt+="
|
|
119
|
+
## Project Standards — NEVER SHIP Rules
|
|
120
|
+
These are absolute violations that must be caught:
|
|
121
|
+
${never_ship_rules}
|
|
122
|
+
"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
always_require_rules=$(jq -r '.quality.always_require[]? // empty' "$quality_profile" 2>/dev/null | sed 's/^/ - /')
|
|
126
|
+
if [[ -n "$always_require_rules" ]]; then
|
|
127
|
+
review_prompt+="
|
|
128
|
+
## Project Standards — ALWAYS REQUIRE
|
|
129
|
+
These must be present in this PR:
|
|
130
|
+
${always_require_rules}
|
|
131
|
+
"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
focus_areas=$(jq -r '.review.focus_areas[]? // empty' "$quality_profile" 2>/dev/null | sed 's/^/ - /')
|
|
135
|
+
if [[ -n "$focus_areas" ]]; then
|
|
136
|
+
review_prompt+="
|
|
137
|
+
## Review Focus Areas
|
|
138
|
+
Pay extra attention to these areas for this project:
|
|
139
|
+
${focus_areas}
|
|
140
|
+
"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
learned_rules=$(jq -r '.quality.learned_rules[]? | "\(.rule) (source: \(.source), confidence: \(.confidence))" | @base64' "$quality_profile" 2>/dev/null | while read -r encoded; do
|
|
144
|
+
[[ -z "$encoded" ]] && continue
|
|
145
|
+
echo "$encoded" | base64 -d 2>/dev/null || true
|
|
146
|
+
done | sed 's/^/ - /')
|
|
147
|
+
if [[ -n "$learned_rules" ]]; then
|
|
148
|
+
review_prompt+="
|
|
149
|
+
## Learned Rules from Previous Reviews
|
|
150
|
+
These patterns were discovered from past code review findings:
|
|
151
|
+
${learned_rules}
|
|
152
|
+
"
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# Inject acceptance criteria from intake stage
|
|
157
|
+
local acceptance_file="$ARTIFACTS_DIR/acceptance-criteria.json"
|
|
158
|
+
if [[ -f "$acceptance_file" ]]; then
|
|
159
|
+
local ac_list
|
|
160
|
+
ac_list=$(jq -r '.acceptance_criteria[]? // empty' "$acceptance_file" 2>/dev/null | sed 's/^/ - /')
|
|
161
|
+
if [[ -n "$ac_list" ]]; then
|
|
162
|
+
review_prompt+="
|
|
163
|
+
## Definition of Done (Acceptance Criteria)
|
|
164
|
+
Verify EVERY criterion below is met:
|
|
165
|
+
${ac_list}
|
|
166
|
+
"
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# Inject scope report (planned vs actual files)
|
|
171
|
+
local scope_file="$ARTIFACTS_DIR/scope-report.json"
|
|
172
|
+
if [[ -f "$scope_file" ]]; then
|
|
173
|
+
local planned_files unplanned_files
|
|
174
|
+
planned_files=$(jq -r '.planned_files[]? // empty' "$scope_file" 2>/dev/null | sed 's/^/ - /')
|
|
175
|
+
unplanned_files=$(jq -r '.unplanned_files[]? // empty' "$scope_file" 2>/dev/null | sed 's/^/ - UNPLANNED: /')
|
|
176
|
+
|
|
177
|
+
if [[ -n "$planned_files" ]]; then
|
|
178
|
+
review_prompt+="
|
|
179
|
+
## Scope Report
|
|
180
|
+
Planned files to modify:
|
|
181
|
+
${planned_files}
|
|
182
|
+
"
|
|
183
|
+
fi
|
|
184
|
+
if [[ -n "$unplanned_files" ]]; then
|
|
185
|
+
review_prompt+="
|
|
186
|
+
Unplanned files changed (scope creep?):
|
|
187
|
+
${unplanned_files}
|
|
188
|
+
"
|
|
189
|
+
fi
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
# Inject previous review findings and anti-patterns from memory
|
|
193
|
+
if type intelligence_search_memory >/dev/null 2>&1; then
|
|
194
|
+
local review_memory
|
|
195
|
+
review_memory=$(intelligence_search_memory "code review findings anti-patterns for: ${GOAL:-}" "${HOME}/.shipwright/memory" 5 2>/dev/null) || true
|
|
196
|
+
review_memory=$(prune_context_section "memory" "$review_memory" 10000)
|
|
197
|
+
if [[ -n "$review_memory" ]]; then
|
|
198
|
+
review_prompt+="
|
|
199
|
+
## Known Issues from Previous Reviews
|
|
200
|
+
These anti-patterns and issues have been found in past reviews of this codebase. Flag them if they recur:
|
|
201
|
+
${review_memory}
|
|
202
|
+
"
|
|
203
|
+
fi
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# Inject project conventions if CLAUDE.md exists
|
|
207
|
+
local claudemd="$PROJECT_ROOT/.claude/CLAUDE.md"
|
|
208
|
+
if [[ -f "$claudemd" ]]; then
|
|
209
|
+
local conventions
|
|
210
|
+
conventions=$(grep -A2 'Common Pitfalls\|Shell Standards\|Bash 3.2' "$claudemd" 2>/dev/null | head -20 || true)
|
|
211
|
+
if [[ -n "$conventions" ]]; then
|
|
212
|
+
review_prompt+="
|
|
213
|
+
## Project Conventions
|
|
214
|
+
${conventions}
|
|
215
|
+
"
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
# ── Constitutional AI: deterministic principle-based checks ──
|
|
220
|
+
if type constitutional_load >/dev/null 2>&1; then
|
|
221
|
+
if constitutional_load 2>/dev/null; then
|
|
222
|
+
local constitution_violations
|
|
223
|
+
constitution_violations=$(constitutional_self_critique "$BASE_BRANCH" 2>/dev/null) || true
|
|
224
|
+
constitution_violations="${constitution_violations:-0}"
|
|
225
|
+
if [[ "$constitution_violations" -gt 0 ]]; then
|
|
226
|
+
local constitution_prompt
|
|
227
|
+
constitution_prompt=$(constitutional_inject_prompt "" "high" 2>/dev/null || true)
|
|
228
|
+
if [[ -n "$constitution_prompt" ]]; then
|
|
229
|
+
review_prompt+="
|
|
230
|
+
${constitution_prompt}
|
|
231
|
+
"
|
|
232
|
+
fi
|
|
233
|
+
fi
|
|
234
|
+
fi
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
# Inject CODEOWNERS focus areas for review
|
|
238
|
+
if [[ "${NO_GITHUB:-}" != "true" ]] && type gh_codeowners >/dev/null 2>&1; then
|
|
239
|
+
local review_owners
|
|
240
|
+
review_owners=$(gh_codeowners 2>/dev/null | head -10 || true)
|
|
241
|
+
if [[ -n "$review_owners" ]]; then
|
|
242
|
+
review_prompt+="
|
|
243
|
+
## Code Owners (focus areas)
|
|
244
|
+
${review_owners}
|
|
245
|
+
"
|
|
246
|
+
fi
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
# Inject Definition of Done if present
|
|
250
|
+
local dod_file="$PROJECT_ROOT/.claude/DEFINITION-OF-DONE.md"
|
|
251
|
+
if [[ -f "$dod_file" ]]; then
|
|
252
|
+
review_prompt+="
|
|
253
|
+
## Definition of Done (verify these)
|
|
254
|
+
$(cat "$dod_file")
|
|
255
|
+
"
|
|
256
|
+
fi
|
|
257
|
+
|
|
258
|
+
# Inject skill prompts for review stage
|
|
259
|
+
# Prefer adaptive selection when available
|
|
260
|
+
if type skill_select_adaptive >/dev/null 2>&1; then
|
|
261
|
+
local _review_skill_files _review_skills
|
|
262
|
+
_review_skill_files=$(skill_select_adaptive "${INTELLIGENCE_ISSUE_TYPE:-backend}" "review" "${ISSUE_BODY:-}" "${INTELLIGENCE_COMPLEXITY:-5}" 2>/dev/null || true)
|
|
263
|
+
if [[ -n "$_review_skill_files" ]]; then
|
|
264
|
+
_review_skills=$(while IFS= read -r _path; do
|
|
265
|
+
[[ -z "$_path" ]] && continue
|
|
266
|
+
[[ -f "$_path" ]] && cat "$_path" 2>/dev/null
|
|
267
|
+
done <<< "$_review_skill_files")
|
|
268
|
+
if [[ -n "$_review_skills" ]]; then
|
|
269
|
+
_review_skills=$(prune_context_section "review-skills" "$_review_skills" 5000)
|
|
270
|
+
review_prompt+="
|
|
271
|
+
## Review Skill Guidance (${INTELLIGENCE_ISSUE_TYPE:-backend} issue)
|
|
272
|
+
${_review_skills}
|
|
273
|
+
"
|
|
274
|
+
fi
|
|
275
|
+
fi
|
|
276
|
+
elif type skill_load_prompts >/dev/null 2>&1; then
|
|
277
|
+
# Fallback to static selection
|
|
278
|
+
local _review_skills
|
|
279
|
+
_review_skills=$(skill_load_prompts "${INTELLIGENCE_ISSUE_TYPE:-backend}" "review" 2>/dev/null || true)
|
|
280
|
+
if [[ -n "$_review_skills" ]]; then
|
|
281
|
+
_review_skills=$(prune_context_section "review-skills" "$_review_skills" 5000)
|
|
282
|
+
review_prompt+="
|
|
283
|
+
## Review Skill Guidance (${INTELLIGENCE_ISSUE_TYPE:-backend} issue)
|
|
284
|
+
${_review_skills}
|
|
285
|
+
"
|
|
286
|
+
fi
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
review_prompt+="
|
|
290
|
+
## Diff to Review
|
|
291
|
+
$(cat "$diff_file")"
|
|
292
|
+
|
|
293
|
+
# Inject skill prompts for review stage
|
|
294
|
+
_skill_prompts=""
|
|
295
|
+
if type skill_load_from_plan >/dev/null 2>&1; then
|
|
296
|
+
_skill_prompts=$(skill_load_from_plan "review" 2>/dev/null || true)
|
|
297
|
+
elif type skill_select_adaptive >/dev/null 2>&1; then
|
|
298
|
+
local _skill_files
|
|
299
|
+
_skill_files=$(skill_select_adaptive "${INTELLIGENCE_ISSUE_TYPE:-backend}" "review" "${ISSUE_BODY:-}" "${INTELLIGENCE_COMPLEXITY:-5}" 2>/dev/null || true)
|
|
300
|
+
if [[ -n "$_skill_files" ]]; then
|
|
301
|
+
_skill_prompts=$(while IFS= read -r _path; do
|
|
302
|
+
[[ -z "$_path" || ! -f "$_path" ]] && continue
|
|
303
|
+
cat "$_path" 2>/dev/null
|
|
304
|
+
done <<< "$_skill_files")
|
|
305
|
+
fi
|
|
306
|
+
elif type skill_load_prompts >/dev/null 2>&1; then
|
|
307
|
+
_skill_prompts=$(skill_load_prompts "${INTELLIGENCE_ISSUE_TYPE:-backend}" "review" 2>/dev/null || true)
|
|
308
|
+
fi
|
|
309
|
+
if [[ -n "$_skill_prompts" ]]; then
|
|
310
|
+
_skill_prompts=$(prune_context_section "skills" "$_skill_prompts" 8000)
|
|
311
|
+
review_prompt="${review_prompt}
|
|
312
|
+
## Skill Guidance (${INTELLIGENCE_ISSUE_TYPE:-backend} issue, AI-selected)
|
|
313
|
+
${_skill_prompts}
|
|
314
|
+
"
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
# Guard total prompt size
|
|
318
|
+
review_prompt=$(guard_prompt_size "review" "$review_prompt")
|
|
319
|
+
|
|
320
|
+
# Skip permissions — pipeline runs headlessly (claude -p) and has no terminal
|
|
321
|
+
# for interactive permission prompts. Same rationale as build stage (line ~1083).
|
|
322
|
+
local review_args=(--print)
|
|
323
|
+
local _review_flags
|
|
324
|
+
_review_flags="$(_pipeline_claude_flags "review" "$review_model")"
|
|
325
|
+
# shellcheck disable=SC2206
|
|
326
|
+
review_args+=($_review_flags --max-turns "$(_smart_int "max_turns.pipeline_stage" 25)" --dangerously-skip-permissions)
|
|
327
|
+
|
|
328
|
+
# ── Two-Stage Review: Pass 1 (Spec Compliance) ──
|
|
329
|
+
local _two_stage=false
|
|
330
|
+
if type skill_has_two_stage_review >/dev/null 2>&1 && skill_has_two_stage_review "${INTELLIGENCE_ISSUE_TYPE:-backend}"; then
|
|
331
|
+
_two_stage=true
|
|
332
|
+
local spec_review_file="$ARTIFACTS_DIR/review-spec.md"
|
|
333
|
+
local plan_file="$ARTIFACTS_DIR/plan.md"
|
|
334
|
+
|
|
335
|
+
if [[ -s "$plan_file" ]]; then
|
|
336
|
+
info "Two-stage review: Pass 1 — Spec compliance"
|
|
337
|
+
local spec_prompt="You are a spec compliance reviewer. Compare the implementation against the plan.
|
|
338
|
+
|
|
339
|
+
## Plan
|
|
340
|
+
$(cat "$plan_file" 2>/dev/null | head -200)
|
|
341
|
+
|
|
342
|
+
## Implementation Diff
|
|
343
|
+
$(cat "$diff_file" 2>/dev/null)
|
|
344
|
+
|
|
345
|
+
## Task
|
|
346
|
+
Compare the diff against the plan:
|
|
347
|
+
1. Does the code implement every task from the plan's checklist?
|
|
348
|
+
2. Were all planned files actually modified?
|
|
349
|
+
3. Is anything from the plan NOT implemented?
|
|
350
|
+
4. Was anything added that WASN'T in the plan?
|
|
351
|
+
|
|
352
|
+
For each gap found:
|
|
353
|
+
- **[SPEC-GAP]** description — what was planned vs what was implemented
|
|
354
|
+
|
|
355
|
+
If all requirements are met, write: \"Spec compliance: PASS — all planned tasks implemented.\"
|
|
356
|
+
"
|
|
357
|
+
spec_prompt=$(guard_prompt_size "spec-review" "$spec_prompt")
|
|
358
|
+
claude "${review_args[@]}" "$spec_prompt" < /dev/null > "$spec_review_file" 2>"${ARTIFACTS_DIR}/.claude-tokens-spec-review.log" || true
|
|
359
|
+
parse_claude_tokens "${ARTIFACTS_DIR}/.claude-tokens-spec-review.log"
|
|
360
|
+
|
|
361
|
+
if [[ -s "$spec_review_file" ]]; then
|
|
362
|
+
local spec_gaps
|
|
363
|
+
spec_gaps=$(grep -c 'SPEC-GAP' "$spec_review_file" 2>/dev/null || true)
|
|
364
|
+
spec_gaps="${spec_gaps:-0}"
|
|
365
|
+
if [[ "$spec_gaps" -gt 0 ]]; then
|
|
366
|
+
warn "Spec review found $spec_gaps gap(s) — see $spec_review_file"
|
|
367
|
+
else
|
|
368
|
+
success "Spec compliance: PASS"
|
|
369
|
+
fi
|
|
370
|
+
emit_event "review.spec_complete" \
|
|
371
|
+
"issue=${ISSUE_NUMBER:-0}" \
|
|
372
|
+
"gaps=$spec_gaps"
|
|
373
|
+
fi
|
|
374
|
+
info "Two-stage review: Pass 2 — Code quality"
|
|
375
|
+
fi
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
claude "${review_args[@]}" "$review_prompt" < /dev/null > "$review_file" 2>"${ARTIFACTS_DIR}/.claude-tokens-review.log" || true
|
|
379
|
+
parse_claude_tokens "${ARTIFACTS_DIR}/.claude-tokens-review.log"
|
|
380
|
+
|
|
381
|
+
if [[ ! -s "$review_file" ]]; then
|
|
382
|
+
warn "Review produced no output — check ${ARTIFACTS_DIR}/.claude-tokens-review.log for errors"
|
|
383
|
+
return 0
|
|
384
|
+
fi
|
|
385
|
+
|
|
386
|
+
# Extract severity counts — try JSON structure first, then grep fallback
|
|
387
|
+
local critical_count=0 bug_count=0 warning_count=0
|
|
388
|
+
|
|
389
|
+
# Check if review output is structured JSON (e.g. from structured review tools)
|
|
390
|
+
local json_parsed=false
|
|
391
|
+
if head -1 "$review_file" 2>/dev/null | grep -q '^{' 2>/dev/null; then
|
|
392
|
+
local j_critical j_bug j_warning
|
|
393
|
+
j_critical=$(jq -r '.issues | map(select(.severity == "Critical")) | length' "$review_file" 2>/dev/null || echo "")
|
|
394
|
+
if [[ -n "$j_critical" && "$j_critical" != "null" ]]; then
|
|
395
|
+
critical_count="$j_critical"
|
|
396
|
+
bug_count=$(jq -r '.issues | map(select(.severity == "Bug" or .severity == "Security")) | length' "$review_file" 2>/dev/null || echo "0")
|
|
397
|
+
warning_count=$(jq -r '.issues | map(select(.severity == "Warning" or .severity == "Suggestion")) | length' "$review_file" 2>/dev/null || echo "0")
|
|
398
|
+
json_parsed=true
|
|
399
|
+
fi
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
# Grep fallback for markdown-formatted review output
|
|
403
|
+
if [[ "$json_parsed" != "true" ]]; then
|
|
404
|
+
critical_count=$(grep -ciE '\*\*\[?Critical\]?\*\*' "$review_file" 2>/dev/null || true)
|
|
405
|
+
critical_count="${critical_count:-0}"
|
|
406
|
+
bug_count=$(grep -ciE '\*\*\[?(Bug|Security)\]?\*\*' "$review_file" 2>/dev/null || true)
|
|
407
|
+
bug_count="${bug_count:-0}"
|
|
408
|
+
warning_count=$(grep -ciE '\*\*\[?(Warning|Suggestion)\]?\*\*' "$review_file" 2>/dev/null || true)
|
|
409
|
+
warning_count="${warning_count:-0}"
|
|
410
|
+
fi
|
|
411
|
+
local total_issues=$((critical_count + bug_count + warning_count))
|
|
412
|
+
|
|
413
|
+
if [[ "$critical_count" -gt 0 ]]; then
|
|
414
|
+
error "Review found ${BOLD}$critical_count critical${RESET} issue(s) — see $review_file"
|
|
415
|
+
elif [[ "$bug_count" -gt 0 ]]; then
|
|
416
|
+
warn "Review found $bug_count bug/security issue(s) — see ${DIM}$review_file${RESET}"
|
|
417
|
+
elif [[ "$total_issues" -gt 0 ]]; then
|
|
418
|
+
info "Review found $total_issues suggestion(s)"
|
|
419
|
+
else
|
|
420
|
+
success "Review clean"
|
|
421
|
+
fi
|
|
422
|
+
|
|
423
|
+
# ── Dark factory: formal spec verification ──
|
|
424
|
+
if type formal_spec_extract >/dev/null 2>&1; then
|
|
425
|
+
local _spec_file="${ARTIFACTS_DIR}/formal-specs.json"
|
|
426
|
+
local _spec_report="${ARTIFACTS_DIR}/formal-spec-report.json"
|
|
427
|
+
local diff_files_for_spec
|
|
428
|
+
diff_files_for_spec=$(_safe_base_diff --name-only 2>/dev/null || true)
|
|
429
|
+
if [[ -n "$diff_files_for_spec" ]]; then
|
|
430
|
+
formal_spec_extract "${PROJECT_ROOT:-.}" "$_spec_file" >/dev/null 2>&1 || true
|
|
431
|
+
if [[ -f "$_spec_file" ]]; then
|
|
432
|
+
local _spec_count
|
|
433
|
+
_spec_count=$(jq -r '.count // 0' "$_spec_file" 2>/dev/null || echo "0")
|
|
434
|
+
if [[ "$_spec_count" -gt 0 ]]; then
|
|
435
|
+
formal_spec_verify "$_spec_file" "${PROJECT_ROOT:-.}" "$_spec_report" >/dev/null 2>&1 || true
|
|
436
|
+
if [[ -f "$_spec_report" ]]; then
|
|
437
|
+
local _spec_violations _spec_pct
|
|
438
|
+
_spec_violations=$(jq -r '.violations // 0' "$_spec_report" 2>/dev/null || echo "0")
|
|
439
|
+
_spec_pct=$(jq -r '.compliance_pct // 100' "$_spec_report" 2>/dev/null || echo "100")
|
|
440
|
+
if [[ "$_spec_violations" -gt 0 ]]; then
|
|
441
|
+
warn "Formal spec verification: ${_spec_violations} violation(s), ${_spec_pct}% compliance"
|
|
442
|
+
else
|
|
443
|
+
success "Formal spec verification: ${_spec_pct}% compliance"
|
|
444
|
+
fi
|
|
445
|
+
emit_event "review.formal_spec" \
|
|
446
|
+
"issue=${ISSUE_NUMBER:-0}" \
|
|
447
|
+
"violations=$_spec_violations" \
|
|
448
|
+
"compliance_pct=$_spec_pct" 2>/dev/null || true
|
|
449
|
+
fi
|
|
450
|
+
fi
|
|
451
|
+
fi
|
|
452
|
+
fi
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
# ── Oversight gate: pipeline review/quality stages block on verdict ──
|
|
456
|
+
if [[ -x "$SCRIPT_DIR/sw-oversight.sh" ]] && [[ "${SKIP_GATES:-false}" != "true" ]]; then
|
|
457
|
+
local reject_reason=""
|
|
458
|
+
local _sec_count
|
|
459
|
+
_sec_count=$(grep -ciE '\*\*\[?Security\]?\*\*' "$review_file" 2>/dev/null || true)
|
|
460
|
+
_sec_count="${_sec_count:-0}"
|
|
461
|
+
local _blocking=$((critical_count + _sec_count))
|
|
462
|
+
[[ "$_blocking" -gt 0 ]] && reject_reason="Review found ${_blocking} critical/security issue(s)"
|
|
463
|
+
if ! bash "$SCRIPT_DIR/sw-oversight.sh" gate --diff "$diff_file" --description "${GOAL:-Pipeline review}" --reject-if "$reject_reason" >/dev/null 2>&1; then
|
|
464
|
+
error "Oversight gate rejected — blocking pipeline"
|
|
465
|
+
emit_event "review.oversight_blocked" "issue=${ISSUE_NUMBER:-0}"
|
|
466
|
+
log_stage "review" "BLOCKED: oversight gate rejected"
|
|
467
|
+
return 1
|
|
468
|
+
fi
|
|
469
|
+
fi
|
|
470
|
+
|
|
471
|
+
# ── Review Blocking Gate ──
|
|
472
|
+
# Block pipeline on critical/bug/security issues (bugs now block as per spec)
|
|
473
|
+
local security_count
|
|
474
|
+
security_count=$(grep -ciE '\*\*\[?Security\]?\*\*' "$review_file" 2>/dev/null || true)
|
|
475
|
+
security_count="${security_count:-0}"
|
|
476
|
+
|
|
477
|
+
local blocking_issues=$((critical_count + bug_count + security_count))
|
|
478
|
+
|
|
479
|
+
if [[ "$blocking_issues" -gt 0 ]]; then
|
|
480
|
+
# Check if compound_quality stage is enabled — if so, let it handle issues
|
|
481
|
+
local compound_enabled="false"
|
|
482
|
+
if [[ -n "${PIPELINE_CONFIG:-}" && -f "${PIPELINE_CONFIG:-/dev/null}" ]]; then
|
|
483
|
+
compound_enabled=$(jq -r '.stages[] | select(.id == "compound_quality") | .enabled' "$PIPELINE_CONFIG" 2>/dev/null) || true
|
|
484
|
+
[[ -z "$compound_enabled" || "$compound_enabled" == "null" ]] && compound_enabled="false"
|
|
485
|
+
fi
|
|
486
|
+
|
|
487
|
+
# Check if this is a fast template (don't block fast pipelines)
|
|
488
|
+
local is_fast="false"
|
|
489
|
+
if [[ "${PIPELINE_NAME:-}" == "fast" || "${PIPELINE_NAME:-}" == "hotfix" ]]; then
|
|
490
|
+
is_fast="true"
|
|
491
|
+
fi
|
|
492
|
+
|
|
493
|
+
if [[ "$compound_enabled" == "true" ]]; then
|
|
494
|
+
info "Review found ${blocking_issues} critical/security issue(s) — compound_quality stage will handle"
|
|
495
|
+
elif [[ "$is_fast" == "true" ]]; then
|
|
496
|
+
warn "Review found ${blocking_issues} critical/security issue(s) — fast template, not blocking"
|
|
497
|
+
elif [[ "${SKIP_GATES:-false}" == "true" ]]; then
|
|
498
|
+
warn "Review found ${blocking_issues} critical/security issue(s) — skip-gates mode, not blocking"
|
|
499
|
+
else
|
|
500
|
+
error "Review found ${BOLD}${blocking_issues} critical/security issue(s)${RESET} — blocking pipeline"
|
|
501
|
+
emit_event "review.blocked" \
|
|
502
|
+
"issue=${ISSUE_NUMBER:-0}" \
|
|
503
|
+
"critical=${critical_count}" \
|
|
504
|
+
"security=${security_count}"
|
|
505
|
+
|
|
506
|
+
# Save blocking issues for self-healing context
|
|
507
|
+
grep -iE '\*\*\[?(Critical|Security)\]?\*\*' "$review_file" > "$ARTIFACTS_DIR/review-blockers.md" 2>/dev/null || true
|
|
508
|
+
|
|
509
|
+
# Post review to GitHub before failing
|
|
510
|
+
if [[ -n "$ISSUE_NUMBER" ]]; then
|
|
511
|
+
local review_summary
|
|
512
|
+
review_summary=$(head -40 "$review_file")
|
|
513
|
+
gh_comment_issue "$ISSUE_NUMBER" "## 🔍 Code Review — ❌ Blocked
|
|
514
|
+
|
|
515
|
+
**Stats:** $diff_stats
|
|
516
|
+
**Blocking issues:** ${blocking_issues} (${critical_count} critical, ${security_count} security)
|
|
517
|
+
|
|
518
|
+
<details>
|
|
519
|
+
<summary>Review details</summary>
|
|
520
|
+
|
|
521
|
+
${review_summary}
|
|
522
|
+
|
|
523
|
+
</details>
|
|
524
|
+
|
|
525
|
+
_Pipeline will attempt self-healing rebuild._"
|
|
526
|
+
fi
|
|
527
|
+
|
|
528
|
+
log_stage "review" "BLOCKED: $blocking_issues critical/security issues found"
|
|
529
|
+
return 1
|
|
530
|
+
fi
|
|
531
|
+
fi
|
|
532
|
+
|
|
533
|
+
# Post review to GitHub issue
|
|
534
|
+
if [[ -n "$ISSUE_NUMBER" ]]; then
|
|
535
|
+
local review_summary
|
|
536
|
+
review_summary=$(head -40 "$review_file")
|
|
537
|
+
gh_comment_issue "$ISSUE_NUMBER" "## 🔍 Code Review
|
|
538
|
+
|
|
539
|
+
**Stats:** $diff_stats
|
|
540
|
+
**Issues found:** $total_issues (${critical_count} critical, ${bug_count} bugs, ${warning_count} suggestions)
|
|
541
|
+
|
|
542
|
+
<details>
|
|
543
|
+
<summary>Review details</summary>
|
|
544
|
+
|
|
545
|
+
${review_summary}
|
|
546
|
+
|
|
547
|
+
</details>"
|
|
548
|
+
fi
|
|
549
|
+
|
|
550
|
+
log_stage "review" "AI review complete ($total_issues issues: $critical_count critical, $bug_count bugs, $warning_count suggestions)"
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
# ─── Spec Verification Stage (dark factory: spec-driven development) ────────
|
|
554
|
+
# Runs between review and compound_quality. Verifies implementation against
|
|
555
|
+
# the spec by checking each acceptance criterion.
|
|
556
|
+
stage_spec_verification() {
|
|
557
|
+
CURRENT_STAGE_ID="spec_verification"
|
|
558
|
+
|
|
559
|
+
# Check if spec-driven is disabled
|
|
560
|
+
if [[ "${SPEC_DRIVEN_ENABLED:-true}" == "false" ]]; then
|
|
561
|
+
info "Spec-driven development disabled — skipping spec verification"
|
|
562
|
+
return 0
|
|
563
|
+
fi
|
|
564
|
+
|
|
565
|
+
local spec_file="${ARTIFACTS_DIR}/spec.json"
|
|
566
|
+
|
|
567
|
+
if [[ ! -f "$spec_file" ]]; then
|
|
568
|
+
info "No spec found — skipping spec verification"
|
|
569
|
+
log_stage "spec_verification" "Skipped: no spec available"
|
|
570
|
+
return 0
|
|
571
|
+
fi
|
|
572
|
+
|
|
573
|
+
info "Verifying implementation against specification..."
|
|
574
|
+
|
|
575
|
+
local criteria_count
|
|
576
|
+
criteria_count=$(jq '.acceptance_criteria | length' "$spec_file" 2>/dev/null || echo "0")
|
|
577
|
+
|
|
578
|
+
if [[ "$criteria_count" -eq 0 ]]; then
|
|
579
|
+
warn "Spec has no acceptance criteria — skipping verification"
|
|
580
|
+
log_stage "spec_verification" "Skipped: no acceptance criteria in spec"
|
|
581
|
+
return 0
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
# Build verification results
|
|
585
|
+
local verified=0
|
|
586
|
+
local unverified=0
|
|
587
|
+
local manual_review=0
|
|
588
|
+
local results_json="["
|
|
589
|
+
local first=true
|
|
590
|
+
local idx=0
|
|
591
|
+
|
|
592
|
+
while [[ "$idx" -lt "$criteria_count" ]]; do
|
|
593
|
+
local criterion verification_method testable
|
|
594
|
+
criterion=$(jq -r --argjson i "$idx" '.acceptance_criteria[$i].criterion // ""' "$spec_file" 2>/dev/null)
|
|
595
|
+
verification_method=$(jq -r --argjson i "$idx" '.acceptance_criteria[$i].verification_method // "manual"' "$spec_file" 2>/dev/null)
|
|
596
|
+
testable=$(jq -r --argjson i "$idx" '.acceptance_criteria[$i].testable // false' "$spec_file" 2>/dev/null)
|
|
597
|
+
|
|
598
|
+
local status="unverified"
|
|
599
|
+
local evidence=""
|
|
600
|
+
|
|
601
|
+
case "$verification_method" in
|
|
602
|
+
unit_test|integration_test)
|
|
603
|
+
# Check if tests exist and pass by looking at test results
|
|
604
|
+
local test_results="${ARTIFACTS_DIR}/test-results.log"
|
|
605
|
+
if [[ -f "$test_results" ]]; then
|
|
606
|
+
# If test results exist and contain no failures, mark as verified
|
|
607
|
+
local test_failures
|
|
608
|
+
test_failures=$(grep -ciE 'fail|error|FAIL' "$test_results" 2>/dev/null || true)
|
|
609
|
+
test_failures="${test_failures:-0}"
|
|
610
|
+
if [[ "$test_failures" -eq 0 ]]; then
|
|
611
|
+
status="verified"
|
|
612
|
+
evidence="Tests passed (no failures in test-results.log)"
|
|
613
|
+
verified=$((verified + 1))
|
|
614
|
+
else
|
|
615
|
+
status="unverified"
|
|
616
|
+
evidence="Test failures detected in test-results.log"
|
|
617
|
+
unverified=$((unverified + 1))
|
|
618
|
+
fi
|
|
619
|
+
else
|
|
620
|
+
status="unverified"
|
|
621
|
+
evidence="No test results found"
|
|
622
|
+
unverified=$((unverified + 1))
|
|
623
|
+
fi
|
|
624
|
+
;;
|
|
625
|
+
static_analysis)
|
|
626
|
+
# Check if constitutional checker ran and passed
|
|
627
|
+
local const_report="${ARTIFACTS_DIR}/constitutional-audit.json"
|
|
628
|
+
if [[ -f "$const_report" ]]; then
|
|
629
|
+
local violations
|
|
630
|
+
violations=$(jq '.total_violations // 0' "$const_report" 2>/dev/null || echo "0")
|
|
631
|
+
if [[ "$violations" -eq 0 ]]; then
|
|
632
|
+
status="verified"
|
|
633
|
+
evidence="Constitutional audit passed (0 violations)"
|
|
634
|
+
verified=$((verified + 1))
|
|
635
|
+
else
|
|
636
|
+
status="unverified"
|
|
637
|
+
evidence="Constitutional audit found $violations violations"
|
|
638
|
+
unverified=$((unverified + 1))
|
|
639
|
+
fi
|
|
640
|
+
else
|
|
641
|
+
status="unverified"
|
|
642
|
+
evidence="No static analysis results found"
|
|
643
|
+
unverified=$((unverified + 1))
|
|
644
|
+
fi
|
|
645
|
+
;;
|
|
646
|
+
manual)
|
|
647
|
+
status="manual_review"
|
|
648
|
+
evidence="Requires human review"
|
|
649
|
+
manual_review=$((manual_review + 1))
|
|
650
|
+
;;
|
|
651
|
+
*)
|
|
652
|
+
status="unverified"
|
|
653
|
+
evidence="Unknown verification method: $verification_method"
|
|
654
|
+
unverified=$((unverified + 1))
|
|
655
|
+
;;
|
|
656
|
+
esac
|
|
657
|
+
|
|
658
|
+
# Build JSON result entry
|
|
659
|
+
local escaped_criterion escaped_evidence
|
|
660
|
+
escaped_criterion=$(printf '%s' "$criterion" | jq -Rs . 2>/dev/null || echo "\"$criterion\"")
|
|
661
|
+
escaped_evidence=$(printf '%s' "$evidence" | jq -Rs . 2>/dev/null || echo "\"$evidence\"")
|
|
662
|
+
|
|
663
|
+
if $first; then
|
|
664
|
+
first=false
|
|
665
|
+
else
|
|
666
|
+
results_json="${results_json},"
|
|
667
|
+
fi
|
|
668
|
+
results_json="${results_json}{\"criterion\":${escaped_criterion},\"verification_method\":\"${verification_method}\",\"status\":\"${status}\",\"evidence\":${escaped_evidence}}"
|
|
669
|
+
|
|
670
|
+
idx=$((idx + 1))
|
|
671
|
+
done
|
|
672
|
+
results_json="${results_json}]"
|
|
673
|
+
|
|
674
|
+
# Compute compliance score
|
|
675
|
+
local total_checkable=$((verified + unverified))
|
|
676
|
+
local compliance_score=0
|
|
677
|
+
if [[ "$total_checkable" -gt 0 ]]; then
|
|
678
|
+
compliance_score=$((verified * 100 / total_checkable))
|
|
679
|
+
elif [[ "$manual_review" -gt 0 && "$unverified" -eq 0 ]]; then
|
|
680
|
+
# All criteria are manual — treat as 100% machine compliance
|
|
681
|
+
compliance_score=100
|
|
682
|
+
fi
|
|
683
|
+
|
|
684
|
+
# Generate verification report
|
|
685
|
+
local report_file="${ARTIFACTS_DIR}/spec-verification-report.json"
|
|
686
|
+
cat > "$report_file" <<REPORTEOF
|
|
687
|
+
{
|
|
688
|
+
"spec_file": "${spec_file}",
|
|
689
|
+
"verified_at": "$(now_iso)",
|
|
690
|
+
"summary": {
|
|
691
|
+
"total_criteria": ${criteria_count},
|
|
692
|
+
"verified": ${verified},
|
|
693
|
+
"unverified": ${unverified},
|
|
694
|
+
"manual_review": ${manual_review},
|
|
695
|
+
"compliance_score": ${compliance_score}
|
|
696
|
+
},
|
|
697
|
+
"results": ${results_json}
|
|
698
|
+
}
|
|
699
|
+
REPORTEOF
|
|
700
|
+
|
|
701
|
+
# Pretty-print if jq available
|
|
702
|
+
if command -v jq >/dev/null 2>&1; then
|
|
703
|
+
local pp
|
|
704
|
+
pp=$(jq '.' "$report_file" 2>/dev/null) || true
|
|
705
|
+
if [[ -n "$pp" ]]; then
|
|
706
|
+
echo "$pp" > "$report_file"
|
|
707
|
+
fi
|
|
708
|
+
fi
|
|
709
|
+
|
|
710
|
+
save_artifact "spec-verification-report.json" "$(cat "$report_file")" || true
|
|
711
|
+
|
|
712
|
+
# Report results
|
|
713
|
+
if [[ "$compliance_score" -lt 80 ]]; then
|
|
714
|
+
warn "Spec compliance: ${compliance_score}% (${verified}/${total_checkable} verified) — below 80% threshold"
|
|
715
|
+
else
|
|
716
|
+
success "Spec compliance: ${compliance_score}% (${verified}/${total_checkable} verified)"
|
|
717
|
+
fi
|
|
718
|
+
|
|
719
|
+
if [[ "$manual_review" -gt 0 ]]; then
|
|
720
|
+
info "${manual_review} criteria flagged for manual review"
|
|
721
|
+
fi
|
|
722
|
+
|
|
723
|
+
emit_event "spec_verification.completed" \
|
|
724
|
+
"issue=${ISSUE_NUMBER:-0}" \
|
|
725
|
+
"compliance=${compliance_score}" \
|
|
726
|
+
"verified=${verified}" \
|
|
727
|
+
"unverified=${unverified}" \
|
|
728
|
+
"manual=${manual_review}"
|
|
729
|
+
|
|
730
|
+
log_stage "spec_verification" "Compliance: ${compliance_score}% — ${verified} verified, ${unverified} unverified, ${manual_review} manual"
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
# ─── Compound Quality (fallback) ────────────────────────────────────────────
|
|
734
|
+
# Machine-verifiable DoD scorecard, then adversarial review, negative testing, e2e checks.
|
|
735
|
+
# If pipeline-intelligence.sh was sourced first, its enhanced version takes priority.
|
|
736
|
+
if ! type stage_compound_quality >/dev/null 2>&1; then
|
|
737
|
+
stage_compound_quality() {
|
|
738
|
+
CURRENT_STAGE_ID="compound_quality"
|
|
739
|
+
# Consume retry context if this is a retry attempt
|
|
740
|
+
local _retry_ctx="${ARTIFACTS_DIR}/.retry-context-compound_quality.md"
|
|
741
|
+
if [[ -s "$_retry_ctx" ]]; then
|
|
742
|
+
local _cq_retry_hints
|
|
743
|
+
_cq_retry_hints=$(cat "$_retry_ctx" 2>/dev/null || true)
|
|
744
|
+
rm -f "$_retry_ctx"
|
|
745
|
+
fi
|
|
746
|
+
|
|
747
|
+
# Source DoD scorecard library
|
|
748
|
+
if [[ -f "$SCRIPT_DIR/lib/dod-scorecard.sh" ]]; then
|
|
749
|
+
# shellcheck disable=SC1090
|
|
750
|
+
source "$SCRIPT_DIR/lib/dod-scorecard.sh"
|
|
751
|
+
fi
|
|
752
|
+
|
|
753
|
+
# ── Machine-Verifiable DoD Scorecard (runs first) ──
|
|
754
|
+
info "Computing machine-verifiable Definition of Done scorecard..."
|
|
755
|
+
local quality_profile="${PROJECT_ROOT}/.claude/quality-profile.json"
|
|
756
|
+
local dod_scorecard_json
|
|
757
|
+
dod_scorecard_json=$(compute_dod_scorecard "${BASE_BRANCH:-main}" "$ARTIFACTS_DIR" "$quality_profile" 2>/dev/null) || true
|
|
758
|
+
|
|
759
|
+
if [[ -n "$dod_scorecard_json" ]]; then
|
|
760
|
+
# Display scorecard
|
|
761
|
+
local scorecard_display
|
|
762
|
+
scorecard_display=$(format_scorecard "$dod_scorecard_json")
|
|
763
|
+
echo "$scorecard_display"
|
|
764
|
+
|
|
765
|
+
# Log scorecard
|
|
766
|
+
log_stage "compound_quality" "DoD Scorecard computed"
|
|
767
|
+
|
|
768
|
+
# If scorecard fails, skip LLM checks and return failure
|
|
769
|
+
if ! scorecard_passed "$dod_scorecard_json"; then
|
|
770
|
+
local blocking_failures
|
|
771
|
+
blocking_failures=$(get_blocking_failures "$dod_scorecard_json")
|
|
772
|
+
error "DoD Scorecard gate failed on: $blocking_failures"
|
|
773
|
+
emit_event "compound_quality.dod_failed" \
|
|
774
|
+
"issue=${ISSUE_NUMBER:-0}" \
|
|
775
|
+
"failures=$blocking_failures"
|
|
776
|
+
return 1
|
|
777
|
+
else
|
|
778
|
+
success "DoD Scorecard gate passed"
|
|
779
|
+
emit_event "compound_quality.dod_passed" "issue=${ISSUE_NUMBER:-0}"
|
|
780
|
+
fi
|
|
781
|
+
fi
|
|
782
|
+
|
|
783
|
+
# Load skill prompts for compound quality (used by adversarial review)
|
|
784
|
+
local _cq_skills=""
|
|
785
|
+
if type skill_load_prompts >/dev/null 2>&1; then
|
|
786
|
+
_cq_skills=$(skill_load_prompts "${INTELLIGENCE_ISSUE_TYPE:-backend}" "compound_quality" 2>/dev/null || true)
|
|
787
|
+
fi
|
|
788
|
+
# Write skill guidance to artifact for sw-adversarial.sh to consume
|
|
789
|
+
if [[ -n "$_cq_skills" ]]; then
|
|
790
|
+
echo "$_cq_skills" > "${ARTIFACTS_DIR}/.compound-quality-skills.md" 2>/dev/null || true
|
|
791
|
+
fi
|
|
792
|
+
if [[ -n "${_cq_retry_hints:-}" ]]; then
|
|
793
|
+
echo "$_cq_retry_hints" >> "${ARTIFACTS_DIR}/.compound-quality-skills.md" 2>/dev/null || true
|
|
794
|
+
fi
|
|
795
|
+
|
|
796
|
+
# Read stage config from pipeline template
|
|
797
|
+
local cfg
|
|
798
|
+
cfg=$(jq -r '.stages[] | select(.id == "compound_quality") | .config // {}' "$PIPELINE_CONFIG" 2>/dev/null) || cfg="{}"
|
|
799
|
+
|
|
800
|
+
local do_adversarial do_negative do_e2e do_dod max_cycles blocking
|
|
801
|
+
do_adversarial=$(echo "$cfg" | jq -r '.adversarial // false')
|
|
802
|
+
do_negative=$(echo "$cfg" | jq -r '.negative // false')
|
|
803
|
+
do_e2e=$(echo "$cfg" | jq -r '.e2e // false')
|
|
804
|
+
do_dod=$(echo "$cfg" | jq -r '.dod_audit // false')
|
|
805
|
+
max_cycles=$(echo "$cfg" | jq -r '.max_cycles // 1')
|
|
806
|
+
blocking=$(echo "$cfg" | jq -r '.compound_quality_blocking // false')
|
|
807
|
+
|
|
808
|
+
local pass_count=0 fail_count=0 total=0
|
|
809
|
+
local compound_log="$ARTIFACTS_DIR/compound-quality.log"
|
|
810
|
+
: > "$compound_log"
|
|
811
|
+
|
|
812
|
+
# ── Adversarial review ──
|
|
813
|
+
if [[ "$do_adversarial" == "true" ]]; then
|
|
814
|
+
total=$((total + 1))
|
|
815
|
+
info "Running adversarial review..."
|
|
816
|
+
if [[ -x "$SCRIPT_DIR/sw-adversarial.sh" ]]; then
|
|
817
|
+
if bash "$SCRIPT_DIR/sw-adversarial.sh" --repo "${REPO_DIR:-.}" >> "$compound_log" 2>&1; then
|
|
818
|
+
pass_count=$((pass_count + 1))
|
|
819
|
+
success "Adversarial review passed"
|
|
820
|
+
else
|
|
821
|
+
fail_count=$((fail_count + 1))
|
|
822
|
+
warn "Adversarial review found issues"
|
|
823
|
+
fi
|
|
824
|
+
else
|
|
825
|
+
warn "sw-adversarial.sh not found, skipping"
|
|
826
|
+
fi
|
|
827
|
+
fi
|
|
828
|
+
|
|
829
|
+
# ── Negative / edge-case testing ──
|
|
830
|
+
if [[ "$do_negative" == "true" ]]; then
|
|
831
|
+
total=$((total + 1))
|
|
832
|
+
info "Running negative test pass..."
|
|
833
|
+
if [[ -n "${TEST_CMD:-}" ]]; then
|
|
834
|
+
if eval "$TEST_CMD" >> "$compound_log" 2>&1; then
|
|
835
|
+
pass_count=$((pass_count + 1))
|
|
836
|
+
success "Negative test pass passed"
|
|
837
|
+
else
|
|
838
|
+
fail_count=$((fail_count + 1))
|
|
839
|
+
warn "Negative test pass found failures"
|
|
840
|
+
fi
|
|
841
|
+
else
|
|
842
|
+
pass_count=$((pass_count + 1))
|
|
843
|
+
info "No test command configured, skipping negative tests"
|
|
844
|
+
fi
|
|
845
|
+
fi
|
|
846
|
+
|
|
847
|
+
# ── E2E checks ──
|
|
848
|
+
if [[ "$do_e2e" == "true" ]]; then
|
|
849
|
+
total=$((total + 1))
|
|
850
|
+
info "Running e2e checks..."
|
|
851
|
+
if [[ -x "$SCRIPT_DIR/sw-e2e-orchestrator.sh" ]]; then
|
|
852
|
+
if bash "$SCRIPT_DIR/sw-e2e-orchestrator.sh" run >> "$compound_log" 2>&1; then
|
|
853
|
+
pass_count=$((pass_count + 1))
|
|
854
|
+
success "E2E checks passed"
|
|
855
|
+
else
|
|
856
|
+
fail_count=$((fail_count + 1))
|
|
857
|
+
warn "E2E checks found issues"
|
|
858
|
+
fi
|
|
859
|
+
else
|
|
860
|
+
pass_count=$((pass_count + 1))
|
|
861
|
+
info "sw-e2e-orchestrator.sh not found, skipping e2e"
|
|
862
|
+
fi
|
|
863
|
+
fi
|
|
864
|
+
|
|
865
|
+
# ── Definition of Done audit ──
|
|
866
|
+
if [[ "$do_dod" == "true" ]]; then
|
|
867
|
+
total=$((total + 1))
|
|
868
|
+
info "Running definition-of-done audit..."
|
|
869
|
+
if [[ -x "$SCRIPT_DIR/sw-quality.sh" ]]; then
|
|
870
|
+
if bash "$SCRIPT_DIR/sw-quality.sh" validate >> "$compound_log" 2>&1; then
|
|
871
|
+
pass_count=$((pass_count + 1))
|
|
872
|
+
success "DoD audit passed"
|
|
873
|
+
else
|
|
874
|
+
fail_count=$((fail_count + 1))
|
|
875
|
+
warn "DoD audit found gaps"
|
|
876
|
+
fi
|
|
877
|
+
else
|
|
878
|
+
pass_count=$((pass_count + 1))
|
|
879
|
+
info "sw-quality.sh not found, skipping DoD audit"
|
|
880
|
+
fi
|
|
881
|
+
fi
|
|
882
|
+
|
|
883
|
+
# ── Summary ──
|
|
884
|
+
log_stage "compound_quality" "Compound quality: $pass_count/$total checks passed, $fail_count failed"
|
|
885
|
+
|
|
886
|
+
if [[ "$fail_count" -gt 0 && "$blocking" == "true" ]]; then
|
|
887
|
+
error "Compound quality gate failed: $fail_count of $total checks failed"
|
|
888
|
+
return 1
|
|
889
|
+
fi
|
|
890
|
+
|
|
891
|
+
return 0
|
|
892
|
+
}
|
|
893
|
+
fi # end fallback stage_compound_quality
|
|
894
|
+
|
|
895
|
+
# ─── Audit Stage ───────────────────────────────────────────────────────────
|
|
896
|
+
# Security and quality audits: secrets scan, file permissions, || true usage,
|
|
897
|
+
# test coverage delta, atomic write checks.
|
|
898
|
+
stage_audit() {
|
|
899
|
+
CURRENT_STAGE_ID="audit"
|
|
900
|
+
|
|
901
|
+
# Read stage config from pipeline template
|
|
902
|
+
local cfg
|
|
903
|
+
cfg=$(jq -r '.stages[] | select(.id == "audit") | .config // {}' "$PIPELINE_CONFIG" 2>/dev/null) || cfg="{}"
|
|
904
|
+
|
|
905
|
+
local do_secret_scan do_perms do_atomic_writes do_coverage blocking
|
|
906
|
+
do_secret_scan=$(echo "$cfg" | jq -r '.secret_scan // true')
|
|
907
|
+
do_perms=$(echo "$cfg" | jq -r '.file_permissions // true')
|
|
908
|
+
do_atomic_writes=$(echo "$cfg" | jq -r '.atomic_writes // true')
|
|
909
|
+
do_coverage=$(echo "$cfg" | jq -r '.coverage_delta // true')
|
|
910
|
+
blocking=$(echo "$cfg" | jq -r '.blocking // false')
|
|
911
|
+
|
|
912
|
+
local audit_log="$ARTIFACTS_DIR/audit.log"
|
|
913
|
+
: > "$audit_log"
|
|
914
|
+
|
|
915
|
+
local issues=0
|
|
916
|
+
|
|
917
|
+
# ── Secret Scanning ──
|
|
918
|
+
if [[ "$do_secret_scan" == "true" ]]; then
|
|
919
|
+
info "Scanning for secrets in changed files..."
|
|
920
|
+
local secret_patterns=(
|
|
921
|
+
"sk-ant-" "ANTHROPIC_API_KEY=" "GITHUB_TOKEN=" "OPENAI_API_KEY="
|
|
922
|
+
"AWS_SECRET_ACCESS_KEY=" "DATABASE_URL=" "PRIVATE_KEY="
|
|
923
|
+
"api_key=" "secret=" "password=" "token="
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
local changed_files
|
|
927
|
+
changed_files=$(git diff --name-only "${BASE_BRANCH:-main}..HEAD" 2>/dev/null || git diff --name-only HEAD~5 2>/dev/null || echo "")
|
|
928
|
+
|
|
929
|
+
for pattern in "${secret_patterns[@]}"; do
|
|
930
|
+
while IFS= read -r file; do
|
|
931
|
+
[[ -z "$file" ]] && continue
|
|
932
|
+
if grep -l "$pattern" "$file" 2>/dev/null | grep -qv node_modules; then
|
|
933
|
+
echo "WARN: Potential secret found in $file: $pattern" >> "$audit_log"
|
|
934
|
+
warn "Found potential secret: $pattern in $file"
|
|
935
|
+
issues=$((issues + 1))
|
|
936
|
+
fi
|
|
937
|
+
done <<< "$changed_files"
|
|
938
|
+
done
|
|
939
|
+
fi
|
|
940
|
+
|
|
941
|
+
# ── File Permission Check ──
|
|
942
|
+
if [[ "$do_perms" == "true" ]]; then
|
|
943
|
+
info "Checking file permissions on sensitive files..."
|
|
944
|
+
local sensitive_patterns=(".env" "secret" "credential" "key" "token" "config")
|
|
945
|
+
|
|
946
|
+
for pattern in "${sensitive_patterns[@]}"; do
|
|
947
|
+
while IFS= read -r file; do
|
|
948
|
+
[[ -z "$file" ]] && continue
|
|
949
|
+
# Check for world-readable files
|
|
950
|
+
local perms
|
|
951
|
+
perms=$(stat -f "%OLp" "$file" 2>/dev/null || stat -c "%a" "$file" 2>/dev/null)
|
|
952
|
+
if [[ "$perms" =~ [4567]$ ]]; then # world-readable
|
|
953
|
+
echo "WARN: World-readable sensitive file: $file ($perms)" >> "$audit_log"
|
|
954
|
+
warn "World-readable file: $file ($perms)"
|
|
955
|
+
issues=$((issues + 1))
|
|
956
|
+
fi
|
|
957
|
+
done < <(find . -name "*${pattern}*" -type f 2>/dev/null | head -20)
|
|
958
|
+
done
|
|
959
|
+
fi
|
|
960
|
+
|
|
961
|
+
# ── || true Count (atomic write pattern) ──
|
|
962
|
+
if [[ "$do_atomic_writes" == "true" ]]; then
|
|
963
|
+
info "Checking for unprotected direct writes (|| true usage)..."
|
|
964
|
+
local baseline_true_count=0
|
|
965
|
+
local current_true_count=0
|
|
966
|
+
|
|
967
|
+
# Baseline (before changes)
|
|
968
|
+
if git rev-parse "${BASE_BRANCH:-main}" >/dev/null 2>&1; then
|
|
969
|
+
baseline_true_count=$(git show "${BASE_BRANCH:-main}:." 2>/dev/null | grep -r "|| true" 2>/dev/null | wc -l)
|
|
970
|
+
fi
|
|
971
|
+
|
|
972
|
+
# Current
|
|
973
|
+
current_true_count=$(grep -r "|| true" --include="*.sh" . 2>/dev/null | wc -l)
|
|
974
|
+
|
|
975
|
+
local true_delta=$((current_true_count - baseline_true_count))
|
|
976
|
+
if [[ $true_delta -gt 0 ]]; then
|
|
977
|
+
echo "WARN: Added $true_delta new '|| true' clauses (may mask errors)" >> "$audit_log"
|
|
978
|
+
warn "Added $true_delta new || true patterns"
|
|
979
|
+
issues=$((issues + 1))
|
|
980
|
+
fi
|
|
981
|
+
fi
|
|
982
|
+
|
|
983
|
+
# ── Test Coverage Delta ──
|
|
984
|
+
if [[ "$do_coverage" == "true" && -n "${COVERAGE_FILE:-}" ]]; then
|
|
985
|
+
info "Comparing test coverage..."
|
|
986
|
+
if [[ -f "$COVERAGE_FILE" ]]; then
|
|
987
|
+
local current_coverage
|
|
988
|
+
current_coverage=$(grep -oP 'Coverage: \K[0-9.]+' "$COVERAGE_FILE" | head -1)
|
|
989
|
+
if [[ -n "$current_coverage" ]]; then
|
|
990
|
+
# Try to get baseline coverage
|
|
991
|
+
local baseline_coverage=0
|
|
992
|
+
if git show "${BASE_BRANCH:-main}:.claude/coverage.txt" >/dev/null 2>&1; then
|
|
993
|
+
baseline_coverage=$(git show "${BASE_BRANCH:-main}:.claude/coverage.txt" 2>/dev/null | \
|
|
994
|
+
grep -oP 'Coverage: \K[0-9.]+' | head -1 || echo "0")
|
|
995
|
+
fi
|
|
996
|
+
|
|
997
|
+
local coverage_delta
|
|
998
|
+
coverage_delta=$(echo "$current_coverage - $baseline_coverage" | bc 2>/dev/null || echo "0")
|
|
999
|
+
if (( $(echo "$coverage_delta < -2" | bc -l 2>/dev/null || echo 0) )); then
|
|
1000
|
+
echo "WARN: Coverage decreased by ${coverage_delta}pp (from ${baseline_coverage}% to ${current_coverage}%)" >> "$audit_log"
|
|
1001
|
+
warn "Coverage delta: ${coverage_delta}pp"
|
|
1002
|
+
issues=$((issues + 1))
|
|
1003
|
+
fi
|
|
1004
|
+
fi
|
|
1005
|
+
fi
|
|
1006
|
+
fi
|
|
1007
|
+
|
|
1008
|
+
log_stage "audit" "Audit complete: $issues issue(s) found"
|
|
1009
|
+
|
|
1010
|
+
if [[ "$issues" -gt 0 && "$blocking" == "true" ]]; then
|
|
1011
|
+
error "Audit gate failed: $issues issue(s) detected"
|
|
1012
|
+
emit_event "pipeline.audit_failed" "issues=$issues"
|
|
1013
|
+
return 1
|
|
1014
|
+
fi
|
|
1015
|
+
|
|
1016
|
+
if [[ "$issues" -gt 0 ]]; then
|
|
1017
|
+
emit_event "pipeline.audit_warnings" "issues=$issues"
|
|
1018
|
+
fi
|
|
1019
|
+
|
|
1020
|
+
return 0
|
|
1021
|
+
}
|
|
1022
|
+
|