shipwright-cli 3.2.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.
Files changed (279) hide show
  1. package/.claude/agents/code-reviewer.md +2 -0
  2. package/.claude/agents/devops-engineer.md +2 -0
  3. package/.claude/agents/doc-fleet-agent.md +2 -0
  4. package/.claude/agents/pipeline-agent.md +2 -0
  5. package/.claude/agents/shell-script-specialist.md +2 -0
  6. package/.claude/agents/test-specialist.md +2 -0
  7. package/.claude/hooks/agent-crash-capture.sh +32 -0
  8. package/.claude/hooks/post-tool-use.sh +3 -2
  9. package/.claude/hooks/pre-tool-use.sh +35 -3
  10. package/README.md +4 -4
  11. package/claude-code/hooks/config-change.sh +18 -0
  12. package/claude-code/hooks/instructions-reloaded.sh +7 -0
  13. package/claude-code/hooks/worktree-create.sh +25 -0
  14. package/claude-code/hooks/worktree-remove.sh +20 -0
  15. package/config/code-constitution.json +130 -0
  16. package/dashboard/middleware/auth.ts +134 -0
  17. package/dashboard/middleware/constants.ts +21 -0
  18. package/dashboard/public/index.html +2 -6
  19. package/dashboard/public/styles.css +100 -97
  20. package/dashboard/routes/auth.ts +38 -0
  21. package/dashboard/server.ts +66 -25
  22. package/dashboard/services/config.ts +26 -0
  23. package/dashboard/services/db.ts +118 -0
  24. package/dashboard/src/canvas/pixel-agent.ts +298 -0
  25. package/dashboard/src/canvas/pixel-sprites.ts +440 -0
  26. package/dashboard/src/canvas/shipyard-effects.ts +367 -0
  27. package/dashboard/src/canvas/shipyard-scene.ts +616 -0
  28. package/dashboard/src/canvas/submarine-layout.ts +267 -0
  29. package/dashboard/src/components/header.ts +8 -7
  30. package/dashboard/src/core/router.ts +1 -0
  31. package/dashboard/src/design/submarine-theme.ts +253 -0
  32. package/dashboard/src/main.ts +2 -0
  33. package/dashboard/src/types/api.ts +2 -1
  34. package/dashboard/src/views/activity.ts +2 -1
  35. package/dashboard/src/views/shipyard.ts +39 -0
  36. package/dashboard/types/index.ts +166 -0
  37. package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
  38. package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
  39. package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
  40. package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
  41. package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
  42. package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
  43. package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
  44. package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
  45. package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
  46. package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
  47. package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
  48. package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
  49. package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
  50. package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
  51. package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
  52. package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
  53. package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
  54. package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
  55. package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
  56. package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
  57. package/docs/research/RESEARCH_INDEX.md +439 -0
  58. package/docs/research/RESEARCH_SOURCES.md +440 -0
  59. package/docs/research/RESEARCH_SUMMARY.txt +275 -0
  60. package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
  61. package/package.json +2 -2
  62. package/scripts/lib/adaptive-model.sh +427 -0
  63. package/scripts/lib/adaptive-timeout.sh +316 -0
  64. package/scripts/lib/audit-trail.sh +309 -0
  65. package/scripts/lib/auto-recovery.sh +471 -0
  66. package/scripts/lib/bandit-selector.sh +431 -0
  67. package/scripts/lib/bootstrap.sh +104 -2
  68. package/scripts/lib/causal-graph.sh +455 -0
  69. package/scripts/lib/compat.sh +126 -0
  70. package/scripts/lib/compound-audit.sh +337 -0
  71. package/scripts/lib/constitutional.sh +454 -0
  72. package/scripts/lib/context-budget.sh +359 -0
  73. package/scripts/lib/convergence.sh +594 -0
  74. package/scripts/lib/cost-optimizer.sh +634 -0
  75. package/scripts/lib/daemon-adaptive.sh +10 -0
  76. package/scripts/lib/daemon-dispatch.sh +106 -17
  77. package/scripts/lib/daemon-failure.sh +34 -4
  78. package/scripts/lib/daemon-patrol.sh +23 -2
  79. package/scripts/lib/daemon-poll-github.sh +361 -0
  80. package/scripts/lib/daemon-poll-health.sh +299 -0
  81. package/scripts/lib/daemon-poll.sh +27 -611
  82. package/scripts/lib/daemon-state.sh +112 -66
  83. package/scripts/lib/daemon-triage.sh +10 -0
  84. package/scripts/lib/dod-scorecard.sh +442 -0
  85. package/scripts/lib/error-actionability.sh +300 -0
  86. package/scripts/lib/formal-spec.sh +461 -0
  87. package/scripts/lib/helpers.sh +177 -4
  88. package/scripts/lib/intent-analysis.sh +409 -0
  89. package/scripts/lib/loop-convergence.sh +350 -0
  90. package/scripts/lib/loop-iteration.sh +682 -0
  91. package/scripts/lib/loop-progress.sh +48 -0
  92. package/scripts/lib/loop-restart.sh +185 -0
  93. package/scripts/lib/memory-effectiveness.sh +506 -0
  94. package/scripts/lib/mutation-executor.sh +352 -0
  95. package/scripts/lib/outcome-feedback.sh +521 -0
  96. package/scripts/lib/pipeline-cli.sh +336 -0
  97. package/scripts/lib/pipeline-commands.sh +1216 -0
  98. package/scripts/lib/pipeline-detection.sh +100 -2
  99. package/scripts/lib/pipeline-execution.sh +897 -0
  100. package/scripts/lib/pipeline-github.sh +28 -3
  101. package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
  102. package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
  103. package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
  104. package/scripts/lib/pipeline-intelligence.sh +100 -1136
  105. package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
  106. package/scripts/lib/pipeline-quality-checks.sh +17 -715
  107. package/scripts/lib/pipeline-quality-gates.sh +563 -0
  108. package/scripts/lib/pipeline-stages-build.sh +730 -0
  109. package/scripts/lib/pipeline-stages-delivery.sh +965 -0
  110. package/scripts/lib/pipeline-stages-intake.sh +1133 -0
  111. package/scripts/lib/pipeline-stages-monitor.sh +407 -0
  112. package/scripts/lib/pipeline-stages-review.sh +1022 -0
  113. package/scripts/lib/pipeline-stages.sh +59 -2929
  114. package/scripts/lib/pipeline-state.sh +36 -5
  115. package/scripts/lib/pipeline-util.sh +487 -0
  116. package/scripts/lib/policy-learner.sh +438 -0
  117. package/scripts/lib/process-reward.sh +493 -0
  118. package/scripts/lib/project-detect.sh +649 -0
  119. package/scripts/lib/quality-profile.sh +334 -0
  120. package/scripts/lib/recruit-commands.sh +885 -0
  121. package/scripts/lib/recruit-learning.sh +739 -0
  122. package/scripts/lib/recruit-roles.sh +648 -0
  123. package/scripts/lib/reward-aggregator.sh +458 -0
  124. package/scripts/lib/rl-optimizer.sh +362 -0
  125. package/scripts/lib/root-cause.sh +427 -0
  126. package/scripts/lib/scope-enforcement.sh +445 -0
  127. package/scripts/lib/session-restart.sh +493 -0
  128. package/scripts/lib/skill-memory.sh +300 -0
  129. package/scripts/lib/skill-registry.sh +775 -0
  130. package/scripts/lib/spec-driven.sh +476 -0
  131. package/scripts/lib/test-helpers.sh +18 -7
  132. package/scripts/lib/test-holdout.sh +429 -0
  133. package/scripts/lib/test-optimizer.sh +511 -0
  134. package/scripts/shipwright-file-suggest.sh +45 -0
  135. package/scripts/skills/adversarial-quality.md +61 -0
  136. package/scripts/skills/api-design.md +44 -0
  137. package/scripts/skills/architecture-design.md +50 -0
  138. package/scripts/skills/brainstorming.md +43 -0
  139. package/scripts/skills/data-pipeline.md +44 -0
  140. package/scripts/skills/deploy-safety.md +64 -0
  141. package/scripts/skills/documentation.md +38 -0
  142. package/scripts/skills/frontend-design.md +45 -0
  143. package/scripts/skills/generated/.gitkeep +0 -0
  144. package/scripts/skills/generated/_refinements/.gitkeep +0 -0
  145. package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
  146. package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
  147. package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
  148. package/scripts/skills/generated/cli-version-management.md +29 -0
  149. package/scripts/skills/generated/collection-system-validation.md +99 -0
  150. package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
  151. package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
  152. package/scripts/skills/generated/test-parallelization-detection.md +65 -0
  153. package/scripts/skills/observability.md +79 -0
  154. package/scripts/skills/performance.md +48 -0
  155. package/scripts/skills/pr-quality.md +49 -0
  156. package/scripts/skills/product-thinking.md +43 -0
  157. package/scripts/skills/security-audit.md +49 -0
  158. package/scripts/skills/systematic-debugging.md +40 -0
  159. package/scripts/skills/testing-strategy.md +47 -0
  160. package/scripts/skills/two-stage-review.md +52 -0
  161. package/scripts/skills/validation-thoroughness.md +55 -0
  162. package/scripts/sw +9 -3
  163. package/scripts/sw-activity.sh +9 -2
  164. package/scripts/sw-adaptive.sh +2 -1
  165. package/scripts/sw-adversarial.sh +2 -1
  166. package/scripts/sw-architecture-enforcer.sh +3 -1
  167. package/scripts/sw-auth.sh +12 -2
  168. package/scripts/sw-autonomous.sh +5 -1
  169. package/scripts/sw-changelog.sh +4 -1
  170. package/scripts/sw-checkpoint.sh +2 -1
  171. package/scripts/sw-ci.sh +5 -1
  172. package/scripts/sw-cleanup.sh +4 -26
  173. package/scripts/sw-code-review.sh +10 -4
  174. package/scripts/sw-connect.sh +2 -1
  175. package/scripts/sw-context.sh +2 -1
  176. package/scripts/sw-cost.sh +48 -3
  177. package/scripts/sw-daemon.sh +66 -9
  178. package/scripts/sw-dashboard.sh +3 -1
  179. package/scripts/sw-db.sh +59 -16
  180. package/scripts/sw-decide.sh +8 -2
  181. package/scripts/sw-decompose.sh +360 -17
  182. package/scripts/sw-deps.sh +4 -1
  183. package/scripts/sw-developer-simulation.sh +4 -1
  184. package/scripts/sw-discovery.sh +325 -2
  185. package/scripts/sw-doc-fleet.sh +4 -1
  186. package/scripts/sw-docs-agent.sh +3 -1
  187. package/scripts/sw-docs.sh +2 -1
  188. package/scripts/sw-doctor.sh +453 -2
  189. package/scripts/sw-dora.sh +4 -1
  190. package/scripts/sw-durable.sh +4 -3
  191. package/scripts/sw-e2e-orchestrator.sh +17 -16
  192. package/scripts/sw-eventbus.sh +7 -1
  193. package/scripts/sw-evidence.sh +364 -12
  194. package/scripts/sw-feedback.sh +550 -9
  195. package/scripts/sw-fix.sh +20 -1
  196. package/scripts/sw-fleet-discover.sh +6 -2
  197. package/scripts/sw-fleet-viz.sh +4 -1
  198. package/scripts/sw-fleet.sh +5 -1
  199. package/scripts/sw-github-app.sh +16 -3
  200. package/scripts/sw-github-checks.sh +3 -2
  201. package/scripts/sw-github-deploy.sh +3 -2
  202. package/scripts/sw-github-graphql.sh +18 -7
  203. package/scripts/sw-guild.sh +5 -1
  204. package/scripts/sw-heartbeat.sh +5 -30
  205. package/scripts/sw-hello.sh +67 -0
  206. package/scripts/sw-hygiene.sh +6 -1
  207. package/scripts/sw-incident.sh +265 -1
  208. package/scripts/sw-init.sh +18 -2
  209. package/scripts/sw-instrument.sh +10 -2
  210. package/scripts/sw-intelligence.sh +42 -6
  211. package/scripts/sw-jira.sh +5 -1
  212. package/scripts/sw-launchd.sh +2 -1
  213. package/scripts/sw-linear.sh +4 -1
  214. package/scripts/sw-logs.sh +4 -1
  215. package/scripts/sw-loop.sh +432 -1128
  216. package/scripts/sw-memory.sh +356 -2
  217. package/scripts/sw-mission-control.sh +6 -1
  218. package/scripts/sw-model-router.sh +481 -26
  219. package/scripts/sw-otel.sh +13 -4
  220. package/scripts/sw-oversight.sh +14 -5
  221. package/scripts/sw-patrol-meta.sh +334 -0
  222. package/scripts/sw-pipeline-composer.sh +5 -1
  223. package/scripts/sw-pipeline-vitals.sh +2 -1
  224. package/scripts/sw-pipeline.sh +53 -2664
  225. package/scripts/sw-pm.sh +12 -5
  226. package/scripts/sw-pr-lifecycle.sh +2 -1
  227. package/scripts/sw-predictive.sh +7 -1
  228. package/scripts/sw-prep.sh +185 -2
  229. package/scripts/sw-ps.sh +5 -25
  230. package/scripts/sw-public-dashboard.sh +15 -3
  231. package/scripts/sw-quality.sh +2 -1
  232. package/scripts/sw-reaper.sh +8 -25
  233. package/scripts/sw-recruit.sh +156 -2303
  234. package/scripts/sw-regression.sh +19 -12
  235. package/scripts/sw-release-manager.sh +3 -1
  236. package/scripts/sw-release.sh +4 -1
  237. package/scripts/sw-remote.sh +3 -1
  238. package/scripts/sw-replay.sh +7 -1
  239. package/scripts/sw-retro.sh +158 -1
  240. package/scripts/sw-review-rerun.sh +3 -1
  241. package/scripts/sw-scale.sh +10 -3
  242. package/scripts/sw-security-audit.sh +6 -1
  243. package/scripts/sw-self-optimize.sh +6 -3
  244. package/scripts/sw-session.sh +9 -3
  245. package/scripts/sw-setup.sh +3 -1
  246. package/scripts/sw-stall-detector.sh +406 -0
  247. package/scripts/sw-standup.sh +15 -7
  248. package/scripts/sw-status.sh +3 -1
  249. package/scripts/sw-strategic.sh +4 -1
  250. package/scripts/sw-stream.sh +7 -1
  251. package/scripts/sw-swarm.sh +18 -6
  252. package/scripts/sw-team-stages.sh +13 -6
  253. package/scripts/sw-templates.sh +5 -29
  254. package/scripts/sw-testgen.sh +7 -1
  255. package/scripts/sw-tmux-pipeline.sh +4 -1
  256. package/scripts/sw-tmux-role-color.sh +2 -0
  257. package/scripts/sw-tmux-status.sh +1 -1
  258. package/scripts/sw-tmux.sh +3 -1
  259. package/scripts/sw-trace.sh +3 -1
  260. package/scripts/sw-tracker-github.sh +3 -0
  261. package/scripts/sw-tracker-jira.sh +3 -0
  262. package/scripts/sw-tracker-linear.sh +3 -0
  263. package/scripts/sw-tracker.sh +3 -1
  264. package/scripts/sw-triage.sh +2 -1
  265. package/scripts/sw-upgrade.sh +3 -1
  266. package/scripts/sw-ux.sh +5 -2
  267. package/scripts/sw-webhook.sh +3 -1
  268. package/scripts/sw-widgets.sh +3 -1
  269. package/scripts/sw-worktree.sh +15 -3
  270. package/scripts/test-skill-injection.sh +1233 -0
  271. package/templates/pipelines/autonomous.json +27 -3
  272. package/templates/pipelines/cost-aware.json +34 -8
  273. package/templates/pipelines/deployed.json +12 -0
  274. package/templates/pipelines/enterprise.json +12 -0
  275. package/templates/pipelines/fast.json +6 -0
  276. package/templates/pipelines/full.json +27 -3
  277. package/templates/pipelines/hotfix.json +6 -0
  278. package/templates/pipelines/standard.json +12 -0
  279. package/templates/pipelines/tdd.json +12 -0
