#!/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 \`\`\`" printf " %-6s %s ... " "$short" "$model" local t0=$SECONDS local status # Build the body via temp files — both jq's --arg AND curl's # --data run into the kernel's argv limit (~128KB) when the diff # is large. Voice-ai full bundle was 156K and hit it twice. # Piping through files (and using --rawfile for jq) sidesteps both. local body_file user_file sys_file body_file=$(mktemp); user_file=$(mktemp); sys_file=$(mktemp) printf '%s' "$user" > "$user_file" printf '%s' "$SYSTEM" > "$sys_file" jq -n --arg model "$model" --rawfile sys "$sys_file" --rawfile user "$user_file" \ '{model:$model, max_tokens:4096, messages:[{role:"system",content:$sys},{role:"user",content:$user}]}' \ > "$body_file" 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-binary "@$body_file") rm -f "$body_file" "$user_file" "$sys_file" 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"