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.
Files changed (283) 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 +22 -8
  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/config/defaults.json +25 -2
  17. package/config/policy.json +1 -1
  18. package/dashboard/middleware/auth.ts +134 -0
  19. package/dashboard/middleware/constants.ts +21 -0
  20. package/dashboard/public/index.html +8 -6
  21. package/dashboard/public/styles.css +176 -97
  22. package/dashboard/routes/auth.ts +38 -0
  23. package/dashboard/server.ts +117 -25
  24. package/dashboard/services/config.ts +26 -0
  25. package/dashboard/services/db.ts +118 -0
  26. package/dashboard/src/canvas/pixel-agent.ts +298 -0
  27. package/dashboard/src/canvas/pixel-sprites.ts +440 -0
  28. package/dashboard/src/canvas/shipyard-effects.ts +367 -0
  29. package/dashboard/src/canvas/shipyard-scene.ts +616 -0
  30. package/dashboard/src/canvas/submarine-layout.ts +267 -0
  31. package/dashboard/src/components/header.ts +8 -7
  32. package/dashboard/src/core/api.ts +5 -0
  33. package/dashboard/src/core/router.ts +1 -0
  34. package/dashboard/src/design/submarine-theme.ts +253 -0
  35. package/dashboard/src/main.ts +2 -0
  36. package/dashboard/src/types/api.ts +12 -1
  37. package/dashboard/src/views/activity.ts +2 -1
  38. package/dashboard/src/views/metrics.ts +69 -1
  39. package/dashboard/src/views/shipyard.ts +39 -0
  40. package/dashboard/types/index.ts +166 -0
  41. package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
  42. package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
  43. package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
  44. package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
  45. package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
  46. package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
  47. package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
  48. package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
  49. package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
  50. package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
  51. package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
  52. package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
  53. package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
  54. package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
  55. package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
  56. package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
  57. package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
  58. package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
  59. package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
  60. package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
  61. package/docs/research/RESEARCH_INDEX.md +439 -0
  62. package/docs/research/RESEARCH_SOURCES.md +440 -0
  63. package/docs/research/RESEARCH_SUMMARY.txt +275 -0
  64. package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
  65. package/package.json +2 -2
  66. package/scripts/lib/adaptive-model.sh +427 -0
  67. package/scripts/lib/adaptive-timeout.sh +316 -0
  68. package/scripts/lib/audit-trail.sh +309 -0
  69. package/scripts/lib/auto-recovery.sh +471 -0
  70. package/scripts/lib/bandit-selector.sh +431 -0
  71. package/scripts/lib/bootstrap.sh +104 -2
  72. package/scripts/lib/causal-graph.sh +455 -0
  73. package/scripts/lib/compat.sh +126 -0
  74. package/scripts/lib/compound-audit.sh +337 -0
  75. package/scripts/lib/constitutional.sh +454 -0
  76. package/scripts/lib/context-budget.sh +359 -0
  77. package/scripts/lib/convergence.sh +594 -0
  78. package/scripts/lib/cost-optimizer.sh +634 -0
  79. package/scripts/lib/daemon-adaptive.sh +14 -2
  80. package/scripts/lib/daemon-dispatch.sh +106 -17
  81. package/scripts/lib/daemon-failure.sh +34 -4
  82. package/scripts/lib/daemon-patrol.sh +25 -4
  83. package/scripts/lib/daemon-poll-github.sh +361 -0
  84. package/scripts/lib/daemon-poll-health.sh +299 -0
  85. package/scripts/lib/daemon-poll.sh +27 -611
  86. package/scripts/lib/daemon-state.sh +119 -66
  87. package/scripts/lib/daemon-triage.sh +10 -0
  88. package/scripts/lib/dod-scorecard.sh +442 -0
  89. package/scripts/lib/error-actionability.sh +300 -0
  90. package/scripts/lib/formal-spec.sh +461 -0
  91. package/scripts/lib/helpers.sh +180 -5
  92. package/scripts/lib/intent-analysis.sh +409 -0
  93. package/scripts/lib/loop-convergence.sh +350 -0
  94. package/scripts/lib/loop-iteration.sh +682 -0
  95. package/scripts/lib/loop-progress.sh +48 -0
  96. package/scripts/lib/loop-restart.sh +185 -0
  97. package/scripts/lib/memory-effectiveness.sh +506 -0
  98. package/scripts/lib/mutation-executor.sh +352 -0
  99. package/scripts/lib/outcome-feedback.sh +521 -0
  100. package/scripts/lib/pipeline-cli.sh +336 -0
  101. package/scripts/lib/pipeline-commands.sh +1216 -0
  102. package/scripts/lib/pipeline-detection.sh +101 -3
  103. package/scripts/lib/pipeline-execution.sh +897 -0
  104. package/scripts/lib/pipeline-github.sh +28 -3
  105. package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
  106. package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
  107. package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
  108. package/scripts/lib/pipeline-intelligence.sh +104 -1138
  109. package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
  110. package/scripts/lib/pipeline-quality-checks.sh +17 -711
  111. package/scripts/lib/pipeline-quality-gates.sh +563 -0
  112. package/scripts/lib/pipeline-stages-build.sh +730 -0
  113. package/scripts/lib/pipeline-stages-delivery.sh +965 -0
  114. package/scripts/lib/pipeline-stages-intake.sh +1133 -0
  115. package/scripts/lib/pipeline-stages-monitor.sh +407 -0
  116. package/scripts/lib/pipeline-stages-review.sh +1022 -0
  117. package/scripts/lib/pipeline-stages.sh +161 -2901
  118. package/scripts/lib/pipeline-state.sh +36 -5
  119. package/scripts/lib/pipeline-util.sh +487 -0
  120. package/scripts/lib/policy-learner.sh +438 -0
  121. package/scripts/lib/process-reward.sh +493 -0
  122. package/scripts/lib/project-detect.sh +649 -0
  123. package/scripts/lib/quality-profile.sh +334 -0
  124. package/scripts/lib/recruit-commands.sh +885 -0
  125. package/scripts/lib/recruit-learning.sh +739 -0
  126. package/scripts/lib/recruit-roles.sh +648 -0
  127. package/scripts/lib/reward-aggregator.sh +458 -0
  128. package/scripts/lib/rl-optimizer.sh +362 -0
  129. package/scripts/lib/root-cause.sh +427 -0
  130. package/scripts/lib/scope-enforcement.sh +445 -0
  131. package/scripts/lib/session-restart.sh +493 -0
  132. package/scripts/lib/skill-memory.sh +300 -0
  133. package/scripts/lib/skill-registry.sh +775 -0
  134. package/scripts/lib/spec-driven.sh +476 -0
  135. package/scripts/lib/test-helpers.sh +18 -7
  136. package/scripts/lib/test-holdout.sh +429 -0
  137. package/scripts/lib/test-optimizer.sh +511 -0
  138. package/scripts/shipwright-file-suggest.sh +45 -0
  139. package/scripts/skills/adversarial-quality.md +61 -0
  140. package/scripts/skills/api-design.md +44 -0
  141. package/scripts/skills/architecture-design.md +50 -0
  142. package/scripts/skills/brainstorming.md +43 -0
  143. package/scripts/skills/data-pipeline.md +44 -0
  144. package/scripts/skills/deploy-safety.md +64 -0
  145. package/scripts/skills/documentation.md +38 -0
  146. package/scripts/skills/frontend-design.md +45 -0
  147. package/scripts/skills/generated/.gitkeep +0 -0
  148. package/scripts/skills/generated/_refinements/.gitkeep +0 -0
  149. package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
  150. package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
  151. package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
  152. package/scripts/skills/generated/cli-version-management.md +29 -0
  153. package/scripts/skills/generated/collection-system-validation.md +99 -0
  154. package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
  155. package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
  156. package/scripts/skills/generated/test-parallelization-detection.md +65 -0
  157. package/scripts/skills/observability.md +79 -0
  158. package/scripts/skills/performance.md +48 -0
  159. package/scripts/skills/pr-quality.md +49 -0
  160. package/scripts/skills/product-thinking.md +43 -0
  161. package/scripts/skills/security-audit.md +49 -0
  162. package/scripts/skills/systematic-debugging.md +40 -0
  163. package/scripts/skills/testing-strategy.md +47 -0
  164. package/scripts/skills/two-stage-review.md +52 -0
  165. package/scripts/skills/validation-thoroughness.md +55 -0
  166. package/scripts/sw +9 -3
  167. package/scripts/sw-activity.sh +9 -8
  168. package/scripts/sw-adaptive.sh +8 -7
  169. package/scripts/sw-adversarial.sh +2 -1
  170. package/scripts/sw-architecture-enforcer.sh +3 -1
  171. package/scripts/sw-auth.sh +12 -2
  172. package/scripts/sw-autonomous.sh +5 -1
  173. package/scripts/sw-changelog.sh +4 -1
  174. package/scripts/sw-checkpoint.sh +2 -1
  175. package/scripts/sw-ci.sh +15 -6
  176. package/scripts/sw-cleanup.sh +4 -26
  177. package/scripts/sw-code-review.sh +45 -20
  178. package/scripts/sw-connect.sh +2 -1
  179. package/scripts/sw-context.sh +2 -1
  180. package/scripts/sw-cost.sh +107 -5
  181. package/scripts/sw-daemon.sh +71 -11
  182. package/scripts/sw-dashboard.sh +3 -1
  183. package/scripts/sw-db.sh +71 -20
  184. package/scripts/sw-decide.sh +8 -2
  185. package/scripts/sw-decompose.sh +360 -17
  186. package/scripts/sw-deps.sh +4 -1
  187. package/scripts/sw-developer-simulation.sh +4 -1
  188. package/scripts/sw-discovery.sh +378 -5
  189. package/scripts/sw-doc-fleet.sh +4 -1
  190. package/scripts/sw-docs-agent.sh +3 -1
  191. package/scripts/sw-docs.sh +2 -1
  192. package/scripts/sw-doctor.sh +453 -2
  193. package/scripts/sw-dora.sh +4 -1
  194. package/scripts/sw-durable.sh +12 -7
  195. package/scripts/sw-e2e-orchestrator.sh +17 -16
  196. package/scripts/sw-eventbus.sh +13 -4
  197. package/scripts/sw-evidence.sh +364 -12
  198. package/scripts/sw-feedback.sh +550 -9
  199. package/scripts/sw-fix.sh +20 -1
  200. package/scripts/sw-fleet-discover.sh +6 -2
  201. package/scripts/sw-fleet-viz.sh +9 -4
  202. package/scripts/sw-fleet.sh +5 -1
  203. package/scripts/sw-github-app.sh +18 -4
  204. package/scripts/sw-github-checks.sh +3 -2
  205. package/scripts/sw-github-deploy.sh +3 -2
  206. package/scripts/sw-github-graphql.sh +18 -7
  207. package/scripts/sw-guild.sh +5 -1
  208. package/scripts/sw-heartbeat.sh +5 -30
  209. package/scripts/sw-hello.sh +67 -0
  210. package/scripts/sw-hygiene.sh +10 -3
  211. package/scripts/sw-incident.sh +273 -5
  212. package/scripts/sw-init.sh +18 -2
  213. package/scripts/sw-instrument.sh +10 -2
  214. package/scripts/sw-intelligence.sh +44 -7
  215. package/scripts/sw-jira.sh +5 -1
  216. package/scripts/sw-launchd.sh +2 -1
  217. package/scripts/sw-linear.sh +4 -1
  218. package/scripts/sw-logs.sh +4 -1
  219. package/scripts/sw-loop.sh +436 -1076
  220. package/scripts/sw-memory.sh +357 -3
  221. package/scripts/sw-mission-control.sh +6 -1
  222. package/scripts/sw-model-router.sh +483 -27
  223. package/scripts/sw-otel.sh +15 -4
  224. package/scripts/sw-oversight.sh +14 -5
  225. package/scripts/sw-patrol-meta.sh +334 -0
  226. package/scripts/sw-pipeline-composer.sh +7 -1
  227. package/scripts/sw-pipeline-vitals.sh +12 -6
  228. package/scripts/sw-pipeline.sh +54 -2653
  229. package/scripts/sw-pm.sh +16 -8
  230. package/scripts/sw-pr-lifecycle.sh +2 -1
  231. package/scripts/sw-predictive.sh +17 -5
  232. package/scripts/sw-prep.sh +185 -2
  233. package/scripts/sw-ps.sh +5 -25
  234. package/scripts/sw-public-dashboard.sh +17 -4
  235. package/scripts/sw-quality.sh +14 -6
  236. package/scripts/sw-reaper.sh +8 -25
  237. package/scripts/sw-recruit.sh +156 -2303
  238. package/scripts/sw-regression.sh +19 -12
  239. package/scripts/sw-release-manager.sh +3 -1
  240. package/scripts/sw-release.sh +4 -1
  241. package/scripts/sw-remote.sh +3 -1
  242. package/scripts/sw-replay.sh +7 -1
  243. package/scripts/sw-retro.sh +158 -1
  244. package/scripts/sw-review-rerun.sh +3 -1
  245. package/scripts/sw-scale.sh +14 -5
  246. package/scripts/sw-security-audit.sh +6 -1
  247. package/scripts/sw-self-optimize.sh +173 -6
  248. package/scripts/sw-session.sh +9 -3
  249. package/scripts/sw-setup.sh +3 -1
  250. package/scripts/sw-stall-detector.sh +406 -0
  251. package/scripts/sw-standup.sh +15 -7
  252. package/scripts/sw-status.sh +3 -1
  253. package/scripts/sw-strategic.sh +14 -6
  254. package/scripts/sw-stream.sh +13 -4
  255. package/scripts/sw-swarm.sh +20 -7
  256. package/scripts/sw-team-stages.sh +13 -6
  257. package/scripts/sw-templates.sh +7 -31
  258. package/scripts/sw-testgen.sh +17 -6
  259. package/scripts/sw-tmux-pipeline.sh +4 -1
  260. package/scripts/sw-tmux-role-color.sh +2 -0
  261. package/scripts/sw-tmux-status.sh +1 -1
  262. package/scripts/sw-tmux.sh +37 -1
  263. package/scripts/sw-trace.sh +3 -1
  264. package/scripts/sw-tracker-github.sh +3 -0
  265. package/scripts/sw-tracker-jira.sh +3 -0
  266. package/scripts/sw-tracker-linear.sh +3 -0
  267. package/scripts/sw-tracker.sh +3 -1
  268. package/scripts/sw-triage.sh +3 -2
  269. package/scripts/sw-upgrade.sh +3 -1
  270. package/scripts/sw-ux.sh +5 -2
  271. package/scripts/sw-webhook.sh +5 -2
  272. package/scripts/sw-widgets.sh +9 -4
  273. package/scripts/sw-worktree.sh +15 -3
  274. package/scripts/test-skill-injection.sh +1233 -0
  275. package/templates/pipelines/autonomous.json +27 -3
  276. package/templates/pipelines/cost-aware.json +34 -8
  277. package/templates/pipelines/deployed.json +12 -0
  278. package/templates/pipelines/enterprise.json +12 -0
  279. package/templates/pipelines/fast.json +6 -0
  280. package/templates/pipelines/full.json +27 -3
  281. package/templates/pipelines/hotfix.json +6 -0
  282. package/templates/pipelines/standard.json +12 -0
  283. package/templates/pipelines/tdd.json +12 -0
