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
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ dod-scorecard.sh — Machine-Verifiable Definition of Done Scorecard ║
4
+ # ║ ║
5
+ # ║ Computes automated checks for PR quality: ║
6
+ # ║ - PR size limits (configurable, default 500 lines) ║
7
+ # ║ - Test count delta (new tests added) ║
8
+ # ║ - Never-ship rule violations (pattern checks) ║
9
+ # ║ - Planned file coverage (from scope-report.json) ║
10
+ # ║ - Acceptance criteria (test evidence verification) ║
11
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
12
+
13
+ [[ -n "${_DOD_SCORECARD_LOADED:-}" ]] && return 0
14
+ _DOD_SCORECARD_LOADED=1
15
+
16
+ # Defaults (safe under set -u)
17
+ ARTIFACTS_DIR="${ARTIFACTS_DIR:-.claude/pipeline-artifacts}"
18
+ SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
19
+ PROJECT_ROOT="${PROJECT_ROOT:-$(pwd)}"
20
+ BASE_BRANCH="${BASE_BRANCH:-main}"
21
+ QUALITY_PROFILE="${QUALITY_PROFILE:-.claude/quality-profile.json}"
22
+
23
+ # ─── Helper: Safe JSON read ───────────────────────────────────────────────────
24
+ # Usage: json_get "$json_string" ".path.to.key" "default_value"
25
+ json_get() {
26
+ local json="$1"
27
+ local key="$2"
28
+ local default="${3:-}"
29
+ jq -r "$key // \"$default\"" <<< "$json" 2>/dev/null || echo "$default"
30
+ }
31
+
32
+ # ─── Helper: Array length (bash 3.2 compatible) ────────────────────────────────
33
+ # Usage: array_len "item1" "item2" "item3"
34
+ # Returns count of non-empty arguments
35
+ array_count_items() {
36
+ local count=0
37
+ for item in "$@"; do
38
+ [[ -n "$item" ]] && count=$((count + 1))
39
+ done
40
+ echo "$count"
41
+ }
42
+
43
+ # ─── Check: PR Size ──────────────────────────────────────────────────────────
44
+ # Returns JSON: {"status": "pass|fail", "value": lines, "limit": limit}
45
+ check_pr_size_score() {
46
+ local base_branch="$1"
47
+ local limit="${2:-500}"
48
+
49
+ local total_lines=0
50
+
51
+ # Try git diff stat first
52
+ if git rev-parse "$base_branch" >/dev/null 2>&1; then
53
+ local diff_stat
54
+ diff_stat=$(git diff --stat "$base_branch...HEAD" 2>/dev/null | tail -1 || true)
55
+ if [[ -n "$diff_stat" ]]; then
56
+ # Format: "N files changed, X insertions(+), Y deletions(-)"
57
+ # Extract insertions count
58
+ total_lines=$(echo "$diff_stat" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo "0")
59
+ fi
60
+ fi
61
+
62
+ local status="pass"
63
+ [[ $total_lines -gt $limit ]] && status="fail"
64
+
65
+ jq -n \
66
+ --arg status "$status" \
67
+ --argjson value "$total_lines" \
68
+ --argjson limit "$limit" \
69
+ '{status: $status, value: $value, limit: $limit}'
70
+ }
71
+
72
+ # ─── Check: Test Count Delta ──────────────────────────────────────────────────
73
+ # Counts test function patterns (describe, it, test, func_test) in changed files
74
+ # Returns JSON: {"status": "pass|fail", "value": new_tests, "baseline": 0}
75
+ check_test_count_delta() {
76
+ local base_branch="$1"
77
+ local baseline="${2:-0}"
78
+
79
+ local new_test_count=0
80
+ local changed_test_files
81
+
82
+ # Get test files that changed
83
+ if git rev-parse "$base_branch" >/dev/null 2>&1; then
84
+ changed_test_files=$(git diff --name-only "$base_branch...HEAD" 2>/dev/null | grep -E '_test\.sh$|\.test\.js$|\.test\.ts$|_spec\.js$' || true)
85
+ fi
86
+
87
+ # Count test patterns in changed files
88
+ if [[ -n "$changed_test_files" ]]; then
89
+ while IFS= read -r file; do
90
+ [[ -z "$file" || ! -f "$file" ]] && continue
91
+ # Count test/describe/it/test patterns
92
+ local count
93
+ count=$(grep -cE '^\s*(describe|it|test|assert_pass|assert_fail)\s*\(' "$file" 2>/dev/null || echo "0")
94
+ new_test_count=$((new_test_count + count))
95
+ done <<< "$changed_test_files"
96
+ fi
97
+
98
+ local status="pass"
99
+ [[ $new_test_count -eq 0 && $baseline -eq 0 ]] && status="pass"
100
+ # Note: we don't fail on zero new tests — some PRs legitimately add no tests
101
+
102
+ jq -n \
103
+ --arg status "$status" \
104
+ --argjson value "$new_test_count" \
105
+ --argjson baseline "$baseline" \
106
+ '{status: $status, value: $value, baseline: $baseline}'
107
+ }
108
+
109
+ # ─── Check: Never-Ship Violations ────────────────────────────────────────────
110
+ # Scans diff for patterns listed in quality profile's never_ship rules
111
+ # Returns JSON: {"status": "pass|fail", "violations": [{"rule": "...", "lines": [...]}]}
112
+ check_never_ship_violations() {
113
+ local base_branch="$1"
114
+ local quality_profile="${2:-.claude/quality-profile.json}"
115
+
116
+ local violations="[]"
117
+ local status="pass"
118
+
119
+ # Load never_ship rules from profile
120
+ if [[ -f "$quality_profile" ]]; then
121
+ local never_ship_rules
122
+ never_ship_rules=$(jq -r '.quality.never_ship[]? // empty' "$quality_profile" 2>/dev/null)
123
+
124
+ if [[ -n "$never_ship_rules" ]]; then
125
+ local diff_content
126
+ if git rev-parse "$base_branch" >/dev/null 2>&1; then
127
+ diff_content=$(git diff "$base_branch...HEAD" 2>/dev/null || true)
128
+ fi
129
+
130
+ if [[ -n "$diff_content" ]]; then
131
+ local temp_violations="[]"
132
+ while IFS= read -r rule; do
133
+ [[ -z "$rule" ]] && continue
134
+ # Check if rule pattern appears in diff (case-insensitive)
135
+ if echo "$diff_content" | grep -qi "$rule"; then
136
+ # Collect lines matching the rule
137
+ local matching_lines
138
+ matching_lines=$(echo "$diff_content" | grep -in "$rule" | head -3 || true)
139
+
140
+ # Build violation entry
141
+ temp_violations=$(jq -n \
142
+ --arg rule "$rule" \
143
+ --arg lines "$matching_lines" \
144
+ --argjson prev "$temp_violations" \
145
+ '$prev + [{"rule": $rule, "lines": ($lines | split("\n") | map(select(length > 0)))}]')
146
+
147
+ status="fail"
148
+ fi
149
+ done <<< "$never_ship_rules"
150
+ violations="$temp_violations"
151
+ fi
152
+ fi
153
+ fi
154
+
155
+ jq -n \
156
+ --arg status "$status" \
157
+ --argjson violations "$violations" \
158
+ '{status: $status, violations: $violations}'
159
+ }
160
+
161
+ # ─── Check: Planned Files Coverage ───────────────────────────────────────────
162
+ # Compares files in scope-report.json against actual changes
163
+ # Returns JSON: {"status": "pass|fail", "planned": N, "touched": N, "unplanned": N}
164
+ check_planned_files_coverage() {
165
+ local scope_report="${1:-$ARTIFACTS_DIR/scope-report.json}"
166
+
167
+ local planned=0 touched=0 unplanned=0 status="pass"
168
+
169
+ if [[ -f "$scope_report" ]]; then
170
+ planned=$(jq -r '.planned_files | length' "$scope_report" 2>/dev/null || echo "0")
171
+ touched=$(jq -r '.planned_files | map(select(.touched == true)) | length' "$scope_report" 2>/dev/null || echo "0")
172
+ unplanned=$(jq -r '.unplanned_files | length' "$scope_report" 2>/dev/null || echo "0")
173
+
174
+ # Check quality profile for whether unplanned files block
175
+ local unplanned_blocks="false"
176
+ if [[ -f "$QUALITY_PROFILE" ]]; then
177
+ unplanned_blocks=$(jq -r '.scope.unplanned_files_block // false' "$QUALITY_PROFILE" 2>/dev/null)
178
+ fi
179
+
180
+ # Status: fail if unplanned files exist and they're configured to block
181
+ if [[ $unplanned -gt 0 && "$unplanned_blocks" == "true" ]]; then
182
+ status="fail"
183
+ fi
184
+ fi
185
+
186
+ jq -n \
187
+ --arg status "$status" \
188
+ --argjson planned "$planned" \
189
+ --argjson touched "$touched" \
190
+ --argjson unplanned "$unplanned" \
191
+ '{status: $status, planned: $planned, touched: $touched, unplanned: $unplanned}'
192
+ }
193
+
194
+ # ─── Check: Acceptance Criteria ──────────────────────────────────────────────
195
+ # Reads acceptance-criteria.json and searches test output for evidence
196
+ # Returns JSON: [{"id": "ac-1", "status": "pass|fail", "evidence": "..."}]
197
+ check_acceptance_criteria() {
198
+ local acceptance_file="${1:-$ARTIFACTS_DIR/acceptance-criteria.json}"
199
+ local test_output="${2:-$ARTIFACTS_DIR/test-results.log}"
200
+
201
+ local criteria="[]"
202
+
203
+ if [[ -f "$acceptance_file" ]]; then
204
+ local test_log=""
205
+ [[ -f "$test_output" ]] && test_log=$(cat "$test_output")
206
+
207
+ # Extract criteria from acceptance file
208
+ local count=0
209
+ while IFS= read -r criterion; do
210
+ [[ -z "$criterion" ]] && continue
211
+ count=$((count + 1))
212
+
213
+ local id="ac-$count"
214
+ local status="pass"
215
+ local evidence=""
216
+
217
+ # Extract search keywords from criterion
218
+ # Acceptance criteria typically contain keywords to search for in test output
219
+ local keywords
220
+ keywords=$(echo "$criterion" | grep -oE '\b(GET|POST|PUT|DELETE|assert|should|expect|test)\b' | head -3 || true)
221
+
222
+ if [[ -n "$test_log" && -n "$keywords" ]]; then
223
+ # Check if any keyword appears in test output
224
+ local found_count=0
225
+ while IFS= read -r keyword; do
226
+ [[ -z "$keyword" ]] && continue
227
+ if echo "$test_log" | grep -qi "$keyword"; then
228
+ found_count=$((found_count + 1))
229
+ fi
230
+ done <<< "$keywords"
231
+
232
+ if [[ $found_count -gt 0 ]]; then
233
+ evidence="Evidence found: keyword(s) present in test output"
234
+ else
235
+ status="fail"
236
+ evidence="No evidence found in test output for criterion"
237
+ fi
238
+ elif [[ -z "$test_log" ]]; then
239
+ evidence="No test output available for verification"
240
+ fi
241
+
242
+ # Build criterion entry
243
+ criteria=$(jq -n \
244
+ --arg id "$id" \
245
+ --arg status "$status" \
246
+ --arg evidence "$evidence" \
247
+ --arg criterion "$criterion" \
248
+ --argjson prev "$criteria" \
249
+ '$prev + [{"id": $id, "status": $status, "evidence": $evidence, "criterion": $criterion}]')
250
+ done < <(jq -r '.acceptance_criteria[]? // empty' "$acceptance_file" 2>/dev/null | while read -r line; do echo "$line"; done)
251
+ fi
252
+
253
+ echo "$criteria"
254
+ }
255
+
256
+ # ─── Compute: Overall DoD Scorecard ──────────────────────────────────────────
257
+ # Runs all checks and produces dod-scorecard.json
258
+ # Usage: compute_dod_scorecard "$base_branch" "$artifacts_dir" "$quality_profile"
259
+ compute_dod_scorecard() {
260
+ local base_branch="${1:-main}"
261
+ local artifacts_dir="${2:-.claude/pipeline-artifacts}"
262
+ local quality_profile="${3:-.claude/quality-profile.json}"
263
+
264
+ # Ensure artifacts directory exists
265
+ mkdir -p "$artifacts_dir"
266
+
267
+ # Run all checks
268
+ local pr_size_check acceptance_criteria_list
269
+ local never_ship_check planned_files_check test_delta_check
270
+
271
+ pr_size_check=$(check_pr_size_score "$base_branch" "$(jq -r '.quality.max_pr_lines // 500' "$quality_profile" 2>/dev/null || echo 500)")
272
+ test_delta_check=$(check_test_count_delta "$base_branch" "0")
273
+ never_ship_check=$(check_never_ship_violations "$base_branch" "$quality_profile")
274
+ planned_files_check=$(check_planned_files_coverage "$artifacts_dir/scope-report.json")
275
+ acceptance_criteria_list=$(check_acceptance_criteria "$artifacts_dir/acceptance-criteria.json" "$artifacts_dir/test-results.log")
276
+
277
+ # Determine overall status and blocking failures
278
+ local overall_status="pass"
279
+ local blocking_failures="[]"
280
+
281
+ # Extract status from checks
282
+ local pr_size_status test_status never_status planned_status
283
+ pr_size_status=$(echo "$pr_size_check" | jq -r '.status')
284
+ test_status=$(echo "$test_delta_check" | jq -r '.status')
285
+ never_status=$(echo "$never_ship_check" | jq -r '.status')
286
+ planned_status=$(echo "$planned_files_check" | jq -r '.status')
287
+
288
+ [[ "$pr_size_status" == "fail" ]] && overall_status="fail"
289
+ [[ "$never_status" == "fail" ]] && overall_status="fail"
290
+
291
+ # Check acceptance criteria for failures
292
+ local ac_failures=0
293
+ ac_failures=$(echo "$acceptance_criteria_list" | jq '[.[] | select(.status == "fail")] | length' 2>/dev/null || echo "0")
294
+ [[ $ac_failures -gt 0 ]] && overall_status="fail"
295
+
296
+ # Build blocking_failures array
297
+ if [[ "$pr_size_status" == "fail" ]]; then
298
+ blocking_failures=$(jq -n --argjson prev "$blocking_failures" '$prev + ["pr_size"]')
299
+ fi
300
+ if [[ "$never_status" == "fail" ]]; then
301
+ blocking_failures=$(jq -n --argjson prev "$blocking_failures" '$prev + ["never_ship"]')
302
+ fi
303
+ if [[ $ac_failures -gt 0 ]]; then
304
+ blocking_failures=$(jq -n --argjson prev "$blocking_failures" '$prev + ["acceptance_criteria"]')
305
+ fi
306
+
307
+ # Build complete scorecard JSON
308
+ local scorecard_json
309
+ scorecard_json=$(jq -n \
310
+ --argjson pr_size "$pr_size_check" \
311
+ --argjson test_count_delta "$test_delta_check" \
312
+ --argjson never_ship_violations "$never_ship_check" \
313
+ --argjson planned_files_coverage "$planned_files_check" \
314
+ --argjson acceptance_criteria "$acceptance_criteria_list" \
315
+ --arg overall "$overall_status" \
316
+ --argjson blocking_failures "$blocking_failures" \
317
+ --arg computed_at "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
318
+ '{
319
+ scorecard: {
320
+ pr_size: $pr_size,
321
+ test_count_delta: $test_count_delta,
322
+ never_ship_violations: $never_ship_violations,
323
+ planned_files_coverage: $planned_files_coverage,
324
+ acceptance_criteria: $acceptance_criteria
325
+ },
326
+ overall: $overall,
327
+ blocking_failures: $blocking_failures,
328
+ computed_at: $computed_at
329
+ }')
330
+
331
+ # Write to file
332
+ echo "$scorecard_json" > "$artifacts_dir/dod-scorecard.json"
333
+ echo "$scorecard_json"
334
+ }
335
+
336
+ # ─── Format: Scorecard for Display ────────────────────────────────────────────
337
+ # Returns human-readable markdown format of the scorecard
338
+ format_scorecard() {
339
+ local scorecard_json="$1"
340
+
341
+ local output="# Definition of Done Scorecard\n\n"
342
+
343
+ # PR Size check
344
+ local pr_size_status pr_size_value pr_size_limit
345
+ pr_size_status=$(echo "$scorecard_json" | jq -r '.scorecard.pr_size.status')
346
+ pr_size_value=$(echo "$scorecard_json" | jq -r '.scorecard.pr_size.value')
347
+ pr_size_limit=$(echo "$scorecard_json" | jq -r '.scorecard.pr_size.limit')
348
+
349
+ local pr_size_emoji="✓"
350
+ [[ "$pr_size_status" == "fail" ]] && pr_size_emoji="✗"
351
+ local pr_size_status_upper
352
+ pr_size_status_upper=$(echo "$pr_size_status" | tr a-z A-Z)
353
+ output+="## PR Size\n$pr_size_emoji $pr_size_status_upper: ${pr_size_value} lines (max: ${pr_size_limit})\n\n"
354
+
355
+ # Test Count Delta
356
+ local test_status test_value test_baseline
357
+ test_status=$(echo "$scorecard_json" | jq -r '.scorecard.test_count_delta.status')
358
+ test_value=$(echo "$scorecard_json" | jq -r '.scorecard.test_count_delta.value')
359
+ test_baseline=$(echo "$scorecard_json" | jq -r '.scorecard.test_count_delta.baseline')
360
+
361
+ local test_emoji="✓"
362
+ [[ "$test_status" == "fail" ]] && test_emoji="✗"
363
+ local test_status_upper
364
+ test_status_upper=$(echo "$test_status" | tr a-z A-Z)
365
+ output+="## Test Coverage\n$test_emoji $test_status_upper: ${test_value} new tests (baseline: ${test_baseline})\n\n"
366
+
367
+ # Never-Ship Violations
368
+ local never_status never_violations_count
369
+ never_status=$(echo "$scorecard_json" | jq -r '.scorecard.never_ship_violations.status')
370
+ never_violations_count=$(echo "$scorecard_json" | jq -r '.scorecard.never_ship_violations.violations | length')
371
+
372
+ local never_emoji="✓"
373
+ [[ "$never_status" == "fail" ]] && never_emoji="✗"
374
+ local never_status_upper
375
+ never_status_upper=$(echo "$never_status" | tr a-z A-Z)
376
+ output+="## Never-Ship Rules\n$never_emoji $never_status_upper: ${never_violations_count} violation(s)\n"
377
+
378
+ if [[ $never_violations_count -gt 0 ]]; then
379
+ output+="$(echo "$scorecard_json" | jq -r '.scorecard.never_ship_violations.violations[] | " - \(.rule)"' | head -5)\n\n"
380
+ else
381
+ output+="\n"
382
+ fi
383
+
384
+ # Planned Files Coverage
385
+ local planned_status planned_count touched_count unplanned_count
386
+ planned_status=$(echo "$scorecard_json" | jq -r '.scorecard.planned_files_coverage.status')
387
+ planned_count=$(echo "$scorecard_json" | jq -r '.scorecard.planned_files_coverage.planned')
388
+ touched_count=$(echo "$scorecard_json" | jq -r '.scorecard.planned_files_coverage.touched')
389
+ unplanned_count=$(echo "$scorecard_json" | jq -r '.scorecard.planned_files_coverage.unplanned')
390
+
391
+ local planned_emoji="✓"
392
+ [[ "$planned_status" == "fail" ]] && planned_emoji="✗"
393
+ local planned_status_upper
394
+ planned_status_upper=$(echo "$planned_status" | tr a-z A-Z)
395
+ output+="## Scope Coverage\n$planned_emoji $planned_status_upper: ${touched_count}/${planned_count} planned files touched, ${unplanned_count} unplanned\n\n"
396
+
397
+ # Acceptance Criteria
398
+ local ac_count ac_passed ac_failed
399
+ ac_count=$(echo "$scorecard_json" | jq '.scorecard.acceptance_criteria | length')
400
+ ac_passed=$(echo "$scorecard_json" | jq '[.scorecard.acceptance_criteria[] | select(.status == "pass")] | length')
401
+ ac_failed=$((ac_count - ac_passed))
402
+
403
+ output+="## Acceptance Criteria\n✓ ${ac_passed}/${ac_count} passed"
404
+ if [[ $ac_failed -gt 0 ]]; then
405
+ output+=" (${ac_failed} failed)\n"
406
+ else
407
+ output+="\n"
408
+ fi
409
+ output+="\n"
410
+
411
+ # Overall status
412
+ local overall_status overall_emoji
413
+ overall_status=$(echo "$scorecard_json" | jq -r '.overall')
414
+ [[ "$overall_status" == "pass" ]] && overall_emoji="✓" || overall_emoji="✗"
415
+ local overall_status_upper
416
+ overall_status_upper=$(echo "$overall_status" | tr a-z A-Z)
417
+
418
+ output+="## Overall Result\n${overall_emoji} **${overall_status_upper}**\n"
419
+
420
+ echo -e "$output"
421
+ }
422
+
423
+ # ─── Gate: Scorecard Pass/Fail ───────────────────────────────────────────────
424
+ # Returns 0 if overall == pass, 1 if overall == fail
425
+ scorecard_passed() {
426
+ local scorecard_json="$1"
427
+ local overall_status
428
+ overall_status=$(echo "$scorecard_json" | jq -r '.overall // "fail"')
429
+
430
+ if [[ "$overall_status" == "pass" ]]; then
431
+ return 0
432
+ else
433
+ return 1
434
+ fi
435
+ }
436
+
437
+ # ─── Blocking Failures Extractor ────────────────────────────────────────────
438
+ # Returns list of blocking failure categories
439
+ get_blocking_failures() {
440
+ local scorecard_json="$1"
441
+ echo "$scorecard_json" | jq -r '.blocking_failures[]? // empty'
442
+ }