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,521 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright lib/outcome-feedback — PR review capture & quality scoring ║
4
+ # ║ Review comments → memory · Merge quality tracking · Rule auto-generation║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
+
9
+ # Double-source guard
10
+ [[ -n "${_SW_OUTCOME_FEEDBACK_LOADED:-}" ]] && return 0
11
+ _SW_OUTCOME_FEEDBACK_LOADED=1
12
+
13
+ # ─── Helpers (already loaded by caller, but provide fallbacks) ───────────────
14
+ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
15
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
16
+ fi
17
+ if [[ "$(type -t info 2>/dev/null)" != "function" ]]; then
18
+ info() { echo "▸ $*"; }
19
+ success() { echo "✓ $*"; }
20
+ warn() { echo "⚠ $*" >&2; }
21
+ error() { echo "✗ $*" >&2; }
22
+ fi
23
+
24
+ # ─── Storage Paths ──────────────────────────────────────────────────────────
25
+
26
+ # Allow override of memory root for testing
27
+ OUTCOME_FEEDBACK_MEMORY_ROOT="${OUTCOME_FEEDBACK_MEMORY_ROOT:-${HOME}/.shipwright/memory}"
28
+
29
+ # Repo hash: echo -n "$repo_path" | shasum -a 256 | cut -c1-12
30
+ _outcome_feedback_repo_hash() {
31
+ local repo_path="${1:-.}"
32
+ echo -n "$repo_path" | shasum -a 256 | cut -c1-12
33
+ }
34
+
35
+ _outcome_feedback_memory_dir() {
36
+ local repo_hash="$1"
37
+ echo "${OUTCOME_FEEDBACK_MEMORY_ROOT}/${repo_hash}"
38
+ }
39
+
40
+ # ─── Category Extraction from Review Comments ────────────────────────────────
41
+
42
+ # Extract review comment categories using keyword matching
43
+ # Returns space-separated list of categories (error_handling, validation, naming, etc.)
44
+ _extract_comment_categories() {
45
+ local comment="$1"
46
+ local categories=""
47
+
48
+ # Normalize to lowercase for matching
49
+ local lower_comment
50
+ lower_comment=$(echo "$comment" | tr '[:upper:]' '[:lower:]')
51
+
52
+ # Check for each category using substring matching
53
+ echo "$lower_comment" | grep -qiE "(error|exception|crash|fail|panic)" && categories="$categories error_handling"
54
+ echo "$lower_comment" | grep -qiE "(validat|check|assert|guard|verify)" && categories="$categories validation"
55
+ echo "$lower_comment" | grep -qiE "(nam|identifier|variable|function|class)" && categories="$categories naming"
56
+ echo "$lower_comment" | grep -qiE "(test|spec|coverage|mock|fixture)" && categories="$categories testing"
57
+ echo "$lower_comment" | grep -qiE "(secur|auth|encript|credential|token|xss|sql)" && categories="$categories security"
58
+ echo "$lower_comment" | grep -qiE "(perform|optim|speed|latency|cache|memory)" && categories="$categories performance"
59
+ echo "$lower_comment" | grep -qiE "(type|interface|contract|signature)" && categories="$categories typing"
60
+ echo "$lower_comment" | grep -qiE "(doc|comment|readme|example)" && categories="$categories documentation"
61
+
62
+ # If no categories detected, use generic "other"
63
+ if [[ -z "$categories" ]]; then
64
+ categories="other"
65
+ fi
66
+
67
+ echo "$categories" | xargs # Trim and normalize whitespace
68
+ }
69
+
70
+ # ─── Capture PR Review Comments ──────────────────────────────────────────────
71
+
72
+ # capture_review_feedback <pr_number> <repo_path>
73
+ # Fetches PR reviews and comments from GitHub, stores as JSON entries in memory
74
+ # Uses gh API if available (guarded by NO_GITHUB check)
75
+ capture_review_feedback() {
76
+ local pr_number="$1"
77
+ local repo_path="${2:-.}"
78
+
79
+ [[ -z "$pr_number" ]] && { error "Usage: capture_review_feedback <pr_number> [repo_path]"; return 1; }
80
+
81
+ # Ensure memory dir exists (always, even if NO_GITHUB)
82
+ local repo_hash
83
+ repo_hash=$(_outcome_feedback_repo_hash "$repo_path")
84
+ local mem_dir
85
+ mem_dir=$(_outcome_feedback_memory_dir "$repo_hash")
86
+ mkdir -p "$mem_dir"
87
+
88
+ local feedback_file="${mem_dir}/review-feedback.jsonl"
89
+
90
+ # Skip if NO_GITHUB is set
91
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
92
+ info "Skipping review capture (NO_GITHUB set)"
93
+ return 0
94
+ fi
95
+
96
+ info "Capturing review feedback for PR #${pr_number}..."
97
+
98
+ # Try to get owner/repo from git remote
99
+ local owner_repo
100
+ owner_repo=$(git -C "$repo_path" remote get-url origin 2>/dev/null | \
101
+ sed -E 's#^(https?://github\.com/|git@github\.com:)##; s#\.git$##' || true)
102
+
103
+ if [[ -z "$owner_repo" ]]; then
104
+ warn "Cannot determine owner/repo — skipping review capture"
105
+ return 0
106
+ fi
107
+
108
+ # Fetch reviews
109
+ local reviews_json
110
+ if reviews_json=$(gh api "repos/${owner_repo}/pulls/${pr_number}/reviews" 2>/dev/null); then
111
+ echo "$reviews_json" | jq -c '.[]? |
112
+ select(.body != null and .body != "") |
113
+ {
114
+ body: .body,
115
+ state: .state,
116
+ author: .user.login,
117
+ timestamp: .submitted_at
118
+ }' | while read -r review; do
119
+ [[ -z "$review" ]] && continue
120
+
121
+ local comment
122
+ comment=$(echo "$review" | jq -r '.body')
123
+ local categories
124
+ categories=$(_extract_comment_categories "$comment")
125
+
126
+ # Create JSONL entry
127
+ echo "$review" | jq \
128
+ --arg pr "$pr_number" \
129
+ --arg cats "$categories" \
130
+ --arg ts "$(now_iso)" \
131
+ '. += {
132
+ pr: ($pr | tonumber),
133
+ categories: $cats,
134
+ captured_at: $ts
135
+ }' >> "$feedback_file"
136
+ done
137
+ fi
138
+
139
+ # Fetch review comments (inline code comments)
140
+ local comments_json
141
+ if comments_json=$(gh api "repos/${owner_repo}/pulls/${pr_number}/comments" 2>/dev/null); then
142
+ echo "$comments_json" | jq -c '.[]? |
143
+ select(.body != null and .body != "") |
144
+ {
145
+ body: .body,
146
+ path: .path,
147
+ commit_id: .commit_id,
148
+ author: .user.login,
149
+ timestamp: .created_at
150
+ }' | while read -r comment_obj; do
151
+ [[ -z "$comment_obj" ]] && continue
152
+
153
+ local comment
154
+ comment=$(echo "$comment_obj" | jq -r '.body')
155
+ local categories
156
+ categories=$(_extract_comment_categories "$comment")
157
+
158
+ # Create JSONL entry
159
+ echo "$comment_obj" | jq \
160
+ --arg pr "$pr_number" \
161
+ --arg cats "$categories" \
162
+ --arg ts "$(now_iso)" \
163
+ '. += {
164
+ pr: ($pr | tonumber),
165
+ categories: $cats,
166
+ captured_at: $ts
167
+ }' >> "$feedback_file"
168
+ done
169
+ fi
170
+
171
+ success "Captured review feedback for PR #${pr_number}"
172
+ return 0
173
+ }
174
+
175
+ # ─── Compute Merge Quality Score ────────────────────────────────────────────
176
+
177
+ # compute_merge_quality <pr_number> <repo_path> [merge_status]
178
+ # Scores PR based on merge outcome: clean_merge=+1, changes_requested=-1, reverted=-3, regression=-2
179
+ # Stores as JSON in merge-quality.jsonl
180
+ compute_merge_quality() {
181
+ local pr_number="$1"
182
+ local repo_path="${2:-.}"
183
+ local merge_status="${3:-clean_merge}" # Default to clean_merge
184
+
185
+ [[ -z "$pr_number" ]] && { error "Usage: compute_merge_quality <pr_number> [repo_path] [merge_status]"; return 1; }
186
+
187
+ local repo_hash
188
+ repo_hash=$(_outcome_feedback_repo_hash "$repo_path")
189
+ local mem_dir
190
+ mem_dir=$(_outcome_feedback_memory_dir "$repo_hash")
191
+ mkdir -p "$mem_dir"
192
+
193
+ local quality_file="${mem_dir}/merge-quality.jsonl"
194
+
195
+ # Compute score based on merge status
196
+ local score=0
197
+ case "$merge_status" in
198
+ clean_merge) score=1 ;;
199
+ changes_requested) score=-1 ;;
200
+ reverted) score=-3 ;;
201
+ regression) score=-2 ;;
202
+ *) score=0 ;;
203
+ esac
204
+
205
+ # Get PR title (if available from GitHub)
206
+ local pr_title=""
207
+ if [[ "${NO_GITHUB:-}" != "true" && "${NO_GITHUB:-}" != "1" ]]; then
208
+ local owner_repo
209
+ owner_repo=$(git -C "$repo_path" remote get-url origin 2>/dev/null | \
210
+ sed -E 's#^(https?://github\.com/|git@github\.com:)##; s#\.git$##' || true)
211
+
212
+ if [[ -n "$owner_repo" ]]; then
213
+ pr_title=$(gh api "repos/${owner_repo}/pulls/${pr_number}" \
214
+ --jq '.title' 2>/dev/null || echo "")
215
+ fi
216
+ fi
217
+
218
+ # Append JSONL entry (compact, one per line)
219
+ local entry
220
+ entry=$(jq -cn \
221
+ --argjson pr "$pr_number" \
222
+ --argjson score "$score" \
223
+ --arg status "$merge_status" \
224
+ --arg title "$pr_title" \
225
+ --arg ts "$(now_iso)" \
226
+ '{
227
+ pr: $pr,
228
+ score: $score,
229
+ type: $status,
230
+ title: $title,
231
+ timestamp: $ts
232
+ }')
233
+
234
+ echo "$entry" >> "$quality_file"
235
+
236
+ success "Computed merge quality: PR #${pr_number} score=${score} (${merge_status})"
237
+ return 0
238
+ }
239
+
240
+ # ─── Rolling Quality Score Calculation ───────────────────────────────────────
241
+
242
+ # get_rolling_quality_score <repo_path> [window_size]
243
+ # Computes average quality score over last N PRs
244
+ # Returns JSON with score, pr_count, details
245
+ get_rolling_quality_score() {
246
+ local repo_path="${1:-.}"
247
+ local window_size="${2:-20}"
248
+
249
+ local repo_hash
250
+ repo_hash=$(_outcome_feedback_repo_hash "$repo_path")
251
+ local mem_dir
252
+ mem_dir=$(_outcome_feedback_memory_dir "$repo_hash")
253
+ local quality_file="${mem_dir}/merge-quality.jsonl"
254
+
255
+ # If file doesn't exist, return zero score
256
+ if [[ ! -f "$quality_file" ]]; then
257
+ echo '{"rolling_score": 0, "pr_count": 0, "details": []}'
258
+ return 0
259
+ fi
260
+
261
+ # Tail last N entries and compute rolling average
262
+ local result
263
+ result=$(tail -n "$window_size" "$quality_file" | jq -s \
264
+ '{
265
+ rolling_score: (if length > 0 then ([.[].score] | add / length) else 0 end),
266
+ pr_count: length,
267
+ by_type: (group_by(.type) | map({type: .[0].type, count: length, avg_score: (([.[].score] | add / length) | . * 100 | round / 100)}))
268
+ }' 2>/dev/null || echo '{"rolling_score": 0, "pr_count": 0}')
269
+
270
+ echo "$result"
271
+ return 0
272
+ }
273
+
274
+ # ─── Detect Recurring Review Patterns ────────────────────────────────────────
275
+
276
+ # detect_review_patterns <repo_path> [min_count]
277
+ # Groups review comments by category
278
+ # Detects patterns appearing in min_count (default 3) or more PRs
279
+ # Returns JSON with pattern_name, count, prs
280
+ detect_review_patterns() {
281
+ local repo_path="${1:-.}"
282
+ local min_count="${2:-3}"
283
+
284
+ local repo_hash
285
+ repo_hash=$(_outcome_feedback_repo_hash "$repo_path")
286
+ local mem_dir
287
+ mem_dir=$(_outcome_feedback_memory_dir "$repo_hash")
288
+ local feedback_file="${mem_dir}/review-feedback.jsonl"
289
+
290
+ # If no feedback file, return empty patterns
291
+ if [[ ! -f "$feedback_file" ]]; then
292
+ echo '{"patterns": []}'
293
+ return 0
294
+ fi
295
+
296
+ # Group by category, count PRs per category, filter by min_count
297
+ local patterns
298
+ patterns=$(jq -s '
299
+ [.[] |
300
+ select(.categories != null) |
301
+ .categories |
302
+ split(" ") |
303
+ .[] |
304
+ select(. != "")] |
305
+ group_by(.) |
306
+ map({
307
+ category: .[0],
308
+ count: length,
309
+ prs: map(input_line_number) | unique
310
+ }) |
311
+ map(select(.count >= '$min_count')) |
312
+ sort_by(-.count)
313
+ ' "$feedback_file" 2>/dev/null || echo "[]")
314
+
315
+ # Output as JSON
316
+ echo "{\"patterns\": $patterns}"
317
+ return 0
318
+ }
319
+
320
+ # ─── Generate Learned Quality Rules ────────────────────────────────────────────
321
+
322
+ # generate_learned_rules <repo_path> <quality_profile_path>
323
+ # Detects recurring review patterns (3+ occurrences)
324
+ # Generates quality rules and adds to quality-profile.json's learned_rules array
325
+ # Idempotent: doesn't duplicate existing rules
326
+ generate_learned_rules() {
327
+ local repo_path="${1:-.}"
328
+ local quality_profile="${2:-./ .claude/quality-profile.json}"
329
+
330
+ [[ -z "$quality_profile" || ! -f "$quality_profile" ]] && {
331
+ warn "Quality profile not found: $quality_profile"
332
+ return 1
333
+ }
334
+
335
+ info "Generating learned rules from review patterns..."
336
+
337
+ # Detect patterns
338
+ local patterns_json
339
+ patterns_json=$(detect_review_patterns "$repo_path" 3 || echo '{"patterns": []}')
340
+
341
+ local pattern_count
342
+ pattern_count=$(echo "$patterns_json" | jq '.patterns | length' 2>/dev/null || echo 0)
343
+
344
+ if [[ "$pattern_count" -eq 0 ]]; then
345
+ info "No recurring patterns detected (threshold: 3+ PRs)"
346
+ return 0
347
+ fi
348
+
349
+ # For each pattern, generate a rule and add to quality profile
350
+ echo "$patterns_json" | jq -c '.patterns[]?' | while read -r pattern; do
351
+ [[ -z "$pattern" ]] && continue
352
+
353
+ local category
354
+ category=$(echo "$pattern" | jq -r '.category')
355
+ local count
356
+ count=$(echo "$pattern" | jq -r '.count')
357
+
358
+ # Compute confidence (count / total_prs, capped at 0.95)
359
+ # For now, use count-based confidence (3 = 0.60, 5 = 0.80, 10 = 0.95)
360
+ local confidence
361
+ confidence=$(echo "scale=2; if ($count >= 10) then 0.95 else ($count / 20) end" | bc 2>/dev/null || echo "0.6")
362
+
363
+ # Generate rule text based on category
364
+ local rule_text
365
+ case "$category" in
366
+ error_handling)
367
+ rule_text="Always add error handling for external API calls and edge cases — try/catch, timeout guards, validation"
368
+ ;;
369
+ validation)
370
+ rule_text="Validate input parameters and contract assumptions before processing — guard clauses, type checks"
371
+ ;;
372
+ naming)
373
+ rule_text="Use clear, descriptive names for functions, variables, and classes — avoid abbreviations, be explicit"
374
+ ;;
375
+ testing)
376
+ rule_text="Write tests for happy path, edge cases, and error conditions — aim for >80% coverage on modified code"
377
+ ;;
378
+ security)
379
+ rule_text="Review all authentication, authorization, and data handling code — no hardcoded secrets, use secure methods"
380
+ ;;
381
+ performance)
382
+ rule_text="Profile for hot paths — avoid N+1 queries, unnecessary allocations, and blocking I/O in loops"
383
+ ;;
384
+ typing)
385
+ rule_text="Use strong types and explicit interfaces — avoid 'any', verify type safety at boundaries"
386
+ ;;
387
+ documentation)
388
+ rule_text="Document public APIs and non-obvious code — include examples, parameter descriptions, return types"
389
+ ;;
390
+ *)
391
+ rule_text="Address recurring review feedback: ${category}"
392
+ ;;
393
+ esac
394
+
395
+ # Check if rule already exists in learned_rules (avoid duplicates)
396
+ local rule_exists
397
+ rule_exists=$(jq --arg cat "$category" \
398
+ '.quality.learned_rules[]? | select(.source == "review_pattern_" + $cat) | .rule' \
399
+ "$quality_profile" 2>/dev/null || echo "")
400
+
401
+ if [[ -n "$rule_exists" ]]; then
402
+ info "Rule already exists for category: $category"
403
+ continue
404
+ fi
405
+
406
+ # Add rule to quality profile
407
+ local tmp_file
408
+ tmp_file=$(mktemp)
409
+ trap "rm -f '$tmp_file'" RETURN
410
+
411
+ jq \
412
+ --arg rule "$rule_text" \
413
+ --arg source "review_pattern_${category}" \
414
+ --arg confidence "$confidence" \
415
+ --arg ts "$(now_iso)" \
416
+ '.quality.learned_rules += [{
417
+ rule: $rule,
418
+ source: $source,
419
+ confidence: ($confidence | tonumber),
420
+ created_at: $ts,
421
+ inject_at: ["plan", "build", "review"]
422
+ }]' \
423
+ "$quality_profile" > "$tmp_file" && \
424
+ mv "$tmp_file" "$quality_profile"
425
+
426
+ success "Added learned rule: ${category} (confidence: ${confidence})"
427
+ done
428
+
429
+ return 0
430
+ }
431
+
432
+ # ─── Format Outcome Summary for DORA Dashboard ───────────────────────────────
433
+
434
+ # format_outcome_summary <repo_path>
435
+ # Produces summary JSON with rolling quality score, pattern analysis, recent PRs
436
+ format_outcome_summary() {
437
+ local repo_path="${1:-.}"
438
+
439
+ # Get rolling quality score
440
+ local rolling_json
441
+ rolling_json=$(get_rolling_quality_score "$repo_path" 20 2>/dev/null || echo '{}')
442
+
443
+ # Get patterns
444
+ local patterns_json
445
+ patterns_json=$(detect_review_patterns "$repo_path" 3 2>/dev/null || echo '{"patterns": []}')
446
+
447
+ # Get repo hash and memory dir info
448
+ local repo_hash
449
+ repo_hash=$(_outcome_feedback_repo_hash "$repo_path")
450
+ local mem_dir
451
+ mem_dir=$(_outcome_feedback_memory_dir "$repo_hash")
452
+
453
+ # Count total feedback entries
454
+ local feedback_count=0
455
+ [[ -f "${mem_dir}/review-feedback.jsonl" ]] && \
456
+ feedback_count=$(wc -l < "${mem_dir}/review-feedback.jsonl" 2>/dev/null || echo 0)
457
+
458
+ # Assemble summary
459
+ local summary
460
+ summary=$(jq -n \
461
+ --argjson rolling "$rolling_json" \
462
+ --argjson patterns "$patterns_json" \
463
+ --argjson feedback_count "$feedback_count" \
464
+ --arg ts "$(now_iso)" \
465
+ '{
466
+ timestamp: $ts,
467
+ rolling_quality: $rolling,
468
+ review_patterns: $patterns,
469
+ feedback_entries: $feedback_count
470
+ }')
471
+
472
+ echo "$summary"
473
+ return 0
474
+ }
475
+
476
+ # ─── Post-Merge Feedback Hook ───────────────────────────────────────────────
477
+
478
+ # post_merge_feedback <pr_number> <repo_path> <quality_profile_path>
479
+ # Full post-merge workflow: capture reviews, compute score, detect patterns, generate rules
480
+ post_merge_feedback() {
481
+ local pr_number="$1"
482
+ local repo_path="${2:-.}"
483
+ local quality_profile="${3:-./.claude/quality-profile.json}"
484
+
485
+ [[ -z "$pr_number" ]] && { error "Usage: post_merge_feedback <pr_number> [repo_path] [quality_profile]"; return 1; }
486
+
487
+ info "Running post-merge feedback collection for PR #${pr_number}..."
488
+
489
+ # Step 1: Capture review comments
490
+ capture_review_feedback "$pr_number" "$repo_path" || true
491
+
492
+ # Step 2: Compute merge quality score
493
+ compute_merge_quality "$pr_number" "$repo_path" "clean_merge" || true
494
+
495
+ # Step 3: Detect patterns
496
+ local patterns
497
+ patterns=$(detect_review_patterns "$repo_path" 3 2>/dev/null || echo '{"patterns": []}')
498
+ local pattern_count
499
+ pattern_count=$(echo "$patterns" | jq '.patterns | length')
500
+
501
+ if [[ "$pattern_count" -gt 0 ]]; then
502
+ info "Detected $pattern_count recurring patterns"
503
+
504
+ # Step 4: Generate learned rules (if quality profile exists)
505
+ if [[ -f "$quality_profile" ]]; then
506
+ generate_learned_rules "$repo_path" "$quality_profile" || true
507
+ fi
508
+ fi
509
+
510
+ success "Post-merge feedback collection complete"
511
+ return 0
512
+ }
513
+
514
+ # Export functions for sourcing
515
+ export -f capture_review_feedback
516
+ export -f compute_merge_quality
517
+ export -f get_rolling_quality_score
518
+ export -f detect_review_patterns
519
+ export -f generate_learned_rules
520
+ export -f format_outcome_summary
521
+ export -f post_merge_feedback