feat: Significant code review suite rewrite with improved graph agent features
This commit is contained in:
41 files changed
+3572
-709
No files matched your search
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Report-contract gate for each domain-reviewer's output.
|
||||
|
||||
"Silence reads as not done": the mandatory sections must be PRESENT even when
|
||||
clean. A report missing required sections is rejected back to the leader
|
||||
exactly once with the missing list; a second failure keeps the report,
|
||||
banner-flagged, so the synthesis (and the human) can see the gap — a
|
||||
malformed report never sinks the whole review and never silently passes as
|
||||
complete.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
REQUIRED = [
|
||||
("## Domain:", "the `## Domain:` header"),
|
||||
("### Findings", "the `### Findings` section"),
|
||||
("### Mandatory checks", "the `### Mandatory checks` section (report blast radius / test adequacy / guard symmetry even when clean)"),
|
||||
("DOMAIN_REVIEW_COMPLETE", "the `DOMAIN_REVIEW_COMPLETE` sentinel"),
|
||||
]
|
||||
MAX_RETRIES = 1
|
||||
|
||||
|
||||
def load_state():
|
||||
if path := os.environ.get("GRAPH_STATE_FILE"):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
report = state.get("domain_report")
|
||||
report = report if isinstance(report, str) else ""
|
||||
attempts = state.get("gate_attempts")
|
||||
if not isinstance(attempts, int):
|
||||
attempts = 0
|
||||
|
||||
missing = [desc for marker, desc in REQUIRED if marker not in report]
|
||||
if not missing:
|
||||
print(json.dumps({}))
|
||||
return
|
||||
|
||||
if attempts < MAX_RETRIES:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"_next": "review_domain",
|
||||
"gate_attempts": attempts + 1,
|
||||
"gate_feedback": (
|
||||
"- COMPLETENESS GATE REJECTED your previous report — missing: "
|
||||
+ "; ".join(missing)
|
||||
+ ". Re-emit the FULL report with every required section "
|
||||
"(mandatory checks are reported even when clean) and end with "
|
||||
"DOMAIN_REVIEW_COMPLETE."
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
group = state.get("domain_group")
|
||||
domain = group.get("domain", "?") if isinstance(group, dict) else "?"
|
||||
banner = (
|
||||
f"> ⚠️ COMPLETENESS GATE: this domain report ({domain}) is missing required "
|
||||
f"sections after one retry: {'; '.join(missing)}. Treat the missing checks as "
|
||||
"NOT DONE, not as clean.\n\n"
|
||||
)
|
||||
print(json.dumps({"domain_report": banner + report}))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as e: # noqa: BLE001 — a gate crash must never sink the map
|
||||
print(json.dumps({"domain_report": f"> ⚠️ COMPLETENESS GATE ERROR: {e}\n\n(report unavailable)"}))
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exact-cover gate for the refined domain groups.
|
||||
|
||||
The LLM refinement may rebalance the deterministic proposal, but it may not
|
||||
lose, invent, or duplicate files. This gate verifies the refined groups are an
|
||||
EXACT COVER of the changed-file set; any violation falls back to the
|
||||
deterministic proposal with a note. The LLM can shape the grouping, never
|
||||
shrink it. Emits `group_items` — the map node's item list.
|
||||
|
||||
Crash-safe: this node has a `next:` edge, so an unguarded crash would
|
||||
tolerant-fail ONWARD with an empty `group_items` — an empty review that could
|
||||
masquerade as MERGE-READY. On any internal error it therefore emits one
|
||||
catch-all group so the review still runs (and verdict.py's pipeline tripwire
|
||||
catches the empty-report case independently).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
def load_state():
|
||||
if path := os.environ.get("GRAPH_STATE_FILE"):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
changed = state.get("changed_files") or []
|
||||
proposed = state.get("proposed_groups") or []
|
||||
refined = state.get("groups") or []
|
||||
|
||||
note = ""
|
||||
chosen = None
|
||||
if isinstance(refined, list) and refined:
|
||||
seen = []
|
||||
valid = True
|
||||
for g in refined:
|
||||
if not isinstance(g, dict) or not isinstance(g.get("files"), list) or not g.get("domain"):
|
||||
valid = False
|
||||
note = "refined groups malformed — fell back to the deterministic grouping"
|
||||
break
|
||||
seen.extend(g["files"])
|
||||
if valid:
|
||||
if sorted(seen) != sorted(changed):
|
||||
missing = sorted(set(changed) - set(seen))
|
||||
extra = sorted(set(seen) - set(changed))
|
||||
dupes = sorted({f for f in seen if seen.count(f) > 1})
|
||||
note = (
|
||||
"refined groups failed the exact-cover check "
|
||||
f"(missing={missing[:5]} extra={extra[:5]} duplicated={dupes[:5]}) — "
|
||||
"fell back to the deterministic grouping"
|
||||
)
|
||||
else:
|
||||
chosen = [{"domain": g["domain"], "files": g["files"]} for g in refined]
|
||||
if chosen is None:
|
||||
chosen = [{"domain": g["domain"], "files": g["files"]} for g in proposed if isinstance(g, dict)]
|
||||
|
||||
print(json.dumps({"group_items": chosen, "groups_note": note}))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as e: # noqa: BLE001 — a crash must never tolerant-fail into an empty review
|
||||
try:
|
||||
files = load_state().get("changed_files") or []
|
||||
except Exception: # noqa: BLE001
|
||||
files = []
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"group_items": [{"domain": "all-changes", "files": files}],
|
||||
"groups_note": f"cover gate error: {e} — fell back to a single catch-all group",
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Downstream-consumers sweep for the code-reviewer graph.
|
||||
|
||||
Runs only when consumer-facing contract surface changed. Extracts the most
|
||||
specific changed contract tokens from the diff deterministically (route path
|
||||
literals, proto message/service/rpc names, exported symbols), greps sibling
|
||||
checkouts under `consumers_root`, and runs a bounded org code search via gh.
|
||||
Hits are reported for the synthesis to raise as 🔴 findings naming the
|
||||
consuming repo + file:line. Degrades silently when unconfigured/offline;
|
||||
public-library caveats are noted, not guessed at.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
def load_state():
|
||||
if path := os.environ.get("GRAPH_STATE_FILE"):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
state = load_state()
|
||||
proj = os.path.abspath(state.get("project_dir") or ".")
|
||||
spec = state.get("resolved_diff_spec") or "worktree"
|
||||
consumers_root = os.path.expanduser((state.get("consumers_root") or "").strip())
|
||||
github_org = (state.get("github_org") or "").strip()
|
||||
|
||||
if not state.get("consumer_surface"):
|
||||
print(json.dumps({"downstream_notes": "(skipped — no consumer-facing contract surface in the diff)"}))
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
def diff_text():
|
||||
args = {"staged": ["--cached"], "worktree": ["HEAD"], "HEAD~1": ["HEAD~1"]}.get(spec, [spec])
|
||||
r = subprocess.run(["git", "-C", proj, "diff", *args], capture_output=True, text=True, timeout=90)
|
||||
return r.stdout if r.returncode == 0 else ""
|
||||
|
||||
|
||||
try:
|
||||
changed = [
|
||||
l[1:] for l in diff_text().splitlines()
|
||||
if (l.startswith("+") or l.startswith("-")) and not l.startswith(("+++", "---"))
|
||||
]
|
||||
text = "\n".join(changed)
|
||||
tokens = set()
|
||||
tokens.update(re.findall(r'["\'](/[a-z0-9][a-z0-9/_{}.:-]{5,})["\']', text)) # route paths
|
||||
tokens.update(re.findall(r"^\s*(?:message|service|rpc)\s+([A-Z][A-Za-z0-9_]{5,})", text, re.M)) # proto names
|
||||
tokens.update(re.findall(r"^\s*(?:pub\s+fn|pub\s+struct|func|export\s+(?:function|const|class))\s+([A-Z][A-Za-z0-9_]{5,})", text, re.M))
|
||||
tokens = sorted(tokens, key=len, reverse=True)[:5]
|
||||
|
||||
notes = []
|
||||
if not tokens:
|
||||
notes.append("(no specific contract tokens extractable from the diff — sweep skipped)")
|
||||
else:
|
||||
notes.append(f"contract tokens probed: {tokens}")
|
||||
if consumers_root and os.path.isdir(consumers_root):
|
||||
self_name = os.path.basename(proj)
|
||||
for tok in tokens:
|
||||
r = subprocess.run(
|
||||
["grep", "-rIl", "--exclude-dir=.git", "--exclude-dir=node_modules",
|
||||
"--exclude-dir=target", "--exclude-dir=vendor", tok, consumers_root],
|
||||
capture_output=True, text=True, timeout=60,
|
||||
)
|
||||
hits = [
|
||||
h for h in r.stdout.splitlines()
|
||||
if h.strip() and f"/{self_name}/" not in h and not h.startswith(proj)
|
||||
][:4]
|
||||
for h in hits:
|
||||
notes.append(f"CONSUMER HIT (sibling checkout): {h} references '{tok}' — "
|
||||
"a changed/removed contract element with a live consumer is a 🔴 finding")
|
||||
elif consumers_root:
|
||||
notes.append(f"(consumers_root '{consumers_root}' not found — local probe skipped)")
|
||||
else:
|
||||
notes.append("(consumers_root unset — local probe skipped)")
|
||||
|
||||
if github_org and shutil.which("gh"):
|
||||
env = dict(os.environ)
|
||||
env.pop("CLICOLOR_FORCE", None)
|
||||
env.pop("FORCE_COLOR", None)
|
||||
env["GH_NO_UPDATE_NOTIFIER"] = "1"
|
||||
for tok in tokens:
|
||||
r = subprocess.run(
|
||||
["gh", "api", "search/code", "-f", f'q=org:{github_org} "{tok}"',
|
||||
"--jq", r'.items[:4][] | "\(.repository.full_name): \(.path)"'],
|
||||
capture_output=True, text=True, timeout=30, env=env,
|
||||
)
|
||||
if r.returncode == 0 and r.stdout.strip():
|
||||
for line in r.stdout.strip().splitlines():
|
||||
notes.append(f"CONSUMER HIT (org search): {line} references '{tok}'")
|
||||
elif github_org:
|
||||
notes.append("(gh unavailable — org code search skipped)")
|
||||
else:
|
||||
notes.append("(github_org unset — org code search skipped)")
|
||||
notes.append("Public-library caveat: consumers of a PUBLIC library are unenumerable — "
|
||||
"for exported-API changes the check degrades to semver/changelog discipline.")
|
||||
print(json.dumps({"downstream_notes": "\n".join(notes)}))
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(json.dumps({"downstream_notes": f"(sweep degraded: {e})"}))
|
||||
@@ -0,0 +1,281 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deterministic review facts for the code-reviewer graph.
|
||||
|
||||
Resolves the diff, the changed-file list, the quality bar (caller-passed →
|
||||
plan frontmatter → detected default), the conventions pointer, review-miss
|
||||
ledger matches, the repo-history sweep (fix/revert clusters, bounded gh
|
||||
PR-comment pull), and the deterministic verdict-trigger signals. Pure
|
||||
git/gh/filesystem — no LLM. Failures degrade into `facts_note`; they never
|
||||
sink the review.
|
||||
"""
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
def load_state():
|
||||
if path := os.environ.get("GRAPH_STATE_FILE"):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
state = load_state()
|
||||
proj = state.get("project_dir") or "."
|
||||
spec = (state.get("diff_spec") or "auto").strip()
|
||||
notes = []
|
||||
out = {}
|
||||
|
||||
|
||||
def git(*args, timeout=60):
|
||||
r = subprocess.run(["git", "-C", proj, *args], capture_output=True, text=True, timeout=timeout)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError((r.stderr or r.stdout).strip()[:300])
|
||||
return r.stdout
|
||||
|
||||
|
||||
def scrubbed_env():
|
||||
env = dict(os.environ)
|
||||
env.pop("CLICOLOR_FORCE", None)
|
||||
env.pop("FORCE_COLOR", None)
|
||||
env["GH_NO_UPDATE_NOTIFIER"] = "1"
|
||||
env["NO_COLOR"] = "1"
|
||||
return env
|
||||
|
||||
|
||||
# --- diff resolution (get_diff semantics) -----------------------------------
|
||||
resolved = spec
|
||||
files = []
|
||||
try:
|
||||
if spec in ("auto", ""):
|
||||
names = git("diff", "--cached", "--numstat")
|
||||
resolved = "staged"
|
||||
if not names.strip():
|
||||
names = git("diff", "--numstat")
|
||||
resolved = "worktree"
|
||||
if not names.strip():
|
||||
names = git("diff", "HEAD~1", "--numstat")
|
||||
resolved = "HEAD~1"
|
||||
elif spec == "staged":
|
||||
names = git("diff", "--cached", "--numstat")
|
||||
elif spec == "worktree":
|
||||
names = git("diff", "HEAD", "--numstat")
|
||||
else:
|
||||
names = git("diff", spec, "--numstat")
|
||||
for line in names.splitlines():
|
||||
parts = line.split("\t")
|
||||
if len(parts) == 3:
|
||||
added, deleted, path = parts
|
||||
if added == "-" and deleted == "-":
|
||||
notes.append(f"binary file skipped: {path}")
|
||||
continue
|
||||
files.append(path.strip())
|
||||
except Exception as e: # noqa: BLE001
|
||||
notes.append(f"diff resolution failed for spec {spec!r}: {e}")
|
||||
|
||||
out["resolved_diff_spec"] = resolved
|
||||
out["changed_files"] = files[:300]
|
||||
out["file_count"] = len(files)
|
||||
|
||||
|
||||
def diff_text():
|
||||
try:
|
||||
if resolved == "staged":
|
||||
return git("diff", "--cached", timeout=90)
|
||||
if resolved == "worktree":
|
||||
return git("diff", "HEAD", timeout=90) if spec == "worktree" else git("diff", timeout=90)
|
||||
if resolved == "HEAD~1":
|
||||
return git("diff", "HEAD~1", timeout=90)
|
||||
return git("diff", resolved, timeout=90)
|
||||
except Exception: # noqa: BLE001
|
||||
return ""
|
||||
|
||||
|
||||
dtext = diff_text()
|
||||
added_lines = "\n".join(
|
||||
l for l in dtext.splitlines() if l.startswith("+") and not l.startswith("+++")
|
||||
)
|
||||
|
||||
# --- quality bar resolution --------------------------------------------------
|
||||
rigor_in = (state.get("rigor_in") or state.get("rigor") or "").strip()
|
||||
surfaces_in = (state.get("surfaces_in") or state.get("surfaces") or "").strip()
|
||||
resolved_rigor, resolved_surfaces, provenance = "production", "", "detected-default"
|
||||
if rigor_in or surfaces_in:
|
||||
resolved_rigor = rigor_in or "production"
|
||||
resolved_surfaces = surfaces_in
|
||||
provenance = "passed"
|
||||
else:
|
||||
for plan in sorted(glob.glob(os.path.join(proj, "plans", "PLAN-*.md"))):
|
||||
try:
|
||||
with open(plan) as f:
|
||||
head = f.read(4000)
|
||||
m = re.match(r"\A---\n(.*?)\n---", head, re.S)
|
||||
if m and re.search(r"^status:\s*active\s*$", m.group(1), re.M):
|
||||
rig = re.search(r"^rigor:\s*(\S+)", m.group(1), re.M)
|
||||
sur = re.search(r"^surfaces:\s*(.+)$", m.group(1), re.M)
|
||||
if rig:
|
||||
resolved_rigor = rig.group(1).strip()
|
||||
if sur:
|
||||
resolved_surfaces = sur.group(1).strip("[] \n")
|
||||
provenance = "plan"
|
||||
break
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
SURFACE_PATTERNS = [
|
||||
("db-migration", r"migrations?/|\.sql$|schema\.(rb|prisma|sql)"),
|
||||
("iac", r"\.tf$|helm|charts?/|Dockerfile|docker-compose|k8s/|manifests?/"),
|
||||
("ci-cd", r"\.github/workflows/|\.gitlab-ci|Jenkinsfile|\.circleci/"),
|
||||
("worker", r"workers?/|jobs?/|consumers?/|cron|celery|sidekiq|river"),
|
||||
("rest-api", r"routes?|handlers?|controllers?|endpoints?|\bapi\b|\.proto$|openapi|swagger|graphql|resolvers?"),
|
||||
("cli", r"\bcmd/|\bcli\b"),
|
||||
("library", r"go\.mod$|package\.json$|Cargo\.toml$|pyproject\.toml$|setup\.py$|__init__\.py$"),
|
||||
]
|
||||
if provenance == "detected-default" and files:
|
||||
detected = []
|
||||
for surface, pat in SURFACE_PATTERNS:
|
||||
if any(re.search(pat, f, re.I) for f in files):
|
||||
detected.append(surface)
|
||||
resolved_surfaces = ", ".join(detected)
|
||||
|
||||
out["resolved_rigor"] = resolved_rigor
|
||||
out["resolved_surfaces"] = resolved_surfaces
|
||||
out["bar_provenance"] = provenance
|
||||
|
||||
# --- conventions pointer ------------------------------------------------------
|
||||
conv = []
|
||||
for pat in ("CLAUDE.md", "COYOTE.md", "CONTRIBUTING.md", ".golangci*", ".eslintrc*",
|
||||
"rustfmt.toml", ".rubocop.yml", "ruff.toml", ".editorconfig", "Makefile"):
|
||||
conv.extend(sorted(glob.glob(os.path.join(proj, pat)))[:2])
|
||||
out["conventions"] = ", ".join(os.path.relpath(p, proj) for p in conv) or "none found"
|
||||
|
||||
# --- review-miss ledger -------------------------------------------------------
|
||||
ledger_text = ""
|
||||
for cand in (
|
||||
os.path.join(os.path.expanduser("~"), ".config", "coyote", "share", "review-misses.md"),
|
||||
os.path.join(os.path.expanduser("~"), ".config", "coyote", "review-misses.md"),
|
||||
):
|
||||
if os.path.isfile(cand):
|
||||
try:
|
||||
with open(cand) as f:
|
||||
ledger_text = f.read()
|
||||
except OSError:
|
||||
pass
|
||||
break
|
||||
|
||||
matches = []
|
||||
if ledger_text:
|
||||
try:
|
||||
repo_name = os.path.basename(git("rev-parse", "--show-toplevel").strip())
|
||||
except Exception: # noqa: BLE001
|
||||
repo_name = os.path.basename(os.path.abspath(proj))
|
||||
keywords = {repo_name.lower()}
|
||||
keywords.update(s.strip().lower() for s in resolved_surfaces.split(",") if s.strip())
|
||||
for f in files:
|
||||
keywords.update(p.lower() for p in re.split(r"[/_.-]", f) if len(p) >= 4)
|
||||
entries = re.split(r"(?=^## MISS-)", ledger_text, flags=re.M)
|
||||
for entry in entries:
|
||||
if not entry.startswith("## MISS-"):
|
||||
continue
|
||||
body = entry.lower()
|
||||
if any(k in body for k in keywords):
|
||||
lines = entry.strip().splitlines()
|
||||
matches.append("\n".join(lines[:8]))
|
||||
if len(matches) >= 8:
|
||||
break
|
||||
out["ledger_matches"] = "\n\n".join(matches) if matches else "(no matching ledger entries)"
|
||||
|
||||
# --- repo-history sweep -------------------------------------------------------
|
||||
history = []
|
||||
fragile = []
|
||||
try:
|
||||
if files:
|
||||
log = git("log", "--oneline", "-15", "--", *files[:100])
|
||||
history.append(log.strip()[:2000])
|
||||
per_file = {}
|
||||
for f in files[:50]:
|
||||
try:
|
||||
fl = git("log", "--oneline", "-10", "--", f)
|
||||
except Exception: # noqa: BLE001
|
||||
continue
|
||||
hits = [l for l in fl.splitlines() if re.search(r"\b(fix|revert|hotfix)\b", l, re.I)]
|
||||
if len(hits) >= 2:
|
||||
fragile.append(f)
|
||||
per_file[f] = hits[:2]
|
||||
for f, hits in per_file.items():
|
||||
history.append(f"FRAGILE: {f} — {len(hits)}+ fix/revert commits recently — raise scrutiny")
|
||||
except Exception as e: # noqa: BLE001
|
||||
notes.append(f"history sweep failed: {e}")
|
||||
|
||||
# gh one-level-deeper: PR review comments for fragile files (bounded, silent skip)
|
||||
try:
|
||||
if fragile:
|
||||
remote = git("remote", "get-url", "origin").strip()
|
||||
m = re.search(r"github\.com[:/]([^/]+/[^/.]+)", remote)
|
||||
if m:
|
||||
repo_slug = m.group(1)
|
||||
env = scrubbed_env()
|
||||
shas = []
|
||||
for f in fragile[:2]:
|
||||
lg = git("log", "--format=%H", "-2", "--", f)
|
||||
shas.extend(lg.split())
|
||||
pr_nums = []
|
||||
for sha in shas[:3]:
|
||||
r = subprocess.run(
|
||||
["gh", "api", f"repos/{repo_slug}/commits/{sha}/pulls", "--jq", ".[].number"],
|
||||
capture_output=True, text=True, timeout=30, env=env,
|
||||
)
|
||||
if r.returncode == 0:
|
||||
pr_nums.extend(n for n in r.stdout.split() if n.isdigit())
|
||||
seen = []
|
||||
for n in pr_nums:
|
||||
if n not in seen:
|
||||
seen.append(n)
|
||||
comments = []
|
||||
for n in seen[:3]:
|
||||
r = subprocess.run(
|
||||
["gh", "api", f"repos/{repo_slug}/pulls/{n}/comments",
|
||||
"--jq", r'.[] | "\(.path): \(.body)"'],
|
||||
capture_output=True, text=True, timeout=30, env=env,
|
||||
)
|
||||
if r.returncode == 0:
|
||||
for line in r.stdout.splitlines():
|
||||
path = line.split(":", 1)[0]
|
||||
if any(path in f or f in path for f in files):
|
||||
comments.append(line[:200])
|
||||
if len(comments) >= 6:
|
||||
break
|
||||
if comments:
|
||||
history.append("Prior PR review comments on fragile paths:\n" + "\n".join(comments[:6]))
|
||||
except Exception: # noqa: BLE001
|
||||
pass # offline / not GitHub / no gh — silent by design
|
||||
|
||||
out["history_notes"] = "\n".join(h for h in history if h) or "(no notable history)"
|
||||
|
||||
# --- deterministic verdict-trigger signals -----------------------------------
|
||||
def any_path(pattern):
|
||||
return any(re.search(pattern, f, re.I) for f in files)
|
||||
|
||||
|
||||
migration_files = [f for f in files if re.search(r"migrations?/|\.sql$", f, re.I)]
|
||||
mig_added = "\n".join(
|
||||
l for l in dtext.splitlines()
|
||||
if l.startswith("+") and not l.startswith("+++")
|
||||
) if migration_files else ""
|
||||
out["destructive_migration"] = bool(
|
||||
migration_files
|
||||
and re.search(r"(?i)drop\s+(table|column)|rename\s+(table|column|to)|alter\s+\S+\s+drop", mig_added)
|
||||
)
|
||||
out["touches_auth"] = bool(
|
||||
any_path(r"auth(?!or)|login|logout|token|session|permission|rbac|\bacl\b|credential")
|
||||
or re.search(r"auth(?!or)|password|jwt|session|credential", added_lines, re.I)
|
||||
)
|
||||
out["consumer_surface"] = any_path(
|
||||
r"routes?|handlers?|\bapi\b|endpoints?|\bcmd/|\bcli\b|\.proto$|openapi|swagger|controllers?|graphql"
|
||||
)
|
||||
|
||||
if notes:
|
||||
out["facts_note"] = "; ".join(notes)[:800]
|
||||
print(json.dumps(out))
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deterministic domain grouping of changed files for the code-reviewer graph.
|
||||
|
||||
Each file lands in the FIRST matching domain (patterns ordered most-specific
|
||||
first); unmatched files land in `general`. When the grouping is very wide,
|
||||
single-file groups merge into `general` so the fan-out stays sane. The LLM
|
||||
refinement node may rebalance this proposal; the cover gate guarantees no
|
||||
file is lost either way. Crash-safe: an error emits one catch-all proposal
|
||||
rather than tolerant-failing into an empty grouping.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
def load_state():
|
||||
if path := os.environ.get("GRAPH_STATE_FILE"):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
DOMAINS = [
|
||||
("db", r"migrations?/|\.sql$|schema\.(rb|prisma|sql)|models?/"),
|
||||
("iac", r"\.tf$|helm|charts?/|(^|/)Dockerfile|docker-compose|k8s/|manifests?/"),
|
||||
("ci-cd", r"\.github/workflows/|\.gitlab-ci|Jenkinsfile|\.circleci/"),
|
||||
("workers", r"workers?/|jobs?/|consumers?/|cron|tasks?/"),
|
||||
("api", r"routes?|handlers?|controllers?|endpoints?|\bapi\b|\.proto$|openapi|swagger|graphql|resolvers?"),
|
||||
("cli", r"\bcmd/|\bcli\b"),
|
||||
("deps", r"go\.(mod|sum)$|package(-lock)?\.json$|yarn\.lock$|Cargo\.(toml|lock)$|requirements[^/]*\.txt$|pyproject\.toml$|Gemfile|pom\.xml$|composer\.json$"),
|
||||
("tests", r"(^|/)tests?/|_test\.|\.test\.|spec\."),
|
||||
("docs", r"\.(md|rst|adoc|txt)$"),
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
files = state.get("changed_files") or []
|
||||
|
||||
groups = {}
|
||||
for f in files:
|
||||
domain = "general"
|
||||
for name, pat in DOMAINS:
|
||||
if re.search(pat, f, re.I):
|
||||
domain = name
|
||||
break
|
||||
groups.setdefault(domain, []).append(f)
|
||||
|
||||
# Wide fan-out: merge single-file groups into general.
|
||||
if len(groups) > 6:
|
||||
for name in list(groups):
|
||||
if name != "general" and len(groups[name]) == 1:
|
||||
groups.setdefault("general", []).extend(groups.pop(name))
|
||||
|
||||
proposed = [{"domain": k, "files": v} for k, v in sorted(groups.items())]
|
||||
print(json.dumps({"proposed_groups": proposed}))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as e: # noqa: BLE001 — never tolerant-fail into an empty grouping
|
||||
try:
|
||||
files = load_state().get("changed_files") or []
|
||||
except Exception: # noqa: BLE001
|
||||
files = []
|
||||
print(json.dumps({"proposed_groups": [{"domain": "all-changes", "files": files}]}))
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Domain linter pass for the code-reviewer graph.
|
||||
|
||||
Runs whichever mechanized checkers exist on PATH against the matching changed
|
||||
files: tflint (Terraform), hadolint (Dockerfiles), actionlint (workflow
|
||||
files), and `buf breaking` when buf-configured protos changed. Read-only,
|
||||
never installs anything, and each tool degrades silently to a skip note.
|
||||
Output is routed per-file where possible so the domain leaders can extract
|
||||
their slice's lines.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
def load_state():
|
||||
if path := os.environ.get("GRAPH_STATE_FILE"):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
state = load_state()
|
||||
proj = state.get("project_dir") or "."
|
||||
files = state.get("changed_files") or []
|
||||
spec = state.get("resolved_diff_spec") or "worktree"
|
||||
notes = []
|
||||
|
||||
|
||||
def run(cmd, timeout=60):
|
||||
r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, cwd=proj)
|
||||
return (r.stdout + r.stderr).strip()
|
||||
|
||||
|
||||
def capped(text, n=40):
|
||||
lines = text.splitlines()
|
||||
return "\n".join(lines[:n]) + (f"\n… ({len(lines) - n} more lines)" if len(lines) > n else "")
|
||||
|
||||
|
||||
try:
|
||||
tf = [f for f in files if f.endswith(".tf")]
|
||||
if tf and shutil.which("tflint"):
|
||||
notes.append("[tflint]\n" + capped(run(["tflint", "--no-color"], 120) or "clean"))
|
||||
elif tf:
|
||||
notes.append("[tflint] skipped — not on PATH")
|
||||
|
||||
dockers = [f for f in files if re.search(r"(^|/)Dockerfile", f)]
|
||||
if dockers and shutil.which("hadolint"):
|
||||
notes.append("[hadolint]\n" + capped(run(["hadolint", "--no-color", *dockers[:10]], 60) or "clean"))
|
||||
elif dockers:
|
||||
notes.append("[hadolint] skipped — not on PATH")
|
||||
|
||||
workflows = [f for f in files if ".github/workflows/" in f]
|
||||
if workflows and shutil.which("actionlint"):
|
||||
notes.append("[actionlint]\n" + capped(run(["actionlint", "-no-color", *workflows[:10]], 60) or "clean"))
|
||||
elif workflows:
|
||||
notes.append("[actionlint] skipped — not on PATH")
|
||||
|
||||
protos = [f for f in files if f.endswith(".proto")]
|
||||
if protos and os.path.isfile(os.path.join(proj, "buf.yaml")) and shutil.which("buf"):
|
||||
base = None
|
||||
if "..." in spec:
|
||||
base = spec.split("...")[0]
|
||||
elif ".." in spec:
|
||||
base = spec.split("..")[0]
|
||||
elif spec not in ("staged", "worktree", "HEAD~1"):
|
||||
base = spec
|
||||
else:
|
||||
try:
|
||||
r = subprocess.run(["git", "-C", proj, "rev-parse", "HEAD"],
|
||||
capture_output=True, text=True, timeout=15)
|
||||
base = r.stdout.strip() if r.returncode == 0 else None
|
||||
except Exception: # noqa: BLE001
|
||||
base = None
|
||||
if base:
|
||||
out = run(["buf", "breaking", "--against", f".git#ref={base}"], 120)
|
||||
notes.append("[buf breaking] (wire-compat breaks are 🔴 by default)\n" + capped(out or "clean"))
|
||||
else:
|
||||
notes.append("[buf breaking] skipped — no base ref resolvable")
|
||||
elif protos:
|
||||
notes.append("[buf breaking] skipped — no buf.yaml or buf not on PATH")
|
||||
except Exception as e: # noqa: BLE001
|
||||
notes.append(f"[linters] pass degraded: {e}")
|
||||
|
||||
print(json.dumps({"linter_notes": "\n\n".join(notes) if notes else "(no applicable linters)"}))
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deterministic report assembly for the code-reviewer graph.
|
||||
|
||||
Builds the standard report format from the structured findings and prose
|
||||
sections. Pure templating — no judgment, no re-summarization, no finding
|
||||
ever added or removed here.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
def load_state():
|
||||
if path := os.environ.get("GRAPH_STATE_FILE"):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
v = state.get("verdict_out") or {}
|
||||
findings = v.get("findings_final") or []
|
||||
counts = v.get("counts") or {}
|
||||
changed = state.get("changed_files") or []
|
||||
rigor = state.get("resolved_rigor") or "production"
|
||||
|
||||
if not changed:
|
||||
print(json.dumps({"final_report": "# Code Review Summary\n\nNo changes to review."}))
|
||||
return
|
||||
|
||||
verdict = v.get("verdict", "NEEDS-HUMAN")
|
||||
lines = [
|
||||
"# Code Review Summary",
|
||||
"",
|
||||
f"**Verdict: {verdict}** — {v.get('reason', '')}",
|
||||
"",
|
||||
"## Walkthrough",
|
||||
state.get("walkthrough") or "(no walkthrough provided)",
|
||||
"",
|
||||
"## Changes",
|
||||
"",
|
||||
"| File | Changes | Findings |",
|
||||
"|------|---------|----------|",
|
||||
]
|
||||
|
||||
desc_by_file = {}
|
||||
for row in state.get("changes_rows") or []:
|
||||
if isinstance(row, dict) and row.get("file"):
|
||||
desc_by_file[row["file"]] = row.get("desc", "")
|
||||
blocking = [f for f in findings if f.get("section") == "blocking"]
|
||||
deferred = [f for f in findings if f.get("section") == "deferred"]
|
||||
for f in changed:
|
||||
per = [x for x in blocking if x.get("file") == f]
|
||||
tally = " ".join(
|
||||
f"{icon} {n}" for icon, n in
|
||||
((i, sum(1 for x in per if x.get("icon") == i)) for i in ("🔴", "🟡", "🟢", "💡"))
|
||||
if n
|
||||
) or "—"
|
||||
lines.append(f"| `{f}` | {desc_by_file.get(f, '')} | {tally} |")
|
||||
|
||||
lines += ["", "## Detailed Findings", ""]
|
||||
by_file = {}
|
||||
for x in blocking:
|
||||
by_file.setdefault(x.get("file", "(unattributed)"), []).append(x)
|
||||
if by_file:
|
||||
for f in sorted(by_file):
|
||||
lines += [f"### `{f}`", ""]
|
||||
for x in by_file[f]:
|
||||
lines += [x.get("block", ""), ""]
|
||||
else:
|
||||
lines += ["No blocking findings.", ""]
|
||||
|
||||
lines += ["## Cross-File Concerns", state.get("cross_file") or "None", ""]
|
||||
if (state.get("operational_history") or "").strip():
|
||||
lines += ["## Operational history", state["operational_history"], ""]
|
||||
if (state.get("org_context_section") or "").strip():
|
||||
lines += ["## Org context", state["org_context_section"], ""]
|
||||
if deferred and rigor != "production":
|
||||
lines += ["## Deferred by quality bar", ""]
|
||||
for x in deferred:
|
||||
lines += [x.get("block", ""), ""]
|
||||
attention = v.get("attention") or []
|
||||
if verdict == "NEEDS-HUMAN" and attention:
|
||||
lines += ["## Human attention required", ""]
|
||||
lines += [f"- {a}" for a in attention]
|
||||
lines += [""]
|
||||
|
||||
miss_ids = sorted({
|
||||
m for x in findings for m in
|
||||
__import__("re").findall(r"MISS-\d+", (x.get("title") or "") + (x.get("block") or ""))
|
||||
})
|
||||
lines += [
|
||||
"---",
|
||||
f"*Reviewed {len(changed)} files, found {counts.get('🔴', 0)} critical, "
|
||||
f"{counts.get('🟡', 0)} warnings, {counts.get('🟢', 0)} suggestions, "
|
||||
f"{counts.get('💡', 0)} nitpicks ({v.get('deferred_count', 0)} deferred by quality bar)*",
|
||||
f"*Quality bar: {rigor} — provenance: {state.get('bar_provenance', '?')}; "
|
||||
f"surfaces: {state.get('resolved_surfaces') or 'none'}*",
|
||||
f"*Verdict: {verdict}; verifier: dropped {v.get('dropped_count', 0)} provably-false"
|
||||
+ (f" ({', '.join(v.get('dropped_titles', [])[:5])})" if v.get("dropped_count") else "")
|
||||
+ f"; review-miss patterns applied: {', '.join(miss_ids) if miss_ids else 'none relevant'}*",
|
||||
]
|
||||
print(json.dumps({"final_report": "\n".join(lines)}))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(json.dumps({"final_report": f"# Code Review Summary\n\n**Verdict: NEEDS-HUMAN** — report rendering error: {e}"}))
|
||||
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deterministic verdict math for the code-reviewer graph.
|
||||
|
||||
The model that wrote the findings never grades its own homework:
|
||||
- verifier verdicts are applied mechanically (FALSE → drop, tallied;
|
||||
VERIFIED → evidence attached; UNVERIFIABLE → kept, marked);
|
||||
- rigor folding is arithmetic over severity + [convention]/[correctness]
|
||||
markers, exactly per the published rules;
|
||||
- MERGE-READY/NEEDS-HUMAN is computed from counts + always-human triggers
|
||||
(deterministic signals ∪ the synthesis's ADDITIVE-only flags).
|
||||
The gate never crashes into a silent verdict: any internal error emits
|
||||
NEEDS-HUMAN naming the error.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
def load_state():
|
||||
if path := os.environ.get("GRAPH_STATE_FILE"):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
def sev_icon(severity):
|
||||
for icon in ("🔴", "🟡", "🟢", "💡"):
|
||||
if icon in (severity or ""):
|
||||
return icon
|
||||
return "🟢"
|
||||
|
||||
|
||||
def parse_verifier(text):
|
||||
"""Extract {id: {verdict, evidence, note}} from finding-verifier output."""
|
||||
verdicts = {}
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
return verdicts
|
||||
start, end = text.find("["), text.rfind("]")
|
||||
if start != -1 and end > start:
|
||||
try:
|
||||
for v in json.loads(text[start : end + 1]):
|
||||
if isinstance(v, dict) and v.get("id"):
|
||||
verdicts[str(v["id"])] = v
|
||||
return verdicts
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
for m in re.finditer(r'\{[^{}]*"id"[^{}]*\}', text):
|
||||
try:
|
||||
v = json.loads(m.group(0))
|
||||
if v.get("id"):
|
||||
verdicts[str(v["id"])] = v
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
return verdicts
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
findings = [f for f in (state.get("findings") or []) if isinstance(f, dict)]
|
||||
rigor = (state.get("resolved_rigor") or "production").lower()
|
||||
verdicts = parse_verifier(state.get("verifier_output"))
|
||||
|
||||
kept, dropped_titles = [], []
|
||||
for f in findings:
|
||||
icon = sev_icon(f.get("severity"))
|
||||
v = verdicts.get(str(f.get("id")))
|
||||
block = f.get("block") or f"#### {f.get('title', '(untitled)')}"
|
||||
if v and icon in ("🔴", "🟡"):
|
||||
if v.get("verdict") == "FALSE":
|
||||
dropped_titles.append(f.get("title", f.get("id", "?")))
|
||||
continue
|
||||
if v.get("verdict") == "VERIFIED" and v.get("evidence"):
|
||||
block += f"\n- **Evidence** (verifier): {v['evidence']}"
|
||||
elif v.get("verdict") == "UNVERIFIABLE":
|
||||
block += "\n- *(unverified: " + (v.get("note") or "could not be established") + ")*"
|
||||
elif icon in ("🔴", "🟡") and not v:
|
||||
block += "\n- *(unverified: no verifier verdict returned for this finding)*"
|
||||
kept.append({**f, "icon": icon, "block": block})
|
||||
|
||||
# --- rigor folding (arithmetic, never rewrites severities) --------------
|
||||
for f in kept:
|
||||
marker = (f.get("marker") or "").lower()
|
||||
icon = f["icon"]
|
||||
section = "blocking"
|
||||
if icon == "🔴":
|
||||
section = "blocking"
|
||||
elif rigor == "prototype" and ((icon == "🟡" and marker == "convention") or icon == "🟢"):
|
||||
section = "deferred"
|
||||
elif rigor == "poc":
|
||||
if icon == "🟡" and marker == "convention":
|
||||
section = "deferred"
|
||||
elif icon in ("🟢", "💡") and marker == "convention":
|
||||
section = "dropped-by-poc"
|
||||
f["section"] = section
|
||||
final = [f for f in kept if f["section"] != "dropped-by-poc"]
|
||||
blocking = [f for f in final if f["section"] == "blocking"]
|
||||
deferred = [f for f in final if f["section"] == "deferred"]
|
||||
|
||||
counts = {i: sum(1 for f in blocking if f["icon"] == i) for i in ("🔴", "🟡", "🟢", "💡")}
|
||||
yellow_correctness = sum(
|
||||
1 for f in blocking if f["icon"] == "🟡" and (f.get("marker") or "").lower() == "correctness"
|
||||
)
|
||||
|
||||
# --- always-human triggers ----------------------------------------------
|
||||
attention = []
|
||||
if state.get("destructive_migration"):
|
||||
attention.append("Destructive/irreversible migration in the diff (DROP/RENAME) — needs a human signature.")
|
||||
if state.get("touches_auth"):
|
||||
attention.append("Authentication/authorization surface changed — needs a human signature.")
|
||||
for flag in state.get("attention_flags") or []:
|
||||
if isinstance(flag, str) and flag.strip():
|
||||
attention.append(flag.strip())
|
||||
placement = (state.get("placement_notes") or "").strip()
|
||||
if placement:
|
||||
attention.append("Placement question (design intent, never auto-resolved): " + placement)
|
||||
# dedup, preserve order
|
||||
seen = set()
|
||||
attention = [a for a in attention if not (a.lower() in seen or seen.add(a.lower()))]
|
||||
|
||||
if (state.get("changed_files") or []) and not (state.get("domain_reports") or []):
|
||||
attention.append(
|
||||
"PIPELINE FAULT: the diff is non-empty but no domain reports were produced — "
|
||||
"the review did not actually run; do not trust this verdict."
|
||||
)
|
||||
reasons = []
|
||||
if counts["🔴"]:
|
||||
reasons.append(f"{counts['🔴']} 🔴 CRITICAL finding(s)")
|
||||
if yellow_correctness:
|
||||
reasons.append(f"{yellow_correctness} 🟡 [correctness] finding(s) outside the deferred section")
|
||||
if attention:
|
||||
reasons.append("always-human trigger(s) fired")
|
||||
verdict = "NEEDS-HUMAN" if reasons else "MERGE-READY"
|
||||
reason_line = "; ".join(reasons) if reasons else "no blocking findings and no always-human triggers"
|
||||
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"verdict_out": {
|
||||
"verdict": verdict,
|
||||
"reason": reason_line,
|
||||
"counts": counts,
|
||||
"deferred_count": len(deferred),
|
||||
"dropped_count": len(dropped_titles),
|
||||
"dropped_titles": dropped_titles,
|
||||
"attention": attention[:5],
|
||||
"findings_final": final,
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as e: # noqa: BLE001 — never crash into a silent verdict
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"verdict_out": {
|
||||
"verdict": "NEEDS-HUMAN",
|
||||
"reason": f"verdict computation error: {e} — treat as needing human review",
|
||||
"counts": {}, "deferred_count": 0, "dropped_count": 0,
|
||||
"dropped_titles": [], "attention": [f"verdict script error: {e}"],
|
||||
"findings_final": [],
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
Reference in new issue
Block a user