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,445 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════════════
3
+ # scope-enforcement.sh — Planned vs actual file tracking, PR size gate
4
+ # Implements Component 4 of the Pipeline Quality Revolution
5
+ # ═══════════════════════════════════════════════════════════════════
6
+ set -euo pipefail
7
+
8
+ [[ -n "${_SCOPE_ENFORCEMENT_LOADED:-}" ]] && return 0
9
+ _SCOPE_ENFORCEMENT_LOADED=1
10
+
11
+ SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
12
+ source "${SCRIPT_DIR}/lib/helpers.sh"
13
+
14
+ VERSION="3.3.0"
15
+
16
+ # ─── Extract planned files from plan.md "Files to Modify" section ─────────
17
+ # Handles multiple markdown formats: bullet lists, numbered lists, tables, code blocks
18
+ # Usage: extract_planned_files "$plan_file"
19
+ # Output: newline-separated list of file paths
20
+ extract_planned_files() {
21
+ local plan_file="$1"
22
+
23
+ if [[ ! -f "$plan_file" ]]; then
24
+ echo ""
25
+ return 0
26
+ fi
27
+
28
+ local in_files_section=false
29
+ local files=""
30
+
31
+ while IFS= read -r line; do
32
+ # Detect "Files to Modify" section header (case-insensitive)
33
+ local line_lower
34
+ line_lower=$(echo "$line" | tr 'A-Z' 'a-z')
35
+ if echo "$line_lower" | grep -E '^[[:space:]]*#+[[:space:]]*(files[[:space:]]+to[[:space:]]+(modify|change))' >/dev/null; then
36
+ in_files_section=true
37
+ continue
38
+ fi
39
+
40
+ # Exit section if we hit another ## header
41
+ if [[ "$in_files_section" == "true" ]] && [[ "$line" =~ ^##[^#] ]]; then
42
+ in_files_section=false
43
+ continue
44
+ fi
45
+
46
+ # Only process lines while we're in the Files section
47
+ if [[ "$in_files_section" == "false" ]]; then
48
+ continue
49
+ fi
50
+
51
+ # Skip empty lines and headers within the section
52
+ if [[ -z "$(echo "$line" | sed 's/^[[:space:]]*$//')" ]]; then
53
+ continue
54
+ fi
55
+
56
+ # Extract paths from bullet lists: "- path/to/file.ts"
57
+ if [[ "$line" =~ ^[[:space:]]*[-*][[:space:]]+([^[:space:]#].+)$ ]]; then
58
+ local item="${BASH_REMATCH[1]}"
59
+ # Clean up markdown formatting
60
+ item=$(echo "$item" | sed 's/`//g' | sed 's/\*\*//g' | sed 's/\[//g' | sed 's/\]//g' | xargs)
61
+ if [[ -n "$item" && ! "$item" =~ ^# ]]; then
62
+ files="${files}${item}"$'\n'
63
+ fi
64
+ continue
65
+ fi
66
+
67
+ # Extract paths from numbered lists: "1. path/to/file.ts" or "1) path/to/file.ts"
68
+ if echo "$line" | grep -E '^[[:space:]]*[0-9]+[.)] ' >/dev/null; then
69
+ local item
70
+ item=$(echo "$line" | sed 's/^[[:space:]]*[0-9]*[.)] //' | xargs)
71
+ item=$(echo "$item" | sed 's/`//g' | sed 's/\*\*//g' | sed 's/\[//g' | sed 's/\]//g')
72
+ if [[ -n "$item" && ! "$item" =~ ^# ]]; then
73
+ files="${files}${item}"$'\n'
74
+ fi
75
+ continue
76
+ fi
77
+
78
+ # Extract paths from markdown tables (pipe-delimited)
79
+ if echo "$line" | grep -E '^[[:space:]]*\|' >/dev/null; then
80
+ # Skip separator rows (all dashes)
81
+ if echo "$line" | grep -E '^\|[-: |]+\|' >/dev/null; then
82
+ continue
83
+ fi
84
+ # Skip header rows (contains "File", "Path", "Purpose", etc.)
85
+ if echo "$line" | grep -i -E '(File|Path|Purpose)' >/dev/null; then
86
+ continue
87
+ fi
88
+ # Extract cells from the line
89
+ local cells
90
+ cells=$(echo "$line" | sed 's/^[[:space:]]*|//' | sed 's/|[[:space:]]*$//')
91
+ # Process first cell (usually the file path)
92
+ local first_cell
93
+ first_cell=$(echo "$cells" | cut -d'|' -f1 | xargs)
94
+ first_cell=$(echo "$first_cell" | sed 's/`//g' | sed 's/\*\*//g' | sed 's/\[//g' | sed 's/\]//g')
95
+ if [[ -n "$first_cell" && "$first_cell" =~ / ]]; then
96
+ files="${files}${first_cell}"$'\n'
97
+ fi
98
+ continue
99
+ fi
100
+
101
+ # Extract paths from code blocks (triple backticks with optional language)
102
+ if [[ "$line" =~ ^\`\`\`([a-zA-Z0-9_-]*)?$ ]]; then
103
+ continue
104
+ fi
105
+
106
+ if [[ "$line" =~ ^\`\`\`$ ]]; then
107
+ continue
108
+ fi
109
+
110
+ # If line looks like a file path (contains /, starts with src/, lib/, etc.)
111
+ local trimmed
112
+ trimmed=$(echo "$line" | sed 's/^[[:space:]]*-[[:space:]]*//' | sed 's/^[[:space:]]*[0-9]*[.)] //' | xargs)
113
+ if [[ "$trimmed" =~ / ]] && [[ -n "$trimmed" ]] && [[ ! "$trimmed" =~ ^[#*] ]]; then
114
+ files="${files}${trimmed}"$'\n'
115
+ fi
116
+ done < "$plan_file"
117
+
118
+ # Remove duplicates and blank lines
119
+ echo "$files" | grep -v '^$' | sort -u || true
120
+ }
121
+
122
+ # ─── Get list of actually changed files from git diff ─────────────────────
123
+ # Usage: get_changed_files "$base_branch"
124
+ # Output: newline-separated list of file paths
125
+ get_changed_files() {
126
+ local base_branch="${1:-origin/main}"
127
+
128
+ # Get files changed compared to base branch
129
+ # Filter out .claude/ files to focus on real code changes
130
+ git diff --name-only "$base_branch"...HEAD 2>/dev/null | grep -v '^\.claude/' || true
131
+ }
132
+
133
+ # ─── Get PR stats (insertions, deletions, files changed) ──────────────────
134
+ # Usage: get_pr_stats "$base_branch"
135
+ # Output: JSON with insertions, deletions, files_changed
136
+ get_pr_stats() {
137
+ local base_branch="${1:-origin/main}"
138
+
139
+ local insertions=0
140
+ local deletions=0
141
+ local files_changed=0
142
+
143
+ # Get stat output
144
+ local stat_output
145
+ stat_output=$(git diff --stat "$base_branch"...HEAD 2>/dev/null || true)
146
+
147
+ if [[ -n "$stat_output" ]]; then
148
+ # Extract counts from the summary line (last line of --stat output)
149
+ # Format: " N files changed, M insertions(+), K deletions(-)"
150
+ local summary
151
+ summary=$(echo "$stat_output" | tail -1)
152
+
153
+ # Extract files changed
154
+ if [[ "$summary" =~ ([0-9]+)[[:space:]]+files?[[:space:]]+changed ]]; then
155
+ files_changed="${BASH_REMATCH[1]}"
156
+ fi
157
+
158
+ # Extract insertions
159
+ if [[ "$summary" =~ ([0-9]+)[[:space:]]+insertions?\(\+ ]]; then
160
+ insertions="${BASH_REMATCH[1]}"
161
+ fi
162
+
163
+ # Extract deletions
164
+ if [[ "$summary" =~ ([0-9]+)[[:space:]]+deletions?\(\- ]]; then
165
+ deletions="${BASH_REMATCH[1]}"
166
+ fi
167
+ fi
168
+
169
+ # Return as JSON
170
+ echo "{\"insertions\":$insertions,\"deletions\":$deletions,\"files_changed\":$files_changed}"
171
+ }
172
+
173
+ # ─── Compare planned vs actual files, generate scope report ───────────────
174
+ # Usage: generate_scope_report "$plan_file" "$base_branch" "$artifacts_dir"
175
+ # Output: scope-report.json in artifacts_dir
176
+ generate_scope_report() {
177
+ local plan_file="$1"
178
+ local base_branch="${2:-origin/main}"
179
+ local artifacts_dir="${3:-.}"
180
+
181
+ # Extract planned files
182
+ local planned_files
183
+ planned_files=$(extract_planned_files "$plan_file")
184
+
185
+ # Get actual changed files
186
+ local actual_files
187
+ actual_files=$(get_changed_files "$base_branch")
188
+
189
+ # Get PR stats
190
+ local pr_stats
191
+ pr_stats=$(get_pr_stats "$base_branch")
192
+
193
+ # Build arrays for comparison
194
+ local planned_array=()
195
+ local actual_array=()
196
+
197
+ while IFS= read -r file; do
198
+ [[ -z "$file" ]] && continue
199
+ planned_array+=("$file")
200
+ done <<< "$planned_files"
201
+
202
+ while IFS= read -r file; do
203
+ [[ -z "$file" ]] && continue
204
+ actual_array+=("$file")
205
+ done <<< "$actual_files"
206
+
207
+ # Calculate overlaps
208
+ local planned_and_touched=()
209
+ local planned_but_untouched=()
210
+ local unplanned_files=()
211
+
212
+ # Files that were both planned AND touched
213
+ for pfile in "${planned_array[@]}"; do
214
+ local found=false
215
+ for afile in "${actual_array[@]}"; do
216
+ if [[ "$pfile" == "$afile" ]]; then
217
+ found=true
218
+ break
219
+ fi
220
+ done
221
+ if [[ "$found" == "true" ]]; then
222
+ planned_and_touched+=("$pfile")
223
+ else
224
+ planned_but_untouched+=("$pfile")
225
+ fi
226
+ done
227
+
228
+ # Files that were touched but NOT planned
229
+ for afile in "${actual_array[@]}"; do
230
+ local found=false
231
+ for pfile in "${planned_array[@]}"; do
232
+ if [[ "$afile" == "$pfile" ]]; then
233
+ found=true
234
+ break
235
+ fi
236
+ done
237
+ if [[ "$found" == "false" ]]; then
238
+ unplanned_files+=("$afile")
239
+ fi
240
+ done
241
+
242
+ # Calculate scope creep score (unplanned / total, or 0 if no files changed)
243
+ local scope_creep_score=0
244
+ local total_files=$((${#planned_array[@]} + ${#unplanned_files[@]}))
245
+ if [[ "$total_files" -gt 0 ]]; then
246
+ scope_creep_score=$(echo "scale=2; ${#unplanned_files[@]} / $total_files" | bc 2>/dev/null || echo "0")
247
+ fi
248
+
249
+ # Build JSON report
250
+ local report="{
251
+ \"planned_files\": ["
252
+ local first=true
253
+ for file in "${planned_array[@]}"; do
254
+ if [[ "$first" == "false" ]]; then
255
+ report="${report},"
256
+ fi
257
+ report="${report}\"$(echo "$file" | sed 's/"/\\"/g')\""
258
+ first=false
259
+ done
260
+ report="${report}],
261
+ \"actual_files\": ["
262
+ first=true
263
+ for file in "${actual_array[@]}"; do
264
+ if [[ "$first" == "false" ]]; then
265
+ report="${report},"
266
+ fi
267
+ report="${report}\"$(echo "$file" | sed 's/"/\\"/g')\""
268
+ first=false
269
+ done
270
+ report="${report}],
271
+ \"planned_and_touched\": ["
272
+ first=true
273
+ for file in "${planned_and_touched[@]}"; do
274
+ if [[ "$first" == "false" ]]; then
275
+ report="${report},"
276
+ fi
277
+ report="${report}\"$(echo "$file" | sed 's/"/\\"/g')\""
278
+ first=false
279
+ done
280
+ report="${report}],
281
+ \"planned_but_untouched\": ["
282
+ first=true
283
+ for file in "${planned_but_untouched[@]}"; do
284
+ if [[ "$first" == "false" ]]; then
285
+ report="${report},"
286
+ fi
287
+ report="${report}\"$(echo "$file" | sed 's/"/\\"/g')\""
288
+ first=false
289
+ done
290
+ report="${report}],
291
+ \"unplanned_files\": ["
292
+ first=true
293
+ for file in "${unplanned_files[@]}"; do
294
+ if [[ "$first" == "false" ]]; then
295
+ report="${report},"
296
+ fi
297
+ report="${report}\"$(echo "$file" | sed 's/"/\\"/g')\""
298
+ first=false
299
+ done
300
+ report="${report}],
301
+ \"pr_stats\": $pr_stats,
302
+ \"scope_creep_score\": $scope_creep_score
303
+ }"
304
+
305
+ # Write report to artifacts directory
306
+ mkdir -p "$artifacts_dir"
307
+ echo "$report" > "$artifacts_dir/scope-report.json"
308
+ return 0
309
+ }
310
+
311
+ # ─── Format scope report for injection into review prompt ─────────────────
312
+ # Usage: format_scope_report_for_prompt "$artifacts_dir"
313
+ format_scope_report_for_prompt() {
314
+ local artifacts_dir="${1:-.}"
315
+ local report_file="$artifacts_dir/scope-report.json"
316
+
317
+ if [[ ! -f "$report_file" ]]; then
318
+ echo "No scope report available."
319
+ return 0
320
+ fi
321
+
322
+ local output="## Scope Analysis
323
+
324
+ "
325
+
326
+ # Extract and format planned vs actual
327
+ local planned_count
328
+ planned_count=$(jq '.planned_files | length' "$report_file" 2>/dev/null || echo "0")
329
+ local actual_count
330
+ actual_count=$(jq '.actual_files | length' "$report_file" 2>/dev/null || echo "0")
331
+
332
+ output="${output}**Planned files:** ${planned_count} | **Actual files changed:** ${actual_count}
333
+
334
+ "
335
+
336
+ # List planned files
337
+ output="${output}### Planned Files
338
+ "
339
+ local planned_list
340
+ planned_list=$(jq -r '.planned_files[]' "$report_file" 2>/dev/null || true)
341
+ if [[ -n "$planned_list" ]]; then
342
+ while IFS= read -r file; do
343
+ output="${output} - \`${file}\`
344
+ "
345
+ done <<< "$planned_list"
346
+ else
347
+ output="${output} (none specified)
348
+ "
349
+ fi
350
+
351
+ # List files planned but not touched
352
+ local untouched_count
353
+ untouched_count=$(jq '.planned_but_untouched | length' "$report_file" 2>/dev/null || echo "0")
354
+ if [[ "$untouched_count" -gt 0 ]]; then
355
+ output="${output}
356
+ ### Planned But Untouched
357
+ "
358
+ local untouched_list
359
+ untouched_list=$(jq -r '.planned_but_untouched[]' "$report_file" 2>/dev/null || true)
360
+ while IFS= read -r file; do
361
+ output="${output} - \`${file}\` (planned but not modified)
362
+ "
363
+ done <<< "$untouched_list"
364
+ fi
365
+
366
+ # List unplanned files (scope creep)
367
+ local unplanned_count
368
+ unplanned_count=$(jq '.unplanned_files | length' "$report_file" 2>/dev/null || echo "0")
369
+ if [[ "$unplanned_count" -gt 0 ]]; then
370
+ output="${output}
371
+ ### Unplanned Files (Scope Creep)
372
+ "
373
+ local unplanned_list
374
+ unplanned_list=$(jq -r '.unplanned_files[]' "$report_file" 2>/dev/null || true)
375
+ while IFS= read -r file; do
376
+ output="${output} - \`${file}\` (not in plan — justify or flag as creep)
377
+ "
378
+ done <<< "$unplanned_list"
379
+ fi
380
+
381
+ # PR stats
382
+ output="${output}
383
+ ### PR Statistics
384
+ "
385
+ local insertions deletions files_changed
386
+ insertions=$(jq '.pr_stats.insertions' "$report_file" 2>/dev/null || echo "0")
387
+ deletions=$(jq '.pr_stats.deletions' "$report_file" 2>/dev/null || echo "0")
388
+ files_changed=$(jq '.pr_stats.files_changed' "$report_file" 2>/dev/null || echo "0")
389
+
390
+ output="${output} - **Files changed:** ${files_changed}
391
+ - **Insertions:** +${insertions}
392
+ - **Deletions:** -${deletions}
393
+ - **Net change:** +$((insertions - deletions)) lines
394
+ "
395
+
396
+ # Scope creep score
397
+ local creep_score
398
+ creep_score=$(jq '.scope_creep_score' "$report_file" 2>/dev/null || echo "0")
399
+ output="${output}
400
+ **Scope creep score:** ${creep_score} (unplanned files as % of total)
401
+ "
402
+
403
+ echo -n "$output"
404
+ }
405
+
406
+ # ─── Check PR size against limit ─────────────────────────────────────────
407
+ # Returns 0 if under limit, 1 if over
408
+ # Usage: check_pr_size "$base_branch" "$max_lines"
409
+ check_pr_size() {
410
+ local base_branch="${1:-origin/main}"
411
+ local max_lines="${2:-500}"
412
+
413
+ # Get total line changes
414
+ local stat_output
415
+ stat_output=$(git diff --stat "$base_branch"...HEAD 2>/dev/null || true)
416
+
417
+ if [[ -z "$stat_output" ]]; then
418
+ return 0
419
+ fi
420
+
421
+ # Extract total from last line
422
+ local total_lines=0
423
+ local summary
424
+ summary=$(echo "$stat_output" | tail -1)
425
+
426
+ # Sum insertions and deletions
427
+ local insertions=0
428
+ local deletions=0
429
+
430
+ if [[ "$summary" =~ ([0-9]+)[[:space:]]+insertions?\(\+ ]]; then
431
+ insertions="${BASH_REMATCH[1]}"
432
+ fi
433
+
434
+ if [[ "$summary" =~ ([0-9]+)[[:space:]]+deletions?\(\- ]]; then
435
+ deletions="${BASH_REMATCH[1]}"
436
+ fi
437
+
438
+ total_lines=$((insertions + deletions))
439
+
440
+ if [[ "$total_lines" -gt "$max_lines" ]]; then
441
+ return 1
442
+ fi
443
+
444
+ return 0
445
+ }