diff --git a/scripts/scrum_review.sh b/scripts/scrum_review.sh new file mode 100755 index 0000000..369f8ab --- /dev/null +++ b/scripts/scrum_review.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Cross-lineage scrum review driver. +# +# Per feedback_cross_lineage_review.md: Opus + Kimi + Qwen3-coder +# review the same diff via /v1/chat. Convergent findings (≥2 +# reviewers) = high-signal real bugs; single-reviewer = lineage +# catch. +# +# Usage: +# ./scripts/scrum_review.sh +# +# Outputs verbatim verdicts to: +# reports/scrum/_evidence//verdicts/_.md +set -euo pipefail +cd "$(dirname "$0")/.." + +DIFF_FILE="${1:-}" +BUNDLE_LABEL="${2:-}" +DATE="${SCRUM_DATE:-$(date +%Y-%m-%d)}" +GATEWAY="${LH_GATEWAY:-http://127.0.0.1:3110}" +OUT_DIR="reports/scrum/_evidence/${DATE}/verdicts" + +if [ -z "$DIFF_FILE" ] || [ ! -f "$DIFF_FILE" ]; then + echo "usage: $0 " + echo "example: $0 reports/scrum/_evidence/2026-04-30/diffs/bundle_A_config_refactor.diff config_refactor" + exit 1 +fi + +mkdir -p "$OUT_DIR" +DIFF_BYTES=$(wc -c < "$DIFF_FILE") +DIFF_LINES=$(wc -l < "$DIFF_FILE") +echo "[scrum] $BUNDLE_LABEL — $DIFF_LINES lines · $DIFF_BYTES bytes · 3 reviewers" + +# System prompt — same shape as the Rust auditor's review template, +# tightened per feedback_cross_lineage_review.md (lead with verdict). +SYSTEM='You are a senior code reviewer in a 3-lineage cross-review. +Your verdict feeds a convergent-finding gate (≥2 reviewers = real +bug). Be terse, evidence-based, and lead with the verdict. + +For each finding, output one block: + + SEVERITY: BLOCK | WARN | INFO + WHERE: : (or :) + WHAT: one-sentence description + WHY: one-sentence rationale grounded in the diff + +Severity ladder: +- BLOCK = ship-blocker. Wrong correctness, security flaw, broken + contract, lost test coverage, panic on real input, secret + leak. Worth blocking the PR. +- WARN = real but non-blocking. Race condition under specific + load, missing edge case, weak naming making future bugs + likely, regression risk in adjacent code. +- INFO = nit / style / better-name suggestion / dead-code remnant. + +Skip the analysis preamble. Lead with the first BLOCK/WARN/INFO +block. End with an empty "VERDICT:" line of "ship | ship-with-fixes +| hold" + ≤15 word summary. + +Never invent line numbers — only cite lines the diff shows.' + +REVIEWERS=( + "opus|opencode/claude-opus-4-7" + "kimi|openrouter/moonshotai/kimi-k2-0905" + "qwen|openrouter/qwen/qwen3-coder" +) + +DIFF_CONTENT=$(cat "$DIFF_FILE") + +run_review() { + local short="$1" model="$2" + local out="$OUT_DIR/${BUNDLE_LABEL}_${short}.md" + local user="Review the following diff. Bundle: $BUNDLE_LABEL. + +\`\`\`diff +$DIFF_CONTENT +\`\`\`" + local body + body=$(jq -n --arg model "$model" --arg sys "$SYSTEM" --arg user "$user" \ + '{model:$model, max_tokens:4096, messages:[{role:"system",content:$sys},{role:"user",content:$user}]}') + printf " %-6s %s ... " "$short" "$model" + local t0=$SECONDS + local status + status=$(curl -sS -o /tmp/scrum_resp.json -w '%{http_code}' --max-time 240 \ + -X POST "$GATEWAY/v1/chat" \ + -H 'Content-Type: application/json' \ + --data "$body") + local elapsed=$((SECONDS - t0)) + if [ "$status" != "200" ]; then + printf "✗ HTTP %s (%ds)\n" "$status" "$elapsed" + head -c 300 /tmp/scrum_resp.json + echo + return 1 + fi + local content latency tokens_in tokens_out + content=$(jq -r '.content' /tmp/scrum_resp.json) + latency=$(jq -r '.latency_ms' /tmp/scrum_resp.json) + tokens_in=$(jq -r '.input_tokens // 0' /tmp/scrum_resp.json) + tokens_out=$(jq -r '.output_tokens // 0' /tmp/scrum_resp.json) + { + echo "# Scrum review — $BUNDLE_LABEL — $short ($model)" + echo + echo "**Latency:** ${latency}ms · **Tokens:** ${tokens_in} in / ${tokens_out} out · **Date:** ${DATE}" + echo + echo "---" + echo + echo "$content" + } > "$out" + printf "✓ %dms · %dt-out → %s\n" "$latency" "$tokens_out" "$out" +} + +for r in "${REVIEWERS[@]}"; do + short="${r%%|*}" + model="${r#*|}" + run_review "$short" "$model" || true +done + +echo "[scrum] $BUNDLE_LABEL complete"