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,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
|