golangLAKEHOUSE/scripts/scrum_review.sh
root e4ee0029c0 scrum_review.sh: reusable 3-lineage cross-review driver
Bash driver wrapping /v1/chat for Opus + Kimi + Qwen3-coder review
runs. Used today to scrum the 4-phase wave (1,624 LoC of chatd +
config-refactor + Rust cleanup) and caught 2 BLOCKs + 2 WARNs.

Usage:
  ./scripts/scrum_review.sh <bundle.diff> <bundle_label>

Output: reports/scrum/_evidence/<DATE>/verdicts/<bundle>_<reviewer>.md
verbatim, per the evidence-only convention. Per-reviewer latency +
token counts captured in the report header.

System prompt enforces the BLOCK/WARN/INFO + WHERE/WHAT/WHY shape
per feedback_cross_lineage_review.md — leads with verdict, no
preamble (Kimi tends to spend tokens thinking otherwise).

Reviewer fleet matches project_golang_lakehouse.md "Scrum routing":
- opencode/claude-opus-4-7
- openrouter/moonshotai/kimi-k2-0905
- openrouter/qwen/qwen3-coder

This is the first dogfood of chatd as the scrum vehicle — eats its
own /v1/chat dispatcher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 00:29:36 -05:00

119 lines
3.9 KiB
Bash
Executable File

#!/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 <bundle.diff> <bundle_label>
#
# Outputs verbatim verdicts to:
# reports/scrum/_evidence/<DATE>/verdicts/<bundle>_<reviewer>.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 <diff_file> <bundle_label>"
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: <file>:<line> (or <file>:<symbol>)
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"