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.
- package/.claude/agents/code-reviewer.md +2 -0
- package/.claude/agents/devops-engineer.md +2 -0
- package/.claude/agents/doc-fleet-agent.md +2 -0
- package/.claude/agents/pipeline-agent.md +2 -0
- package/.claude/agents/shell-script-specialist.md +2 -0
- package/.claude/agents/test-specialist.md +2 -0
- package/.claude/hooks/agent-crash-capture.sh +32 -0
- package/.claude/hooks/post-tool-use.sh +3 -2
- package/.claude/hooks/pre-tool-use.sh +35 -3
- package/README.md +22 -8
- package/claude-code/hooks/config-change.sh +18 -0
- package/claude-code/hooks/instructions-reloaded.sh +7 -0
- package/claude-code/hooks/worktree-create.sh +25 -0
- package/claude-code/hooks/worktree-remove.sh +20 -0
- package/config/code-constitution.json +130 -0
- package/config/defaults.json +25 -2
- package/config/policy.json +1 -1
- package/dashboard/middleware/auth.ts +134 -0
- package/dashboard/middleware/constants.ts +21 -0
- package/dashboard/public/index.html +8 -6
- package/dashboard/public/styles.css +176 -97
- package/dashboard/routes/auth.ts +38 -0
- package/dashboard/server.ts +117 -25
- package/dashboard/services/config.ts +26 -0
- package/dashboard/services/db.ts +118 -0
- package/dashboard/src/canvas/pixel-agent.ts +298 -0
- package/dashboard/src/canvas/pixel-sprites.ts +440 -0
- package/dashboard/src/canvas/shipyard-effects.ts +367 -0
- package/dashboard/src/canvas/shipyard-scene.ts +616 -0
- package/dashboard/src/canvas/submarine-layout.ts +267 -0
- package/dashboard/src/components/header.ts +8 -7
- package/dashboard/src/core/api.ts +5 -0
- package/dashboard/src/core/router.ts +1 -0
- package/dashboard/src/design/submarine-theme.ts +253 -0
- package/dashboard/src/main.ts +2 -0
- package/dashboard/src/types/api.ts +12 -1
- package/dashboard/src/views/activity.ts +2 -1
- package/dashboard/src/views/metrics.ts +69 -1
- package/dashboard/src/views/shipyard.ts +39 -0
- package/dashboard/types/index.ts +166 -0
- package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
- package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
- package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
- package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
- package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
- package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
- package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
- package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
- package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
- package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
- package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
- package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
- package/docs/research/RESEARCH_INDEX.md +439 -0
- package/docs/research/RESEARCH_SOURCES.md +440 -0
- package/docs/research/RESEARCH_SUMMARY.txt +275 -0
- package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
- package/package.json +2 -2
- package/scripts/lib/adaptive-model.sh +427 -0
- package/scripts/lib/adaptive-timeout.sh +316 -0
- package/scripts/lib/audit-trail.sh +309 -0
- package/scripts/lib/auto-recovery.sh +471 -0
- package/scripts/lib/bandit-selector.sh +431 -0
- package/scripts/lib/bootstrap.sh +104 -2
- package/scripts/lib/causal-graph.sh +455 -0
- package/scripts/lib/compat.sh +126 -0
- package/scripts/lib/compound-audit.sh +337 -0
- package/scripts/lib/constitutional.sh +454 -0
- package/scripts/lib/context-budget.sh +359 -0
- package/scripts/lib/convergence.sh +594 -0
- package/scripts/lib/cost-optimizer.sh +634 -0
- package/scripts/lib/daemon-adaptive.sh +14 -2
- package/scripts/lib/daemon-dispatch.sh +106 -17
- package/scripts/lib/daemon-failure.sh +34 -4
- package/scripts/lib/daemon-patrol.sh +25 -4
- package/scripts/lib/daemon-poll-github.sh +361 -0
- package/scripts/lib/daemon-poll-health.sh +299 -0
- package/scripts/lib/daemon-poll.sh +27 -611
- package/scripts/lib/daemon-state.sh +119 -66
- package/scripts/lib/daemon-triage.sh +10 -0
- package/scripts/lib/dod-scorecard.sh +442 -0
- package/scripts/lib/error-actionability.sh +300 -0
- package/scripts/lib/formal-spec.sh +461 -0
- package/scripts/lib/helpers.sh +180 -5
- package/scripts/lib/intent-analysis.sh +409 -0
- package/scripts/lib/loop-convergence.sh +350 -0
- package/scripts/lib/loop-iteration.sh +682 -0
- package/scripts/lib/loop-progress.sh +48 -0
- package/scripts/lib/loop-restart.sh +185 -0
- package/scripts/lib/memory-effectiveness.sh +506 -0
- package/scripts/lib/mutation-executor.sh +352 -0
- package/scripts/lib/outcome-feedback.sh +521 -0
- package/scripts/lib/pipeline-cli.sh +336 -0
- package/scripts/lib/pipeline-commands.sh +1216 -0
- package/scripts/lib/pipeline-detection.sh +101 -3
- package/scripts/lib/pipeline-execution.sh +897 -0
- package/scripts/lib/pipeline-github.sh +28 -3
- package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
- package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
- package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
- package/scripts/lib/pipeline-intelligence.sh +104 -1138
- package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
- package/scripts/lib/pipeline-quality-checks.sh +17 -711
- package/scripts/lib/pipeline-quality-gates.sh +563 -0
- package/scripts/lib/pipeline-stages-build.sh +730 -0
- package/scripts/lib/pipeline-stages-delivery.sh +965 -0
- package/scripts/lib/pipeline-stages-intake.sh +1133 -0
- package/scripts/lib/pipeline-stages-monitor.sh +407 -0
- package/scripts/lib/pipeline-stages-review.sh +1022 -0
- package/scripts/lib/pipeline-stages.sh +161 -2901
- package/scripts/lib/pipeline-state.sh +36 -5
- package/scripts/lib/pipeline-util.sh +487 -0
- package/scripts/lib/policy-learner.sh +438 -0
- package/scripts/lib/process-reward.sh +493 -0
- package/scripts/lib/project-detect.sh +649 -0
- package/scripts/lib/quality-profile.sh +334 -0
- package/scripts/lib/recruit-commands.sh +885 -0
- package/scripts/lib/recruit-learning.sh +739 -0
- package/scripts/lib/recruit-roles.sh +648 -0
- package/scripts/lib/reward-aggregator.sh +458 -0
- package/scripts/lib/rl-optimizer.sh +362 -0
- package/scripts/lib/root-cause.sh +427 -0
- package/scripts/lib/scope-enforcement.sh +445 -0
- package/scripts/lib/session-restart.sh +493 -0
- package/scripts/lib/skill-memory.sh +300 -0
- package/scripts/lib/skill-registry.sh +775 -0
- package/scripts/lib/spec-driven.sh +476 -0
- package/scripts/lib/test-helpers.sh +18 -7
- package/scripts/lib/test-holdout.sh +429 -0
- package/scripts/lib/test-optimizer.sh +511 -0
- package/scripts/shipwright-file-suggest.sh +45 -0
- package/scripts/skills/adversarial-quality.md +61 -0
- package/scripts/skills/api-design.md +44 -0
- package/scripts/skills/architecture-design.md +50 -0
- package/scripts/skills/brainstorming.md +43 -0
- package/scripts/skills/data-pipeline.md +44 -0
- package/scripts/skills/deploy-safety.md +64 -0
- package/scripts/skills/documentation.md +38 -0
- package/scripts/skills/frontend-design.md +45 -0
- package/scripts/skills/generated/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
- package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
- package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
- package/scripts/skills/generated/cli-version-management.md +29 -0
- package/scripts/skills/generated/collection-system-validation.md +99 -0
- package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
- package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
- package/scripts/skills/generated/test-parallelization-detection.md +65 -0
- package/scripts/skills/observability.md +79 -0
- package/scripts/skills/performance.md +48 -0
- package/scripts/skills/pr-quality.md +49 -0
- package/scripts/skills/product-thinking.md +43 -0
- package/scripts/skills/security-audit.md +49 -0
- package/scripts/skills/systematic-debugging.md +40 -0
- package/scripts/skills/testing-strategy.md +47 -0
- package/scripts/skills/two-stage-review.md +52 -0
- package/scripts/skills/validation-thoroughness.md +55 -0
- package/scripts/sw +9 -3
- package/scripts/sw-activity.sh +9 -8
- package/scripts/sw-adaptive.sh +8 -7
- package/scripts/sw-adversarial.sh +2 -1
- package/scripts/sw-architecture-enforcer.sh +3 -1
- package/scripts/sw-auth.sh +12 -2
- package/scripts/sw-autonomous.sh +5 -1
- package/scripts/sw-changelog.sh +4 -1
- package/scripts/sw-checkpoint.sh +2 -1
- package/scripts/sw-ci.sh +15 -6
- package/scripts/sw-cleanup.sh +4 -26
- package/scripts/sw-code-review.sh +45 -20
- package/scripts/sw-connect.sh +2 -1
- package/scripts/sw-context.sh +2 -1
- package/scripts/sw-cost.sh +107 -5
- package/scripts/sw-daemon.sh +71 -11
- package/scripts/sw-dashboard.sh +3 -1
- package/scripts/sw-db.sh +71 -20
- package/scripts/sw-decide.sh +8 -2
- package/scripts/sw-decompose.sh +360 -17
- package/scripts/sw-deps.sh +4 -1
- package/scripts/sw-developer-simulation.sh +4 -1
- package/scripts/sw-discovery.sh +378 -5
- package/scripts/sw-doc-fleet.sh +4 -1
- package/scripts/sw-docs-agent.sh +3 -1
- package/scripts/sw-docs.sh +2 -1
- package/scripts/sw-doctor.sh +453 -2
- package/scripts/sw-dora.sh +4 -1
- package/scripts/sw-durable.sh +12 -7
- package/scripts/sw-e2e-orchestrator.sh +17 -16
- package/scripts/sw-eventbus.sh +13 -4
- package/scripts/sw-evidence.sh +364 -12
- package/scripts/sw-feedback.sh +550 -9
- package/scripts/sw-fix.sh +20 -1
- package/scripts/sw-fleet-discover.sh +6 -2
- package/scripts/sw-fleet-viz.sh +9 -4
- package/scripts/sw-fleet.sh +5 -1
- package/scripts/sw-github-app.sh +18 -4
- package/scripts/sw-github-checks.sh +3 -2
- package/scripts/sw-github-deploy.sh +3 -2
- package/scripts/sw-github-graphql.sh +18 -7
- package/scripts/sw-guild.sh +5 -1
- package/scripts/sw-heartbeat.sh +5 -30
- package/scripts/sw-hello.sh +67 -0
- package/scripts/sw-hygiene.sh +10 -3
- package/scripts/sw-incident.sh +273 -5
- package/scripts/sw-init.sh +18 -2
- package/scripts/sw-instrument.sh +10 -2
- package/scripts/sw-intelligence.sh +44 -7
- package/scripts/sw-jira.sh +5 -1
- package/scripts/sw-launchd.sh +2 -1
- package/scripts/sw-linear.sh +4 -1
- package/scripts/sw-logs.sh +4 -1
- package/scripts/sw-loop.sh +436 -1076
- package/scripts/sw-memory.sh +357 -3
- package/scripts/sw-mission-control.sh +6 -1
- package/scripts/sw-model-router.sh +483 -27
- package/scripts/sw-otel.sh +15 -4
- package/scripts/sw-oversight.sh +14 -5
- package/scripts/sw-patrol-meta.sh +334 -0
- package/scripts/sw-pipeline-composer.sh +7 -1
- package/scripts/sw-pipeline-vitals.sh +12 -6
- package/scripts/sw-pipeline.sh +54 -2653
- package/scripts/sw-pm.sh +16 -8
- package/scripts/sw-pr-lifecycle.sh +2 -1
- package/scripts/sw-predictive.sh +17 -5
- package/scripts/sw-prep.sh +185 -2
- package/scripts/sw-ps.sh +5 -25
- package/scripts/sw-public-dashboard.sh +17 -4
- package/scripts/sw-quality.sh +14 -6
- package/scripts/sw-reaper.sh +8 -25
- package/scripts/sw-recruit.sh +156 -2303
- package/scripts/sw-regression.sh +19 -12
- package/scripts/sw-release-manager.sh +3 -1
- package/scripts/sw-release.sh +4 -1
- package/scripts/sw-remote.sh +3 -1
- package/scripts/sw-replay.sh +7 -1
- package/scripts/sw-retro.sh +158 -1
- package/scripts/sw-review-rerun.sh +3 -1
- package/scripts/sw-scale.sh +14 -5
- package/scripts/sw-security-audit.sh +6 -1
- package/scripts/sw-self-optimize.sh +173 -6
- package/scripts/sw-session.sh +9 -3
- package/scripts/sw-setup.sh +3 -1
- package/scripts/sw-stall-detector.sh +406 -0
- package/scripts/sw-standup.sh +15 -7
- package/scripts/sw-status.sh +3 -1
- package/scripts/sw-strategic.sh +14 -6
- package/scripts/sw-stream.sh +13 -4
- package/scripts/sw-swarm.sh +20 -7
- package/scripts/sw-team-stages.sh +13 -6
- package/scripts/sw-templates.sh +7 -31
- package/scripts/sw-testgen.sh +17 -6
- package/scripts/sw-tmux-pipeline.sh +4 -1
- package/scripts/sw-tmux-role-color.sh +2 -0
- package/scripts/sw-tmux-status.sh +1 -1
- package/scripts/sw-tmux.sh +37 -1
- package/scripts/sw-trace.sh +3 -1
- package/scripts/sw-tracker-github.sh +3 -0
- package/scripts/sw-tracker-jira.sh +3 -0
- package/scripts/sw-tracker-linear.sh +3 -0
- package/scripts/sw-tracker.sh +3 -1
- package/scripts/sw-triage.sh +3 -2
- package/scripts/sw-upgrade.sh +3 -1
- package/scripts/sw-ux.sh +5 -2
- package/scripts/sw-webhook.sh +5 -2
- package/scripts/sw-widgets.sh +9 -4
- package/scripts/sw-worktree.sh +15 -3
- package/scripts/test-skill-injection.sh +1233 -0
- package/templates/pipelines/autonomous.json +27 -3
- package/templates/pipelines/cost-aware.json +34 -8
- package/templates/pipelines/deployed.json +12 -0
- package/templates/pipelines/enterprise.json +12 -0
- package/templates/pipelines/fast.json +6 -0
- package/templates/pipelines/full.json +27 -3
- package/templates/pipelines/hotfix.json +6 -0
- package/templates/pipelines/standard.json +12 -0
- 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
|
+
}
|