@@ -0,0 +1,563 @@
1
+ # pipeline-quality-gates.sh — Quality gate checks for pipeline-quality-checks.sh
2
+ # Source from pipeline-quality-checks.sh. Requires ARTIFACTS_DIR, SCRIPT_DIR.
3
+ [[ -n "${_PIPELINE_QUALITY_GATES_LOADED:-}" ]] && return 0
4
+ _PIPELINE_QUALITY_GATES_LOADED=1
5
+
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
+ quality_check_security() {
15
+ info "Security audit..."
16
+ local audit_log="$ARTIFACTS_DIR/security-audit.log"
17
+ local audit_exit=0
18
+ local tool_found=false
19
+
20
+ # Try npm audit
21
+ if [[ -f "package.json" ]] && command -v npm >/dev/null 2>&1; then
22
+ tool_found=true
23
+ npm audit --production 2>&1 | tee "$audit_log" || audit_exit=$?
24
+ # Try pip-audit
25
+ elif [[ -f "requirements.txt" || -f "pyproject.toml" ]] && command -v pip-audit >/dev/null 2>&1; then
26
+ tool_found=true
27
+ pip-audit 2>&1 | tee "$audit_log" || audit_exit=$?
28
+ # Try cargo audit
29
+ elif [[ -f "Cargo.toml" ]] && command -v cargo-audit >/dev/null 2>&1; then
30
+ tool_found=true
31
+ cargo audit 2>&1 | tee "$audit_log" || audit_exit=$?
32
+ fi
33
+
34
+ if [[ "$tool_found" != "true" ]]; then
35
+ info "No security audit tool found — skipping"
36
+ echo "No audit tool available" > "$audit_log"
37
+ return 0
38
+ fi
39
+
40
+ # Parse results for critical/high severity
41
+ local critical_count high_count
42
+ critical_count=$(grep -ciE 'critical' "$audit_log" 2>/dev/null || true)
43
+ critical_count="${critical_count:-0}"
44
+ high_count=$(grep -ciE 'high' "$audit_log" 2>/dev/null || true)
45
+ high_count="${high_count:-0}"
46
+
47
+ emit_event "quality.security" \
48
+ "issue=${ISSUE_NUMBER:-0}" \
49
+ "critical=$critical_count" \
50
+ "high=$high_count"
51
+
52
+ if [[ "$critical_count" -gt 0 ]]; then
53
+ warn "Security audit: ${critical_count} critical, ${high_count} high"
54
+ return 1
55
+ fi
56
+
57
+ success "Security audit: clean"
58
+ return 0
59
+ }
60
+
61
+ quality_check_bundle_size() {
62
+ info "Bundle size check..."
63
+ local metrics_log="$ARTIFACTS_DIR/bundle-metrics.log"
64
+ local bundle_size=0
65
+ local bundle_dir=""
66
+
67
+ # Find build output directory — check config files first, then common dirs
68
+ # Parse tsconfig.json outDir
69
+ if [[ -z "$bundle_dir" && -f "tsconfig.json" ]]; then
70
+ local ts_out
71
+ ts_out=$(jq -r '.compilerOptions.outDir // empty' tsconfig.json 2>/dev/null || true)
72
+ [[ -n "$ts_out" && -d "$ts_out" ]] && bundle_dir="$ts_out"
73
+ fi
74
+ # Parse package.json build script for output hints
75
+ if [[ -z "$bundle_dir" && -f "package.json" ]]; then
76
+ local build_script
77
+ build_script=$(jq -r '.scripts.build // ""' package.json 2>/dev/null || true)
78
+ if [[ -n "$build_script" ]]; then
79
+ # Check for common output flags: --outDir, -o, --out-dir
80
+ local parsed_out
81
+ parsed_out=$(echo "$build_script" | grep -oE '(--outDir|--out-dir|-o)\s+[^ ]+' 2>/dev/null | awk '{print $NF}' | head -1 || true)
82
+ [[ -n "$parsed_out" && -d "$parsed_out" ]] && bundle_dir="$parsed_out"
83
+ fi
84
+ fi
85
+ # Fallback: check common directories
86
+ if [[ -z "$bundle_dir" ]]; then
87
+ for dir in dist build out .next target; do
88
+ if [[ -d "$dir" ]]; then
89
+ bundle_dir="$dir"
90
+ break
91
+ fi
92
+ done
93
+ fi
94
+
95
+ if [[ -z "$bundle_dir" ]]; then
96
+ info "No build output directory found — skipping bundle check"
97
+ echo "No build directory" > "$metrics_log"
98
+ return 0
99
+ fi
100
+
101
+ bundle_size=$(du -sk "$bundle_dir" 2>/dev/null | cut -f1 || echo "0")
102
+ local bundle_size_human
103
+ bundle_size_human=$(du -sh "$bundle_dir" 2>/dev/null | cut -f1 || echo "unknown")
104
+
105
+ echo "Bundle directory: $bundle_dir" > "$metrics_log"
106
+ echo "Size: ${bundle_size}KB (${bundle_size_human})" >> "$metrics_log"
107
+
108
+ emit_event "quality.bundle" \
109
+ "issue=${ISSUE_NUMBER:-0}" \
110
+ "size_kb=$bundle_size" \
111
+ "directory=$bundle_dir"
112
+
113
+ # Adaptive bundle size check: statistical deviation from historical mean
114
+ local repo_hash_bundle
115
+ repo_hash_bundle=$(echo -n "$PROJECT_ROOT" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
116
+ local bundle_baselines_dir="${HOME}/.shipwright/baselines/${repo_hash_bundle}"
117
+ local bundle_history_file="${bundle_baselines_dir}/bundle-history.json"
118
+
119
+ local bundle_history="[]"
120
+ if [[ -f "$bundle_history_file" ]]; then
121
+ bundle_history=$(jq '.sizes // []' "$bundle_history_file" 2>/dev/null || echo "[]")
122
+ fi
123
+
124
+ local bundle_hist_count
125
+ bundle_hist_count=$(echo "$bundle_history" | jq 'length' 2>/dev/null || echo "0")
126
+
127
+ if [[ "$bundle_hist_count" -ge 3 ]]; then
128
+ # Statistical check: alert on growth > 2σ from historical mean
129
+ local mean_size stddev_size
130
+ mean_size=$(echo "$bundle_history" | jq 'add / length' 2>/dev/null || echo "0")
131
+ stddev_size=$(echo "$bundle_history" | jq '
132
+ (add / length) as $mean |
133
+ (map(. - $mean | . * .) | add / length | sqrt)
134
+ ' 2>/dev/null || echo "0")
135
+
136
+ # Adaptive tolerance: small repos (<1MB mean) get wider tolerance (3σ), large repos get 2σ
137
+ local sigma_mult
138
+ sigma_mult=$(awk -v mean="$mean_size" 'BEGIN{ print (mean < 1024 ? 3 : 2) }')
139
+ local adaptive_max
140
+ adaptive_max=$(awk -v mean="$mean_size" -v sd="$stddev_size" -v mult="$sigma_mult" \
141
+ 'BEGIN{ t = mean + mult*sd; min_t = mean * 1.1; printf "%.0f", (t > min_t ? t : min_t) }')
142
+
143
+ echo "History: ${bundle_hist_count} runs | Mean: ${mean_size}KB | StdDev: ${stddev_size}KB | Max: ${adaptive_max}KB (${sigma_mult}σ)" >> "$metrics_log"
144
+
145
+ if [[ "$bundle_size" -gt "$adaptive_max" ]] 2>/dev/null; then
146
+ local growth_pct
147
+ growth_pct=$(awk -v cur="$bundle_size" -v mean="$mean_size" 'BEGIN{printf "%d", ((cur - mean) / mean) * 100}')
148
+ warn "Bundle size ${growth_pct}% above average (${mean_size}KB → ${bundle_size}KB, ${sigma_mult}σ threshold: ${adaptive_max}KB)"
149
+ return 1
150
+ fi
151
+ else
152
+ # Fallback: legacy memory baseline (not enough history for statistical check)
153
+ local bundle_growth_limit
154
+ bundle_growth_limit=$(_config_get_int "quality.bundle_growth_legacy_pct" 20 2>/dev/null || echo 20)
155
+ local baseline_size=""
156
+ if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
157
+ baseline_size=$(bash "$SCRIPT_DIR/sw-memory.sh" get "bundle_size_kb" 2>/dev/null) || true
158
+ fi
159
+ if [[ -n "$baseline_size" && "$baseline_size" -gt 0 ]] 2>/dev/null; then
160
+ local growth_pct
161
+ growth_pct=$(awk -v cur="$bundle_size" -v base="$baseline_size" 'BEGIN{printf "%d", ((cur - base) / base) * 100}')
162
+ echo "Baseline: ${baseline_size}KB | Growth: ${growth_pct}%" >> "$metrics_log"
163
+ if [[ "$growth_pct" -gt "$bundle_growth_limit" ]]; then
164
+ warn "Bundle size grew ${growth_pct}% (${baseline_size}KB → ${bundle_size}KB)"
165
+ return 1
166
+ fi
167
+ fi
168
+ fi
169
+
170
+ # Append current size to rolling history (keep last 10)
171
+ mkdir -p "$bundle_baselines_dir"
172
+ local updated_bundle_hist
173
+ updated_bundle_hist=$(echo "$bundle_history" | jq --arg sz "$bundle_size" '
174
+ . + [($sz | tonumber)] | .[-10:]
175
+ ' 2>/dev/null || echo "[$bundle_size]")
176
+ local tmp_bundle_hist
177
+ tmp_bundle_hist=$(mktemp "${bundle_baselines_dir}/bundle-history.json.XXXXXX")
178
+ jq -n --argjson sizes "$updated_bundle_hist" --arg updated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
179
+ '{sizes: $sizes, updated: $updated}' > "$tmp_bundle_hist" 2>/dev/null
180
+ mv "$tmp_bundle_hist" "$bundle_history_file" 2>/dev/null || true
181
+
182
+ # Intelligence: identify top dependency bloaters
183
+ if type intelligence_search_memory >/dev/null 2>&1 && [[ -f "package.json" ]] && command -v jq >/dev/null 2>&1; then
184
+ local dep_sizes=""
185
+ local deps
186
+ deps=$(jq -r '.dependencies // {} | keys[]' package.json 2>/dev/null || true)
187
+ if [[ -n "$deps" ]]; then
188
+ while IFS= read -r dep; do
189
+ [[ -z "$dep" ]] && continue
190
+ local dep_dir="node_modules/${dep}"
191
+ if [[ -d "$dep_dir" ]]; then
192
+ local dep_size
193
+ dep_size=$(du -sk "$dep_dir" 2>/dev/null | cut -f1 || echo "0")
194
+ dep_sizes="${dep_sizes}${dep_size} ${dep}
195
+ "
196
+ fi
197
+ done <<< "$deps"
198
+ if [[ -n "$dep_sizes" ]]; then
199
+ local top_bloaters
200
+ top_bloaters=$(echo "$dep_sizes" | sort -rn | head -3)
201
+ if [[ -n "$top_bloaters" ]]; then
202
+ echo "" >> "$metrics_log"
203
+ echo "Top 3 dependency sizes:" >> "$metrics_log"
204
+ echo "$top_bloaters" | while IFS=' ' read -r sz nm; do
205
+ [[ -z "$nm" ]] && continue
206
+ echo " ${nm}: ${sz}KB" >> "$metrics_log"
207
+ done
208
+ info "Top bloaters: $(echo "$top_bloaters" | head -1 | awk '{print $2 ": " $1 "KB"}')"
209
+ fi
210
+ fi
211
+ fi
212
+ fi
213
+
214
+ info "Bundle size: ${bundle_size_human}${bundle_hist_count:+ (${bundle_hist_count} historical samples)}"
215
+ return 0
216
+ }
217
+
218
+ quality_check_perf_regression() {
219
+ info "Performance regression check..."
220
+ local metrics_log="$ARTIFACTS_DIR/perf-metrics.log"
221
+ local test_log="$ARTIFACTS_DIR/test-results.log"
222
+
223
+ if [[ ! -f "$test_log" ]]; then
224
+ info "No test results — skipping perf check"
225
+ echo "No test results available" > "$metrics_log"
226
+ return 0
227
+ fi
228
+
229
+ # Extract test suite duration — multi-framework patterns
230
+ local duration_ms=""
231
+ # Jest/Vitest: "Time: 12.34 s" or "Duration 12.34s"
232
+ duration_ms=$(grep -oE 'Time:\s*[0-9.]+\s*s' "$test_log" 2>/dev/null | grep -oE '[0-9.]+' | tail -1 || true)
233
+ [[ -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)
234
+ # pytest: "passed in 12.34s" or "====== 5 passed in 12.34 seconds ======"
235
+ [[ -z "$duration_ms" ]] && duration_ms=$(grep -oE 'passed in [0-9.]+s' "$test_log" 2>/dev/null | grep -oE '[0-9.]+' | tail -1 || true)
236
+ # Go test: "ok pkg 12.345s"
237
+ [[ -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)
238
+ # Cargo test: "test result: ok. ... finished in 12.34s"
239
+ [[ -z "$duration_ms" ]] && duration_ms=$(grep -oE 'finished in [0-9.]+s' "$test_log" 2>/dev/null | grep -oE '[0-9.]+' | tail -1 || true)
240
+ # Generic: "12.34 seconds" or "12.34s"
241
+ [[ -z "$duration_ms" ]] && duration_ms=$(grep -oE '[0-9.]+ ?s(econds?)?' "$test_log" 2>/dev/null | grep -oE '[0-9.]+' | tail -1 || true)
242
+
243
+ # Claude fallback: parse test output when no pattern matches
244
+ if [[ -z "$duration_ms" ]]; then
245
+ local intel_enabled="false"
246
+ local daemon_cfg="${PROJECT_ROOT}/.claude/daemon-config.json"
247
+ if [[ -f "$daemon_cfg" ]]; then
248
+ intel_enabled=$(jq -r '.intelligence.enabled // false' "$daemon_cfg" 2>/dev/null || echo "false")
249
+ fi
250
+ if [[ "$intel_enabled" == "true" ]] && command -v claude >/dev/null 2>&1; then
251
+ local tail_output
252
+ tail_output=$(tail -30 "$test_log" 2>/dev/null || true)
253
+ if [[ -n "$tail_output" ]]; then
254
+ 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.
255
+
256
+ $tail_output" < /dev/null 2>/dev/null | grep -oE '^[0-9.]+$' | head -1 || true)
257
+ [[ "$duration_ms" == "NONE" ]] && duration_ms=""
258
+ fi
259
+ fi
260
+ fi
261
+
262
+ if [[ -z "$duration_ms" ]]; then
263
+ info "Could not extract test duration — skipping perf check"
264
+ echo "Duration not parseable" > "$metrics_log"
265
+ return 0
266
+ fi
267
+
268
+ echo "Test duration: ${duration_ms}s" > "$metrics_log"
269
+
270
+ emit_event "quality.perf" \
271
+ "issue=${ISSUE_NUMBER:-0}" \
272
+ "duration_s=$duration_ms"
273
+
274
+ # Adaptive performance check: 2σ from rolling 10-run average
275
+ local repo_hash_perf
276
+ repo_hash_perf=$(echo -n "$PROJECT_ROOT" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
277
+ local perf_baselines_dir="${HOME}/.shipwright/baselines/${repo_hash_perf}"
278
+ local perf_history_file="${perf_baselines_dir}/perf-history.json"
279
+
280
+ # Read historical durations (rolling window of last 10 runs)
281
+ local history_json="[]"
282
+ if [[ -f "$perf_history_file" ]]; then
283
+ history_json=$(jq '.durations // []' "$perf_history_file" 2>/dev/null || echo "[]")
284
+ fi
285
+
286
+ local history_count
287
+ history_count=$(echo "$history_json" | jq 'length' 2>/dev/null || echo "0")
288
+
289
+ if [[ "$history_count" -ge 3 ]]; then
290
+ # Calculate mean and standard deviation from history
291
+ local mean_dur stddev_dur
292
+ mean_dur=$(echo "$history_json" | jq 'add / length' 2>/dev/null || echo "0")
293
+ stddev_dur=$(echo "$history_json" | jq '
294
+ (add / length) as $mean |
295
+ (map(. - $mean | . * .) | add / length | sqrt)
296
+ ' 2>/dev/null || echo "0")
297
+
298
+ # Threshold: mean + 2σ (but at least 10% above mean)
299
+ local adaptive_threshold
300
+ adaptive_threshold=$(awk -v mean="$mean_dur" -v sd="$stddev_dur" \
301
+ 'BEGIN{ t = mean + 2*sd; min_t = mean * 1.1; printf "%.2f", (t > min_t ? t : min_t) }')
302
+
303
+ echo "History: ${history_count} runs | Mean: ${mean_dur}s | StdDev: ${stddev_dur}s | Threshold: ${adaptive_threshold}s" >> "$metrics_log"
304
+
305
+ if awk -v cur="$duration_ms" -v thresh="$adaptive_threshold" 'BEGIN{exit !(cur > thresh)}' 2>/dev/null; then
306
+ local slowdown_pct
307
+ slowdown_pct=$(awk -v cur="$duration_ms" -v mean="$mean_dur" 'BEGIN{printf "%d", ((cur - mean) / mean) * 100}')
308
+ warn "Tests ${slowdown_pct}% slower than rolling average (${mean_dur}s → ${duration_ms}s, threshold: ${adaptive_threshold}s)"
309
+ return 1
310
+ fi
311
+ else
312
+ # Fallback: legacy memory baseline (not enough history for statistical check)
313
+ local perf_regression_limit
314
+ perf_regression_limit=$(_config_get_int "quality.perf_regression_legacy_pct" 30 2>/dev/null || echo 30)
315
+ local baseline_dur=""
316
+ if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
317
+ baseline_dur=$(bash "$SCRIPT_DIR/sw-memory.sh" get "test_duration_s" 2>/dev/null) || true
318
+ fi
319
+ if [[ -n "$baseline_dur" ]] && awk -v cur="$duration_ms" -v base="$baseline_dur" 'BEGIN{exit !(base > 0)}' 2>/dev/null; then
320
+ local slowdown_pct
321
+ slowdown_pct=$(awk -v cur="$duration_ms" -v base="$baseline_dur" 'BEGIN{printf "%d", ((cur - base) / base) * 100}')
322
+ echo "Baseline: ${baseline_dur}s | Slowdown: ${slowdown_pct}%" >> "$metrics_log"
323
+ if [[ "$slowdown_pct" -gt "$perf_regression_limit" ]]; then
324
+ warn "Tests ${slowdown_pct}% slower (${baseline_dur}s → ${duration_ms}s)"
325
+ return 1
326
+ fi
327
+ fi
328
+ fi
329
+
330
+ # Append current duration to rolling history (keep last 10)
331
+ mkdir -p "$perf_baselines_dir"
332
+ local updated_history
333
+ updated_history=$(echo "$history_json" | jq --arg dur "$duration_ms" '
334
+ . + [($dur | tonumber)] | .[-10:]
335
+ ' 2>/dev/null || echo "[$duration_ms]")
336
+ local tmp_perf_hist
337
+ tmp_perf_hist=$(mktemp "${perf_baselines_dir}/perf-history.json.XXXXXX")
338
+ jq -n --argjson durations "$updated_history" --arg updated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
339
+ '{durations: $durations, updated: $updated}' > "$tmp_perf_hist" 2>/dev/null
340
+ mv "$tmp_perf_hist" "$perf_history_file" 2>/dev/null || true
341
+
342
+ info "Test duration: ${duration_ms}s${history_count:+ (${history_count} historical samples)}"
343
+ return 0
344
+ }
345
+
346
+ quality_check_api_compat() {
347
+ info "API compatibility check..."
348
+ local compat_log="$ARTIFACTS_DIR/api-compat.log"
349
+
350
+ # Look for OpenAPI/Swagger specs — search beyond hardcoded paths
351
+ local spec_file=""
352
+ for candidate in openapi.json openapi.yaml swagger.json swagger.yaml api/openapi.json docs/openapi.yaml; do
353
+ if [[ -f "$candidate" ]]; then
354
+ spec_file="$candidate"
355
+ break
356
+ fi
357
+ done
358
+ # Broader search if nothing found at common paths
359
+ if [[ -z "$spec_file" ]]; then
360
+ 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)
361
+ fi
362
+
363
+ if [[ -z "$spec_file" ]]; then
364
+ info "No OpenAPI/Swagger spec found — skipping API compat check"
365
+ echo "No API spec found" > "$compat_log"
366
+ return 0
367
+ fi
368
+
369
+ # Check if spec was modified in this branch
370
+ local spec_changed
371
+ spec_changed=$(git diff --name-only "${BASE_BRANCH}...HEAD" 2>/dev/null | grep -c "$(basename "$spec_file")" || true)
372
+ spec_changed="${spec_changed:-0}"
373
+
374
+ if [[ "$spec_changed" -eq 0 ]]; then
375
+ info "API spec unchanged"
376
+ echo "Spec unchanged" > "$compat_log"
377
+ return 0
378
+ fi
379
+
380
+ # Diff the spec against base branch
381
+ local old_spec new_spec
382
+ old_spec=$(git show "${BASE_BRANCH}:${spec_file}" 2>/dev/null || true)
383
+ new_spec=$(cat "$spec_file" 2>/dev/null || true)
384
+
385
+ if [[ -z "$old_spec" ]]; then
386
+ info "New API spec — no baseline to compare"
387
+ echo "New spec, no baseline" > "$compat_log"
388
+ return 0
389
+ fi
390
+
391
+ # Check for breaking changes: removed endpoints, changed methods
392
+ local removed_endpoints=""
393
+ if command -v jq >/dev/null 2>&1 && [[ "$spec_file" == *.json ]]; then
394
+ local old_paths new_paths
395
+ old_paths=$(echo "$old_spec" | jq -r '.paths | keys[]' 2>/dev/null | sort || true)
396
+ new_paths=$(jq -r '.paths | keys[]' "$spec_file" 2>/dev/null | sort || true)
397
+ removed_endpoints=$(comm -23 <(echo "$old_paths") <(echo "$new_paths") 2>/dev/null || true)
398
+ fi
399
+
400
+ # Enhanced schema diff: parameter changes, response schema, auth changes
401
+ local param_changes="" schema_changes=""
402
+ if command -v jq >/dev/null 2>&1 && [[ "$spec_file" == *.json ]]; then
403
+ # Detect parameter changes on existing endpoints
404
+ local common_paths
405
+ 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)
406
+ if [[ -n "$common_paths" ]]; then
407
+ while IFS= read -r path; do
408
+ [[ -z "$path" ]] && continue
409
+ local old_params new_params
410
+ old_params=$(echo "$old_spec" | jq -r --arg p "$path" '.paths[$p] | to_entries[] | .value.parameters // [] | .[].name' 2>/dev/null | sort || true)
411
+ new_params=$(jq -r --arg p "$path" '.paths[$p] | to_entries[] | .value.parameters // [] | .[].name' "$spec_file" 2>/dev/null | sort || true)
412
+ local removed_params
413
+ removed_params=$(comm -23 <(echo "$old_params") <(echo "$new_params") 2>/dev/null || true)
414
+ [[ -n "$removed_params" ]] && param_changes="${param_changes}${path}: removed params: ${removed_params}
415
+ "
416
+ done <<< "$common_paths"
417
+ fi
418
+ fi
419
+
420
+ # Intelligence: semantic API diff for complex changes
421
+ local semantic_diff=""
422
+ if type intelligence_search_memory >/dev/null 2>&1 && command -v claude >/dev/null 2>&1; then
423
+ local spec_git_diff
424
+ spec_git_diff=$(git diff "${BASE_BRANCH}...HEAD" -- "$spec_file" 2>/dev/null | head -200 || true)
425
+ if [[ -n "$spec_git_diff" ]]; then
426
+ 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.
427
+
428
+ ${spec_git_diff}" --model "$(_smart_model validation haiku)" < /dev/null 2>/dev/null || true)
429
+ fi
430
+ fi
431
+
432
+ {
433
+ echo "Spec: $spec_file"
434
+ echo "Changed: yes"
435
+ if [[ -n "$removed_endpoints" ]]; then
436
+ echo "BREAKING — Removed endpoints:"
437
+ echo "$removed_endpoints"
438
+ fi
439
+ if [[ -n "$param_changes" ]]; then
440
+ echo "BREAKING — Parameter changes:"
441
+ echo "$param_changes"
442
+ fi
443
+ if [[ -n "$semantic_diff" ]]; then
444
+ echo ""
445
+ echo "Semantic analysis:"
446
+ echo "$semantic_diff"
447
+ fi
448
+ if [[ -z "$removed_endpoints" && -z "$param_changes" ]]; then
449
+ echo "No breaking changes detected"
450
+ fi
451
+ } > "$compat_log"
452
+
453
+ if [[ -n "$removed_endpoints" || -n "$param_changes" ]]; then
454
+ local issue_count=0
455
+ [[ -n "$removed_endpoints" ]] && issue_count=$((issue_count + $(echo "$removed_endpoints" | wc -l | xargs)))
456
+ [[ -n "$param_changes" ]] && issue_count=$((issue_count + $(echo "$param_changes" | grep -c '.' 2>/dev/null || true)))
457
+ warn "API breaking changes: ${issue_count} issue(s) found"
458
+ return 1
459
+ fi
460
+
461
+ success "API compatibility: no breaking changes"
462
+ return 0
463
+ }
464
+
465
+ quality_check_coverage() {
466
+ info "Coverage analysis..."
467
+ local test_log="$ARTIFACTS_DIR/test-results.log"
468
+
469
+ if [[ ! -f "$test_log" ]]; then
470
+ info "No test results — skipping coverage check"
471
+ return 0
472
+ fi
473
+
474
+ # Extract coverage percentage using shared parser
475
+ local coverage=""
476
+ coverage=$(parse_coverage_from_output "$test_log")
477
+
478
+ # Claude fallback: parse test output when no pattern matches
479
+ if [[ -z "$coverage" ]]; then
480
+ local intel_enabled_cov="false"
481
+ local daemon_cfg_cov="${PROJECT_ROOT}/.claude/daemon-config.json"
482
+ if [[ -f "$daemon_cfg_cov" ]]; then
483
+ intel_enabled_cov=$(jq -r '.intelligence.enabled // false' "$daemon_cfg_cov" 2>/dev/null || echo "false")
484
+ fi
485
+ if [[ "$intel_enabled_cov" == "true" ]] && command -v claude >/dev/null 2>&1; then
486
+ local tail_cov_output
487
+ tail_cov_output=$(tail -40 "$test_log" 2>/dev/null || true)
488
+ if [[ -n "$tail_cov_output" ]]; then
489
+ 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.
490
+
491
+ $tail_cov_output" < /dev/null 2>/dev/null | grep -oE '^[0-9.]+$' | head -1 || true)
492
+ [[ "$coverage" == "NONE" ]] && coverage=""
493
+ fi
494
+ fi
495
+ fi
496
+
497
+ if [[ -z "$coverage" ]]; then
498
+ info "Could not extract coverage — skipping"
499
+ return 0
500
+ fi
501
+
502
+ emit_event "quality.coverage" \
503
+ "issue=${ISSUE_NUMBER:-0}" \
504
+ "coverage=$coverage"
505
+
506
+ # Check against pipeline config minimum
507
+ local coverage_min
508
+ coverage_min=$(jq -r --arg id "test" '(.stages[] | select(.id == $id) | .config.coverage_min) // 0' "$PIPELINE_CONFIG" 2>/dev/null) || true
509
+ [[ -z "$coverage_min" || "$coverage_min" == "null" ]] && coverage_min=0
510
+
511
+ # Adaptive baseline: read from baselines file, enforce no-regression (>= baseline - 2%)
512
+ local repo_hash_cov
513
+ repo_hash_cov=$(echo -n "$PROJECT_ROOT" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
514
+ local baselines_dir="${HOME}/.shipwright/baselines/${repo_hash_cov}"
515
+ local coverage_baseline_file="${baselines_dir}/coverage.json"
516
+
517
+ local baseline_coverage=""
518
+ if [[ -f "$coverage_baseline_file" ]]; then
519
+ baseline_coverage=$(jq -r '.baseline // empty' "$coverage_baseline_file" 2>/dev/null) || true
520
+ fi
521
+ # Fallback: try legacy memory baseline
522
+ if [[ -z "$baseline_coverage" ]] && [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
523
+ baseline_coverage=$(bash "$SCRIPT_DIR/sw-memory.sh" get "coverage_pct" 2>/dev/null) || true
524
+ fi
525
+
526
+ local dropped=false
527
+ if [[ -n "$baseline_coverage" && "$baseline_coverage" != "0" ]] && awk -v cur="$coverage" -v base="$baseline_coverage" 'BEGIN{exit !(base > 0)}' 2>/dev/null; then
528
+ # Adaptive: allow 2% regression tolerance from baseline
529
+ local min_allowed
530
+ min_allowed=$(awk -v base="$baseline_coverage" 'BEGIN{printf "%d", base - 2}')
531
+ if awk -v cur="$coverage" -v min="$min_allowed" 'BEGIN{exit !(cur < min)}' 2>/dev/null; then
532
+ warn "Coverage regression: ${baseline_coverage}% → ${coverage}% (adaptive min: ${min_allowed}%)"
533
+ dropped=true
534
+ fi
535
+ fi
536
+
537
+ if [[ "$coverage_min" -gt 0 ]] 2>/dev/null && awk -v cov="$coverage" -v min="$coverage_min" 'BEGIN{exit !(cov < min)}' 2>/dev/null; then
538
+ warn "Coverage ${coverage}% below minimum ${coverage_min}%"
539
+ return 1
540
+ fi
541
+
542
+ if $dropped; then
543
+ return 1
544
+ fi
545
+
546
+ # Update baseline on success (first run or improvement)
547
+ if [[ -z "$baseline_coverage" ]] || awk -v cur="$coverage" -v base="$baseline_coverage" 'BEGIN{exit !(cur >= base)}' 2>/dev/null; then
548
+ mkdir -p "$baselines_dir"
549
+ local tmp_cov_baseline
550
+ tmp_cov_baseline=$(mktemp "${baselines_dir}/coverage.json.XXXXXX")
551
+ jq -n --arg baseline "$coverage" --arg updated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
552
+ '{baseline: ($baseline | tonumber), updated: $updated}' > "$tmp_cov_baseline" 2>/dev/null
553
+ mv "$tmp_cov_baseline" "$coverage_baseline_file" 2>/dev/null || true
554
+ fi
555
+
556
+ info "Coverage: ${coverage}%${baseline_coverage:+ (baseline: ${baseline_coverage}%)}"
557
+ return 0
558
+ }
559
+
560
+ # ─── Compound Quality Checks ──────────────────────────────────────────────
561
+ # Adversarial review, negative prompting, E2E validation, and DoD audit.
562
+ # Feeds findings back into a self-healing rebuild loop for automatic fixes.
563
+