@@ -3,555 +3,21 @@
3
3
  [[ -n "${_PIPELINE_QUALITY_CHECKS_LOADED:-}" ]] && return 0
4
4
  _PIPELINE_QUALITY_CHECKS_LOADED=1
5
5
 
6
- quality_check_security() {
7
- info "Security audit..."
8
- local audit_log="$ARTIFACTS_DIR/security-audit.log"
9
- local audit_exit=0
10
- local tool_found=false
11
-
12
- # Try npm audit
13
- if [[ -f "package.json" ]] && command -v npm >/dev/null 2>&1; then
14
- tool_found=true
15
- npm audit --production 2>&1 | tee "$audit_log" || audit_exit=$?
16
- # Try pip-audit
17
- elif [[ -f "requirements.txt" || -f "pyproject.toml" ]] && command -v pip-audit >/dev/null 2>&1; then
18
- tool_found=true
19
- pip-audit 2>&1 | tee "$audit_log" || audit_exit=$?
20
- # Try cargo audit
21
- elif [[ -f "Cargo.toml" ]] && command -v cargo-audit >/dev/null 2>&1; then
22
- tool_found=true
23
- cargo audit 2>&1 | tee "$audit_log" || audit_exit=$?
24
- fi
25
-
26
- if [[ "$tool_found" != "true" ]]; then
27
- info "No security audit tool found — skipping"
28
- echo "No audit tool available" > "$audit_log"
29
- return 0
30
- fi
31
-
32
- # Parse results for critical/high severity
33
- local critical_count high_count
34
- critical_count=$(grep -ciE 'critical' "$audit_log" 2>/dev/null || true)
35
- critical_count="${critical_count:-0}"
36
- high_count=$(grep -ciE 'high' "$audit_log" 2>/dev/null || true)
37
- high_count="${high_count:-0}"
38
-
39
- emit_event "quality.security" \
40
- "issue=${ISSUE_NUMBER:-0}" \
41
- "critical=$critical_count" \
42
- "high=$high_count"
43
-
44
- if [[ "$critical_count" -gt 0 ]]; then
45
- warn "Security audit: ${critical_count} critical, ${high_count} high"
46
- return 1
47
- fi
48
-
49
- success "Security audit: clean"
50
- return 0
51
- }
52
-
53
- quality_check_bundle_size() {
54
- info "Bundle size check..."
55
- local metrics_log="$ARTIFACTS_DIR/bundle-metrics.log"
56
- local bundle_size=0
57
- local bundle_dir=""
58
-
59
- # Find build output directory — check config files first, then common dirs
60
- # Parse tsconfig.json outDir
61
- if [[ -z "$bundle_dir" && -f "tsconfig.json" ]]; then
62
- local ts_out
63
- ts_out=$(jq -r '.compilerOptions.outDir // empty' tsconfig.json 2>/dev/null || true)
64
- [[ -n "$ts_out" && -d "$ts_out" ]] && bundle_dir="$ts_out"
65
- fi
66
- # Parse package.json build script for output hints
67
- if [[ -z "$bundle_dir" && -f "package.json" ]]; then
68
- local build_script
69
- build_script=$(jq -r '.scripts.build // ""' package.json 2>/dev/null || true)
70
- if [[ -n "$build_script" ]]; then
71
- # Check for common output flags: --outDir, -o, --out-dir
72
- local parsed_out
73
- parsed_out=$(echo "$build_script" | grep -oE '(--outDir|--out-dir|-o)\s+[^ ]+' 2>/dev/null | awk '{print $NF}' | head -1 || true)
74
- [[ -n "$parsed_out" && -d "$parsed_out" ]] && bundle_dir="$parsed_out"
75
- fi
76
- fi
77
- # Fallback: check common directories
78
- if [[ -z "$bundle_dir" ]]; then
79
- for dir in dist build out .next target; do
80
- if [[ -d "$dir" ]]; then
81
- bundle_dir="$dir"
82
- break
83
- fi
84
- done
85
- fi
86
-
87
- if [[ -z "$bundle_dir" ]]; then
88
- info "No build output directory found — skipping bundle check"
89
- echo "No build directory" > "$metrics_log"
90
- return 0
91
- fi
92
-
93
- bundle_size=$(du -sk "$bundle_dir" 2>/dev/null | cut -f1 || echo "0")
94
- local bundle_size_human
95
- bundle_size_human=$(du -sh "$bundle_dir" 2>/dev/null | cut -f1 || echo "unknown")
96
-
97
- echo "Bundle directory: $bundle_dir" > "$metrics_log"
98
- echo "Size: ${bundle_size}KB (${bundle_size_human})" >> "$metrics_log"
99
-
100
- emit_event "quality.bundle" \
101
- "issue=${ISSUE_NUMBER:-0}" \
102
- "size_kb=$bundle_size" \
103
- "directory=$bundle_dir"
104
-
105
- # Adaptive bundle size check: statistical deviation from historical mean
106
- local repo_hash_bundle
107
- repo_hash_bundle=$(echo -n "$PROJECT_ROOT" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
108
- local bundle_baselines_dir="${HOME}/.shipwright/baselines/${repo_hash_bundle}"
109
- local bundle_history_file="${bundle_baselines_dir}/bundle-history.json"
110
-
111
- local bundle_history="[]"
112
- if [[ -f "$bundle_history_file" ]]; then
113
- bundle_history=$(jq '.sizes // []' "$bundle_history_file" 2>/dev/null || echo "[]")
114
- fi
115
-
116
- local bundle_hist_count
117
- bundle_hist_count=$(echo "$bundle_history" | jq 'length' 2>/dev/null || echo "0")
118
-
119
- if [[ "$bundle_hist_count" -ge 3 ]]; then
120
- # Statistical check: alert on growth > 2σ from historical mean
121
- local mean_size stddev_size
122
- mean_size=$(echo "$bundle_history" | jq 'add / length' 2>/dev/null || echo "0")
123
- stddev_size=$(echo "$bundle_history" | jq '
124
- (add / length) as $mean |
125
- (map(. - $mean | . * .) | add / length | sqrt)
126
- ' 2>/dev/null || echo "0")
127
-
128
- # Adaptive tolerance: small repos (<1MB mean) get wider tolerance (3σ), large repos get 2σ
129
- local sigma_mult
130
- sigma_mult=$(awk -v mean="$mean_size" 'BEGIN{ print (mean < 1024 ? 3 : 2) }')
131
- local adaptive_max
132
- adaptive_max=$(awk -v mean="$mean_size" -v sd="$stddev_size" -v mult="$sigma_mult" \
133
- 'BEGIN{ t = mean + mult*sd; min_t = mean * 1.1; printf "%.0f", (t > min_t ? t : min_t) }')
134
-
135
- echo "History: ${bundle_hist_count} runs | Mean: ${mean_size}KB | StdDev: ${stddev_size}KB | Max: ${adaptive_max}KB (${sigma_mult}σ)" >> "$metrics_log"
136
-
137
- if [[ "$bundle_size" -gt "$adaptive_max" ]] 2>/dev/null; then
138
- local growth_pct
139
- growth_pct=$(awk -v cur="$bundle_size" -v mean="$mean_size" 'BEGIN{printf "%d", ((cur - mean) / mean) * 100}')
140
- warn "Bundle size ${growth_pct}% above average (${mean_size}KB → ${bundle_size}KB, ${sigma_mult}σ threshold: ${adaptive_max}KB)"
141
- return 1
142
- fi
143
- else
144
- # Fallback: legacy memory baseline (not enough history for statistical check)
145
- local bundle_growth_limit
146
- bundle_growth_limit=$(_config_get_int "quality.bundle_growth_legacy_pct" 20 2>/dev/null || echo 20)
147
- local baseline_size=""
148
- if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
149
- baseline_size=$(bash "$SCRIPT_DIR/sw-memory.sh" get "bundle_size_kb" 2>/dev/null) || true
150
- fi
151
- if [[ -n "$baseline_size" && "$baseline_size" -gt 0 ]] 2>/dev/null; then
152
- local growth_pct
153
- growth_pct=$(awk -v cur="$bundle_size" -v base="$baseline_size" 'BEGIN{printf "%d", ((cur - base) / base) * 100}')
154
- echo "Baseline: ${baseline_size}KB | Growth: ${growth_pct}%" >> "$metrics_log"
155
- if [[ "$growth_pct" -gt "$bundle_growth_limit" ]]; then
156
- warn "Bundle size grew ${growth_pct}% (${baseline_size}KB → ${bundle_size}KB)"
157
- return 1
158
- fi
159
- fi
160
- fi
161
-
162
- # Append current size to rolling history (keep last 10)
163
- mkdir -p "$bundle_baselines_dir"
164
- local updated_bundle_hist
165
- updated_bundle_hist=$(echo "$bundle_history" | jq --arg sz "$bundle_size" '
166
- . + [($sz | tonumber)] | .[-10:]
167
- ' 2>/dev/null || echo "[$bundle_size]")
168
- local tmp_bundle_hist
169
- tmp_bundle_hist=$(mktemp "${bundle_baselines_dir}/bundle-history.json.XXXXXX")
170
- jq -n --argjson sizes "$updated_bundle_hist" --arg updated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
171
- '{sizes: $sizes, updated: $updated}' > "$tmp_bundle_hist" 2>/dev/null
172
- mv "$tmp_bundle_hist" "$bundle_history_file" 2>/dev/null || true
173
-
174
- # Intelligence: identify top dependency bloaters
175
- if type intelligence_search_memory >/dev/null 2>&1 && [[ -f "package.json" ]] && command -v jq >/dev/null 2>&1; then
176
- local dep_sizes=""
177
- local deps
178
- deps=$(jq -r '.dependencies // {} | keys[]' package.json 2>/dev/null || true)
179
- if [[ -n "$deps" ]]; then
180
- while IFS= read -r dep; do
181
- [[ -z "$dep" ]] && continue
182
- local dep_dir="node_modules/${dep}"
183
- if [[ -d "$dep_dir" ]]; then
184
- local dep_size
185
- dep_size=$(du -sk "$dep_dir" 2>/dev/null | cut -f1 || echo "0")
186
- dep_sizes="${dep_sizes}${dep_size} ${dep}
187
- "
188
- fi
189
- done <<< "$deps"
190
- if [[ -n "$dep_sizes" ]]; then
191
- local top_bloaters
192
- top_bloaters=$(echo "$dep_sizes" | sort -rn | head -3)
193
- if [[ -n "$top_bloaters" ]]; then
194
- echo "" >> "$metrics_log"
195
- echo "Top 3 dependency sizes:" >> "$metrics_log"
196
- echo "$top_bloaters" | while IFS=' ' read -r sz nm; do
197
- [[ -z "$nm" ]] && continue
198
- echo " ${nm}: ${sz}KB" >> "$metrics_log"
199
- done
200
- info "Top bloaters: $(echo "$top_bloaters" | head -1 | awk '{print $2 ": " $1 "KB"}')"
201
- fi
202
- fi
203
- fi
204
- fi
205
-
206
- info "Bundle size: ${bundle_size_human}${bundle_hist_count:+ (${bundle_hist_count} historical samples)}"
207
- return 0
208
- }
209
-
210
- quality_check_perf_regression() {
211
- info "Performance regression check..."
212
- local metrics_log="$ARTIFACTS_DIR/perf-metrics.log"
213
- local test_log="$ARTIFACTS_DIR/test-results.log"
214
-
215
- if [[ ! -f "$test_log" ]]; then
216
- info "No test results — skipping perf check"
217
- echo "No test results available" > "$metrics_log"
218
- return 0
219
- fi
220
-
221
- # Extract test suite duration — multi-framework patterns
222
- local duration_ms=""
223
- # Jest/Vitest: "Time: 12.34 s" or "Duration 12.34s"
224
- duration_ms=$(grep -oE 'Time:\s*[0-9.]+\s*s' "$test_log" 2>/dev/null | grep -oE '[0-9.]+' | tail -1 || true)
225
- [[ -z "$duration_ms" ]] && duration_ms=$(grep -oE 'Duration\s+[0-9.]+\s*s' "$test_log" 2>/dev/null | grep -oE '[0-9.]+' | tail -1 || true)
226
- # pytest: "passed in 12.34s" or "====== 5 passed in 12.34 seconds ======"
227
- [[ -z "$duration_ms" ]] && duration_ms=$(grep -oE 'passed in [0-9.]+s' "$test_log" 2>/dev/null | grep -oE '[0-9.]+' | tail -1 || true)
228
- # Go test: "ok pkg 12.345s"
229
- [[ -z "$duration_ms" ]] && duration_ms=$(grep -oE '^ok\s+\S+\s+[0-9.]+s' "$test_log" 2>/dev/null | grep -oE '[0-9.]+s' | grep -oE '[0-9.]+' | tail -1 || true)
230
- # Cargo test: "test result: ok. ... finished in 12.34s"
231
- [[ -z "$duration_ms" ]] && duration_ms=$(grep -oE 'finished in [0-9.]+s' "$test_log" 2>/dev/null | grep -oE '[0-9.]+' | tail -1 || true)
232
- # Generic: "12.34 seconds" or "12.34s"
233
- [[ -z "$duration_ms" ]] && duration_ms=$(grep -oE '[0-9.]+ ?s(econds?)?' "$test_log" 2>/dev/null | grep -oE '[0-9.]+' | tail -1 || true)
234
-
235
- # Claude fallback: parse test output when no pattern matches
236
- if [[ -z "$duration_ms" ]]; then
237
- local intel_enabled="false"
238
- local daemon_cfg="${PROJECT_ROOT}/.claude/daemon-config.json"
239
- if [[ -f "$daemon_cfg" ]]; then
240
- intel_enabled=$(jq -r '.intelligence.enabled // false' "$daemon_cfg" 2>/dev/null || echo "false")
241
- fi
242
- if [[ "$intel_enabled" == "true" ]] && command -v claude >/dev/null 2>&1; then
243
- local tail_output
244
- tail_output=$(tail -30 "$test_log" 2>/dev/null || true)
245
- if [[ -n "$tail_output" ]]; then
246
- duration_ms=$(claude --print -p "Extract ONLY the total test suite duration in seconds from this output. Reply with ONLY a number (e.g. 12.34). If no duration found, reply NONE.
247
-
248
- $tail_output" < /dev/null 2>/dev/null | grep -oE '^[0-9.]+$' | head -1 || true)
249
- [[ "$duration_ms" == "NONE" ]] && duration_ms=""
250
- fi
251
- fi
252
- fi
253
-
254
- if [[ -z "$duration_ms" ]]; then
255
- info "Could not extract test duration — skipping perf check"
256
- echo "Duration not parseable" > "$metrics_log"
257
- return 0
258
- fi
259
-
260
- echo "Test duration: ${duration_ms}s" > "$metrics_log"
261
-
262
- emit_event "quality.perf" \
263
- "issue=${ISSUE_NUMBER:-0}" \
264
- "duration_s=$duration_ms"
265
-
266
- # Adaptive performance check: 2σ from rolling 10-run average
267
- local repo_hash_perf
268
- repo_hash_perf=$(echo -n "$PROJECT_ROOT" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
269
- local perf_baselines_dir="${HOME}/.shipwright/baselines/${repo_hash_perf}"
270
- local perf_history_file="${perf_baselines_dir}/perf-history.json"
271
-
272
- # Read historical durations (rolling window of last 10 runs)
273
- local history_json="[]"
274
- if [[ -f "$perf_history_file" ]]; then
275
- history_json=$(jq '.durations // []' "$perf_history_file" 2>/dev/null || echo "[]")
276
- fi
277
-
278
- local history_count
279
- history_count=$(echo "$history_json" | jq 'length' 2>/dev/null || echo "0")
280
-
281
- if [[ "$history_count" -ge 3 ]]; then
282
- # Calculate mean and standard deviation from history
283
- local mean_dur stddev_dur
284
- mean_dur=$(echo "$history_json" | jq 'add / length' 2>/dev/null || echo "0")
285
- stddev_dur=$(echo "$history_json" | jq '
286
- (add / length) as $mean |
287
- (map(. - $mean | . * .) | add / length | sqrt)
288
- ' 2>/dev/null || echo "0")
289
-
290
- # Threshold: mean + 2σ (but at least 10% above mean)
291
- local adaptive_threshold
292
- adaptive_threshold=$(awk -v mean="$mean_dur" -v sd="$stddev_dur" \
293
- 'BEGIN{ t = mean + 2*sd; min_t = mean * 1.1; printf "%.2f", (t > min_t ? t : min_t) }')
294
-
295
- echo "History: ${history_count} runs | Mean: ${mean_dur}s | StdDev: ${stddev_dur}s | Threshold: ${adaptive_threshold}s" >> "$metrics_log"
296
-
297
- if awk -v cur="$duration_ms" -v thresh="$adaptive_threshold" 'BEGIN{exit !(cur > thresh)}' 2>/dev/null; then
298
- local slowdown_pct
299
- slowdown_pct=$(awk -v cur="$duration_ms" -v mean="$mean_dur" 'BEGIN{printf "%d", ((cur - mean) / mean) * 100}')
300
- warn "Tests ${slowdown_pct}% slower than rolling average (${mean_dur}s → ${duration_ms}s, threshold: ${adaptive_threshold}s)"
301
- return 1
302
- fi
303
- else
304
- # Fallback: legacy memory baseline (not enough history for statistical check)
305
- local perf_regression_limit
306
- perf_regression_limit=$(_config_get_int "quality.perf_regression_legacy_pct" 30 2>/dev/null || echo 30)
307
- local baseline_dur=""
308
- if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
309
- baseline_dur=$(bash "$SCRIPT_DIR/sw-memory.sh" get "test_duration_s" 2>/dev/null) || true
310
- fi
311
- if [[ -n "$baseline_dur" ]] && awk -v cur="$duration_ms" -v base="$baseline_dur" 'BEGIN{exit !(base > 0)}' 2>/dev/null; then
312
- local slowdown_pct
313
- slowdown_pct=$(awk -v cur="$duration_ms" -v base="$baseline_dur" 'BEGIN{printf "%d", ((cur - base) / base) * 100}')
314
- echo "Baseline: ${baseline_dur}s | Slowdown: ${slowdown_pct}%" >> "$metrics_log"
315
- if [[ "$slowdown_pct" -gt "$perf_regression_limit" ]]; then
316
- warn "Tests ${slowdown_pct}% slower (${baseline_dur}s → ${duration_ms}s)"
317
- return 1
318
- fi
319
- fi
320
- fi
321
-
322
- # Append current duration to rolling history (keep last 10)
323
- mkdir -p "$perf_baselines_dir"
324
- local updated_history
325
- updated_history=$(echo "$history_json" | jq --arg dur "$duration_ms" '
326
- . + [($dur | tonumber)] | .[-10:]
327
- ' 2>/dev/null || echo "[$duration_ms]")
328
- local tmp_perf_hist
329
- tmp_perf_hist=$(mktemp "${perf_baselines_dir}/perf-history.json.XXXXXX")
330
- jq -n --argjson durations "$updated_history" --arg updated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
331
- '{durations: $durations, updated: $updated}' > "$tmp_perf_hist" 2>/dev/null
332
- mv "$tmp_perf_hist" "$perf_history_file" 2>/dev/null || true
333
-
334
- info "Test duration: ${duration_ms}s${history_count:+ (${history_count} historical samples)}"
335
- return 0
336
- }
337
-
338
- quality_check_api_compat() {
339
- info "API compatibility check..."
340
- local compat_log="$ARTIFACTS_DIR/api-compat.log"
341
-
342
- # Look for OpenAPI/Swagger specs — search beyond hardcoded paths
343
- local spec_file=""
344
- for candidate in openapi.json openapi.yaml swagger.json swagger.yaml api/openapi.json docs/openapi.yaml; do
345
- if [[ -f "$candidate" ]]; then
346
- spec_file="$candidate"
347
- break
348
- fi
349
- done
350
- # Broader search if nothing found at common paths
351
- if [[ -z "$spec_file" ]]; then
352
- spec_file=$(find . -maxdepth 4 \( -name "openapi*.json" -o -name "openapi*.yaml" -o -name "openapi*.yml" -o -name "swagger*.json" -o -name "swagger*.yaml" -o -name "swagger*.yml" \) -type f 2>/dev/null | head -1 || true)
353
- fi
354
-
355
- if [[ -z "$spec_file" ]]; then
356
- info "No OpenAPI/Swagger spec found — skipping API compat check"
357
- echo "No API spec found" > "$compat_log"
358
- return 0
359
- fi
360
-
361
- # Check if spec was modified in this branch
362
- local spec_changed
363
- spec_changed=$(git diff --name-only "${BASE_BRANCH}...HEAD" 2>/dev/null | grep -c "$(basename "$spec_file")" || true)
364
- spec_changed="${spec_changed:-0}"
365
-
366
- if [[ "$spec_changed" -eq 0 ]]; then
367
- info "API spec unchanged"
368
- echo "Spec unchanged" > "$compat_log"
369
- return 0
370
- fi
371
-
372
- # Diff the spec against base branch
373
- local old_spec new_spec
374
- old_spec=$(git show "${BASE_BRANCH}:${spec_file}" 2>/dev/null || true)
375
- new_spec=$(cat "$spec_file" 2>/dev/null || true)
376
-
377
- if [[ -z "$old_spec" ]]; then
378
- info "New API spec — no baseline to compare"
379
- echo "New spec, no baseline" > "$compat_log"
380
- return 0
381
- fi
382
-
383
- # Check for breaking changes: removed endpoints, changed methods
384
- local removed_endpoints=""
385
- if command -v jq >/dev/null 2>&1 && [[ "$spec_file" == *.json ]]; then
386
- local old_paths new_paths
387
- old_paths=$(echo "$old_spec" | jq -r '.paths | keys[]' 2>/dev/null | sort || true)
388
- new_paths=$(jq -r '.paths | keys[]' "$spec_file" 2>/dev/null | sort || true)
389
- removed_endpoints=$(comm -23 <(echo "$old_paths") <(echo "$new_paths") 2>/dev/null || true)
390
- fi
391
-
392
- # Enhanced schema diff: parameter changes, response schema, auth changes
393
- local param_changes="" schema_changes=""
394
- if command -v jq >/dev/null 2>&1 && [[ "$spec_file" == *.json ]]; then
395
- # Detect parameter changes on existing endpoints
396
- local common_paths
397
- common_paths=$(comm -12 <(echo "$old_spec" | jq -r '.paths | keys[]' 2>/dev/null | sort) <(jq -r '.paths | keys[]' "$spec_file" 2>/dev/null | sort) 2>/dev/null || true)
398
- if [[ -n "$common_paths" ]]; then
399
- while IFS= read -r path; do
400
- [[ -z "$path" ]] && continue
401
- local old_params new_params
402
- old_params=$(echo "$old_spec" | jq -r --arg p "$path" '.paths[$p] | to_entries[] | .value.parameters // [] | .[].name' 2>/dev/null | sort || true)
403
- new_params=$(jq -r --arg p "$path" '.paths[$p] | to_entries[] | .value.parameters // [] | .[].name' "$spec_file" 2>/dev/null | sort || true)
404
- local removed_params
405
- removed_params=$(comm -23 <(echo "$old_params") <(echo "$new_params") 2>/dev/null || true)
406
- [[ -n "$removed_params" ]] && param_changes="${param_changes}${path}: removed params: ${removed_params}
407
- "
408
- done <<< "$common_paths"
409
- fi
410
- fi
411
-
412
- # Intelligence: semantic API diff for complex changes
413
- local semantic_diff=""
414
- if type intelligence_search_memory >/dev/null 2>&1 && command -v claude >/dev/null 2>&1; then
415
- local spec_git_diff
416
- spec_git_diff=$(git diff "${BASE_BRANCH}...HEAD" -- "$spec_file" 2>/dev/null | head -200 || true)
417
- if [[ -n "$spec_git_diff" ]]; then
418
- semantic_diff=$(claude --print --output-format text -p "Analyze this API spec diff for breaking changes. List: removed endpoints, changed parameters, altered response schemas, auth changes. Be concise.
419
-
420
- ${spec_git_diff}" --model haiku < /dev/null 2>/dev/null || true)
421
- fi
422
- fi
423
-
424
- {
425
- echo "Spec: $spec_file"
426
- echo "Changed: yes"
427
- if [[ -n "$removed_endpoints" ]]; then
428
- echo "BREAKING — Removed endpoints:"
429
- echo "$removed_endpoints"
430
- fi
431
- if [[ -n "$param_changes" ]]; then
432
- echo "BREAKING — Parameter changes:"
433
- echo "$param_changes"
434
- fi
435
- if [[ -n "$semantic_diff" ]]; then
436
- echo ""
437
- echo "Semantic analysis:"
438
- echo "$semantic_diff"
439
- fi
440
- if [[ -z "$removed_endpoints" && -z "$param_changes" ]]; then
441
- echo "No breaking changes detected"
442
- fi
443
- } > "$compat_log"
444
-
445
- if [[ -n "$removed_endpoints" || -n "$param_changes" ]]; then
446
- local issue_count=0
447
- [[ -n "$removed_endpoints" ]] && issue_count=$((issue_count + $(echo "$removed_endpoints" | wc -l | xargs)))
448
- [[ -n "$param_changes" ]] && issue_count=$((issue_count + $(echo "$param_changes" | grep -c '.' 2>/dev/null || true)))
449
- warn "API breaking changes: ${issue_count} issue(s) found"
450
- return 1
451
- fi
452
-
453
- success "API compatibility: no breaking changes"
454
- return 0
455
- }
456
-
457
- quality_check_coverage() {
458
- info "Coverage analysis..."
459
- local test_log="$ARTIFACTS_DIR/test-results.log"
460
-
461
- if [[ ! -f "$test_log" ]]; then
462
- info "No test results — skipping coverage check"
463
- return 0
464
- fi
465
-
466
- # Extract coverage percentage using shared parser
467
- local coverage=""
468
- coverage=$(parse_coverage_from_output "$test_log")
469
-
470
- # Claude fallback: parse test output when no pattern matches
471
- if [[ -z "$coverage" ]]; then
472
- local intel_enabled_cov="false"
473
- local daemon_cfg_cov="${PROJECT_ROOT}/.claude/daemon-config.json"
474
- if [[ -f "$daemon_cfg_cov" ]]; then
475
- intel_enabled_cov=$(jq -r '.intelligence.enabled // false' "$daemon_cfg_cov" 2>/dev/null || echo "false")
476
- fi
477
- if [[ "$intel_enabled_cov" == "true" ]] && command -v claude >/dev/null 2>&1; then
478
- local tail_cov_output
479
- tail_cov_output=$(tail -40 "$test_log" 2>/dev/null || true)
480
- if [[ -n "$tail_cov_output" ]]; then
481
- coverage=$(claude --print -p "Extract ONLY the overall code coverage percentage from this test output. Reply with ONLY a number (e.g. 85.5). If no coverage found, reply NONE.
482
-
483
- $tail_cov_output" < /dev/null 2>/dev/null | grep -oE '^[0-9.]+$' | head -1 || true)
484
- [[ "$coverage" == "NONE" ]] && coverage=""
485
- fi
486
- fi
487
- fi
488
-
489
- if [[ -z "$coverage" ]]; then
490
- info "Could not extract coverage — skipping"
491
- return 0
492
- fi
493
-
494
- emit_event "quality.coverage" \
495
- "issue=${ISSUE_NUMBER:-0}" \
496
- "coverage=$coverage"
497
-
498
- # Check against pipeline config minimum
499
- local coverage_min
500
- coverage_min=$(jq -r --arg id "test" '(.stages[] | select(.id == $id) | .config.coverage_min) // 0' "$PIPELINE_CONFIG" 2>/dev/null) || true
501
- [[ -z "$coverage_min" || "$coverage_min" == "null" ]] && coverage_min=0
502
-
503
- # Adaptive baseline: read from baselines file, enforce no-regression (>= baseline - 2%)
504
- local repo_hash_cov
505
- repo_hash_cov=$(echo -n "$PROJECT_ROOT" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
506
- local baselines_dir="${HOME}/.shipwright/baselines/${repo_hash_cov}"
507
- local coverage_baseline_file="${baselines_dir}/coverage.json"
508
-
509
- local baseline_coverage=""
510
- if [[ -f "$coverage_baseline_file" ]]; then
511
- baseline_coverage=$(jq -r '.baseline // empty' "$coverage_baseline_file" 2>/dev/null) || true
512
- fi
513
- # Fallback: try legacy memory baseline
514
- if [[ -z "$baseline_coverage" ]] && [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
515
- baseline_coverage=$(bash "$SCRIPT_DIR/sw-memory.sh" get "coverage_pct" 2>/dev/null) || true
516
- fi
517
-
518
- local dropped=false
519
- if [[ -n "$baseline_coverage" && "$baseline_coverage" != "0" ]] && awk -v cur="$coverage" -v base="$baseline_coverage" 'BEGIN{exit !(base > 0)}' 2>/dev/null; then
520
- # Adaptive: allow 2% regression tolerance from baseline
521
- local min_allowed
522
- min_allowed=$(awk -v base="$baseline_coverage" 'BEGIN{printf "%d", base - 2}')
523
- if awk -v cur="$coverage" -v min="$min_allowed" 'BEGIN{exit !(cur < min)}' 2>/dev/null; then
524
- warn "Coverage regression: ${baseline_coverage}% → ${coverage}% (adaptive min: ${min_allowed}%)"
525
- dropped=true
526
- fi
527
- fi
528
-
529
- if [[ "$coverage_min" -gt 0 ]] 2>/dev/null && awk -v cov="$coverage" -v min="$coverage_min" 'BEGIN{exit !(cov < min)}' 2>/dev/null; then
530
- warn "Coverage ${coverage}% below minimum ${coverage_min}%"
531
- return 1
532
- fi
533
-
534
- if $dropped; then
535
- return 1
536
- fi
537
-
538
- # Update baseline on success (first run or improvement)
539
- if [[ -z "$baseline_coverage" ]] || awk -v cur="$coverage" -v base="$baseline_coverage" 'BEGIN{exit !(cur >= base)}' 2>/dev/null; then
540
- mkdir -p "$baselines_dir"
541
- local tmp_cov_baseline
542
- tmp_cov_baseline=$(mktemp "${baselines_dir}/coverage.json.XXXXXX")
543
- jq -n --arg baseline "$coverage" --arg updated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
544
- '{baseline: ($baseline | tonumber), updated: $updated}' > "$tmp_cov_baseline" 2>/dev/null
545
- mv "$tmp_cov_baseline" "$coverage_baseline_file" 2>/dev/null || true
546
- fi
547
-
548
- info "Coverage: ${coverage}%${baseline_coverage:+ (baseline: ${baseline_coverage}%)}"
549
- return 0
550
- }
551
-
552
- # ─── Compound Quality Checks ──────────────────────────────────────────────
553
- # Adversarial review, negative prompting, E2E validation, and DoD audit.
554
- # Feeds findings back into a self-healing rebuild loop for automatic fixes.
6
+ # Defaults for variables normally set by sw-pipeline.sh (safe under set -u).
7
+ ARTIFACTS_DIR="${ARTIFACTS_DIR:-.claude/pipeline-artifacts}"
8
+ SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
9
+ PROJECT_ROOT="${PROJECT_ROOT:-$(pwd)}"
10
+ BASE_BRANCH="${BASE_BRANCH:-main}"
11
+ PIPELINE_CONFIG="${PIPELINE_CONFIG:-}"
12
+ TEST_CMD="${TEST_CMD:-}"
13
+
14
+ # Source sub-modules
15
+ if [[ -f "${SCRIPT_DIR}/lib/pipeline-quality-gates.sh" ]]; then
16
+ source "${SCRIPT_DIR}/lib/pipeline-quality-gates.sh"
17
+ fi
18
+ if [[ -f "${SCRIPT_DIR}/lib/pipeline-quality-bash-compat.sh" ]]; then
19
+ source "${SCRIPT_DIR}/lib/pipeline-quality-bash-compat.sh"
20
+ fi
555
21
 
556
22
  run_adversarial_review() {
557
23
  local diff_content
@@ -658,6 +124,7 @@ $diff_content"
658
124
  return 0
659
125
  }
660
126
 
127
+
661
128
  run_negative_prompting() {
662
129
  local changed_files
663
130
  changed_files=$(git diff --name-only "${BASE_BRANCH}...HEAD" 2>/dev/null || true)
@@ -744,6 +211,7 @@ run_e2e_validation() {
744
211
  fi
745
212
  }
746
213
 
214
+
747
215
  run_dod_audit() {
748
216
  local dod_file="$PROJECT_ROOT/.claude/DEFINITION-OF-DONE.md"
749
217
 
@@ -840,85 +308,7 @@ PIPELINE_ADAPTIVE_COMPLEXITY=""
840
308
  # Scans modified .sh files for common bash 3.2 incompatibilities
841
309
  # Returns: count of violations found
842
310
  # ──────────────────────────────────────────────────────────────────────────────
843
- run_bash_compat_check() {
844
- local violations=0
845
- local violation_details=""
846
-
847
- # Get modified .sh files relative to base branch
848
- local changed_files
849
- changed_files=$(git diff --name-only "origin/${BASE_BRANCH:-main}...HEAD" -- '*.sh' 2>/dev/null || echo "")
850
-
851
- if [[ -z "$changed_files" ]]; then
852
- echo "0"
853
- return 0
854
- fi
855
-
856
- # Check each file for bash 3.2 incompatibilities
857
- while IFS= read -r filepath; do
858
- [[ -z "$filepath" ]] && continue
859
-
860
- # declare -A (associative arrays; declare -a is bash 3.2 compatible)
861
- local declare_a_count
862
- declare_a_count=$(grep -c 'declare[[:space:]]*-A' "$filepath" 2>/dev/null || true)
863
- if [[ "$declare_a_count" -gt 0 ]]; then
864
- violations=$((violations + declare_a_count))
865
- violation_details="${violation_details}${filepath}: declare -A (${declare_a_count} occurrences)
866
- "
867
- fi
868
-
869
- # readarray or mapfile
870
- local readarray_count
871
- readarray_count=$(grep -c 'readarray\|mapfile' "$filepath" 2>/dev/null || true)
872
- if [[ "$readarray_count" -gt 0 ]]; then
873
- violations=$((violations + readarray_count))
874
- violation_details="${violation_details}${filepath}: readarray/mapfile (${readarray_count} occurrences)
875
- "
876
- fi
877
311
 
878
- # ${var,,} or ${var^^} (case conversion)
879
- local case_conv_count
880
- case_conv_count=$(grep -c '\$\{[a-zA-Z_][a-zA-Z0-9_]*,,' "$filepath" 2>/dev/null || true)
881
- case_conv_count=$((case_conv_count + $(grep -c '\$\{[a-zA-Z_][a-zA-Z0-9_]*\^\^' "$filepath" 2>/dev/null || true)))
882
- if [[ "$case_conv_count" -gt 0 ]]; then
883
- violations=$((violations + case_conv_count))
884
- violation_details="${violation_details}${filepath}: case conversion \$\{var,,\} or \$\{var\^\^\} (${case_conv_count} occurrences)
885
- "
886
- fi
887
-
888
- # |& (pipe stderr to stdout in-place)
889
- local pipe_ampersand_count
890
- pipe_ampersand_count=$(grep -c '|&' "$filepath" 2>/dev/null || true)
891
- if [[ "$pipe_ampersand_count" -gt 0 ]]; then
892
- violations=$((violations + pipe_ampersand_count))
893
- violation_details="${violation_details}${filepath}: |& operator (${pipe_ampersand_count} occurrences)
894
- "
895
- fi
896
-
897
- # ;& or ;;& in case statements (advanced fallthrough)
898
- local advanced_case_count
899
- advanced_case_count=$(grep -c ';&\|;;&' "$filepath" 2>/dev/null || true)
900
- if [[ "$advanced_case_count" -gt 0 ]]; then
901
- violations=$((violations + advanced_case_count))
902
- violation_details="${violation_details}${filepath}: advanced case ;& or ;;& (${advanced_case_count} occurrences)
903
- "
904
- fi
905
-
906
- done <<< "$changed_files"
907
-
908
- # Log details if violations found
909
- if [[ "$violations" -gt 0 ]]; then
910
- warn "Bash 3.2 compatibility check: ${violations} violation(s) found:"
911
- echo "$violation_details" | sed 's/^/ /'
912
- fi
913
-
914
- echo "$violations"
915
- }
916
-
917
- # ──────────────────────────────────────────────────────────────────────────────
918
- # Test Coverage Check
919
- # Runs configured test command and extracts coverage percentage
920
- # Returns: coverage percentage (0-100), or "skip" if no test command configured
921
- # ──────────────────────────────────────────────────────────────────────────────
922
312
  run_test_coverage_check() {
923
313
  local test_cmd="${TEST_CMD:-}"
924
314
  if [[ -z "$test_cmd" ]]; then
@@ -969,91 +359,3 @@ run_test_coverage_check() {
969
359
  # Scans modified files for anti-patterns: direct echo > file to state/config files
970
360
  # Returns: count of violations found
971
361
  # ──────────────────────────────────────────────────────────────────────────────
972
- run_atomic_write_check() {
973
- local violations=0
974
- local violation_details=""
975
-
976
- # Get modified files (not just .sh — includes state/config files)
977
- local changed_files
978
- changed_files=$(git diff --name-only "origin/${BASE_BRANCH:-main}...HEAD" 2>/dev/null || echo "")
979
-
980
- if [[ -z "$changed_files" ]]; then
981
- echo "0"
982
- return 0
983
- fi
984
-
985
- # Check for direct writes to state/config files (patterns that should use tmp+mv)
986
- # Look for: echo "..." > state/config files
987
- while IFS= read -r filepath; do
988
- [[ -z "$filepath" ]] && continue
989
-
990
- # Only check state/config/artifacts files
991
- if [[ ! "$filepath" =~ (state|config|artifact|cache|db|json)$ ]]; then
992
- continue
993
- fi
994
-
995
- # Check for direct redirection writes (> file) in state/config paths
996
- local bad_writes
997
- bad_writes=$(git show "HEAD:$filepath" 2>/dev/null | grep -c 'echo.*>' 2>/dev/null || true)
998
- bad_writes="${bad_writes:-0}"
999
-
1000
- if [[ "$bad_writes" -gt 0 ]]; then
1001
- violations=$((violations + bad_writes))
1002
- violation_details="${violation_details}${filepath}: ${bad_writes} direct write(s) (should use tmp+mv)
1003
- "
1004
- fi
1005
- done <<< "$changed_files"
1006
-
1007
- if [[ "$violations" -gt 0 ]]; then
1008
- warn "Atomic write violations: ${violations} found (should use tmp file + mv pattern):"
1009
- echo "$violation_details" | sed 's/^/ /'
1010
- fi
1011
-
1012
- echo "$violations"
1013
- }
1014
-
1015
- # ──────────────────────────────────────────────────────────────────────────────
1016
- # New Function Test Detection
1017
- # Detects new functions added in the diff but checks if corresponding tests exist
1018
- # Returns: count of untested new functions
1019
- # ──────────────────────────────────────────────────────────────────────────────
1020
- run_new_function_test_check() {
1021
- local untested_functions=0
1022
- local details=""
1023
-
1024
- # Get diff
1025
- local diff_content
1026
- diff_content=$(git diff "origin/${BASE_BRANCH:-main}...HEAD" 2>/dev/null || true)
1027
-
1028
- if [[ -z "$diff_content" ]]; then
1029
- echo "0"
1030
- return 0
1031
- fi
1032
-
1033
- # Extract newly added function definitions (lines starting with +functionname())
1034
- local new_functions
1035
- new_functions=$(echo "$diff_content" | grep -E '^\+[a-zA-Z_][a-zA-Z0-9_]*\(\)' | sed 's/^\+//' | sed 's/()//' || true)
1036
-
1037
- if [[ -z "$new_functions" ]]; then
1038
- echo "0"
1039
- return 0
1040
- fi
1041
-
1042
- # For each new function, check if test files were modified
1043
- local test_files_modified=0
1044
- test_files_modified=$(echo "$diff_content" | grep -c '\-\-\-.*test\|\.test\.\|_test\.' || true)
1045
-
1046
- # Simple heuristic: if we have new functions but no test file modifications, warn
1047
- if [[ "$test_files_modified" -eq 0 ]]; then
1048
- local func_count
1049
- func_count=$(echo "$new_functions" | wc -l | xargs)
1050
- untested_functions="$func_count"
1051
- details="Added ${func_count} new function(s) but no test file modifications detected"
1052
- fi
1053
-
1054
- if [[ "$untested_functions" -gt 0 ]]; then
1055
- warn "New functions without tests: ${details}"
1056
- fi
1057
-
1058
- echo "$untested_functions"
1059
- }