Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ humans invoke identically, so behaviour isn't re-derived from prose each time.
PyAutoHeart readiness verdict and explains it, mapping each reason to its
capability. The single component that talks to Heart; every conductor
consults it rather than querying Heart directly. Never dispatches or mutates.
- **`agents/faculties/review/`** — *reviews the change*: prepares a feature
branch's ReviewSurface (diff, commits, risk flags) and defines the procedure
by which the reviewing agent maps it to a **CLEAN / FINDINGS / BLOCKED**
verdict — the automatic-review leg of the autonomous-ship gate
([`AUTONOMY.md`](AUTONOMY.md)). Dev-workflow ship only: it never opines on
release readiness (Heart's, via vitals) and never fixes what it finds.

> **Build Agent vs. the release conductor — resolved.** The Build Agent's
> release mode owns the **single** readiness-gate + execution path
Expand Down Expand Up @@ -145,6 +151,7 @@ bin/pyauto-brain build --dry-run # reason + plan only (emit the BuildDecision)
bin/pyauto-brain release # reason about readiness, then release on green
bin/pyauto-brain health # (conductor) run the health loop with a human, toward green
bin/pyauto-brain vitals # (faculty) one tick + the unified dashboard card (raw read)
bin/pyauto-brain review --task <name> # (faculty) a branch's ReviewSurface for the ship gate's review leg
```

Like the other PyAuto repos, PyAutoBrain runs from its checkout (no pip install);
Expand Down
74 changes: 74 additions & 0 deletions agents/faculties/review/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Review faculty

> **Tier: faculty** — a read-only reasoning capability the conductors
> *consult*, not a front door you drive. It *reviews the change*: given a task
> worktree / feature branch, it returns a verdict — **CLEAN**, **FINDINGS**, or
> **BLOCKED** — and stops. It never dispatches, never mutates, never fixes what
> it finds. It is the automatic-review leg of the autonomous-ship gate
> ([`../../../AUTONOMY.md`](../../../AUTONOMY.md)).

## Boundary (settled — do not reopen)

A diff review is a **side-effect-free opinion**, which is the definition of a
faculty — it does **not** belong in Heart. Heart is the organism-state observer
(repo state, CI, PRs, deep install checks on `main`); it never looks at feature
branches and stays the sole authority on *release* readiness. This faculty
gates the **dev workflow's ship step** only:

- It must never grow release opinions — release readiness is Heart's, adopted
via the vitals faculty.
- It must never edit code, post comments, or open anything — findings are
returned to the consulting conductor, which decides what to do.
- Its sensors are the branch diff and the harness review tooling, the way the
vitals faculty's sensor is Heart.

## The verdict

| Verdict | Meaning | Autonomous-run consequence |
|---------|---------|---------------------------|
| **CLEAN** | review + verify pass found nothing that must change | ship leg satisfied |
| **FINDINGS** | ranked defects/cleanups that must be resolved or judged | resolve, or downgrade to a human checkpoint — never ship past it |
| **BLOCKED** | could not review (no diff, unresolvable base, tooling failure) | treat as `human-required` |

Verdict semantics are consumed by the ship gate exactly as `AUTONOMY.md`
defines — a verdict is an input to the gate, never a bypass of it.

## How the faculty works (surface script + reviewing agent)

The deterministic entrypoint prepares the **review surface**; the *verdict* is
produced by the reviewing agent (the session or subagent consulting this
faculty) following the procedure below — mirroring how vitals' script reads
Heart and the agent reasons over the verdict.

1. `review.sh` (→ `_review.py`, stdlib-only) resolves the task worktree or
repo paths and emits, per repo: merge-base against `origin/main`, commits
ahead, diff stat, changed files, and risk flags (public-API-shaped paths,
config/schema files, tests changed or not, generated files).
2. The reviewing agent runs, over that surface: a **code review at high
effort** (correctness first, then reuse/simplification) and a **verify
pass** — drive the affected flow end-to-end, not just tests (for
doc/doctrine diffs: link resolution + single-source check instead).
3. Map the outcome to the verdict: any unresolved must-fix → **FINDINGS**
(ranked list, file:line, failure scenario); nothing → **CLEAN**; could not
complete steps 1–2 → **BLOCKED** (say why).

## Run

```bash
bin/pyauto-brain review --task <task-name> # resolve ~/Code/PyAutoLabs-wt/<task>/ claimed repos
bin/pyauto-brain review --repo <path> [--repo ...] # explicit repo checkouts
bin/pyauto-brain review --task <task-name> --json # machine-readable ReviewSurface
```

Exit codes: `0` surface produced · `4` no reviewable diff / could not resolve ·
`5` bad usage. The script never exits non-zero for *findings* — findings are
the agent's judgment, not the script's.

## What this faculty must never do

- Dispatch, mutate, fix, comment, or open PRs/issues — it only opines.
- Query Heart or emit release-readiness opinions (vitals' job).
- Substitute its verdict for tests, smoke tests, or the Heart gate — the
autonomous-ship gate requires **all four** legs (`AUTONOMY.md`).
- Auto-resolve its own FINDINGS — resolution belongs to the consulting
conductor and re-review.
134 changes: 134 additions & 0 deletions agents/faculties/review/_review.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""agents/faculties/review/_review.py — the review-surface substrate.

The **review faculty** is a read-only opinion sink: given a task worktree or
explicit repo checkouts, it prepares the **ReviewSurface** — everything the
reviewing agent needs to run the review procedure in this faculty's AGENTS.md
(code review at high effort + a verify pass) and map the outcome to a
CLEAN / FINDINGS / BLOCKED verdict.

This script produces the *surface*, never the *verdict*: findings are the
reviewing agent's judgment. It is stdlib-only, never writes anything, and
never exits non-zero because of what the diff contains.

Exit codes: 0 surface produced · 4 no reviewable diff / could not resolve ·
5 bad usage.
"""
from __future__ import annotations

import argparse
import json
import subprocess
import sys
from pathlib import Path

WT_BASE = Path.home() / "Code" / "PyAutoLabs-wt"

# Paths that smell like public API / behaviour when changed — flags only,
# the reviewing agent judges.
RISK_MARKERS = {
"config-or-schema": (".yaml", ".yml", ".json", ".toml", ".ini", ".cfg"),
"packaging": ("setup.py", "setup.cfg", "pyproject.toml", "requirements"),
}


def _git(repo: Path, *args: str) -> str:
out = subprocess.run(
["git", "-C", str(repo), *args],
capture_output=True, text=True, check=False,
)
return out.stdout.strip() if out.returncode == 0 else ""


def repo_surface(repo: Path) -> dict | None:
"""One repo's slice of the ReviewSurface, or None if there is no diff."""
if not (repo / ".git").exists():
return None
branch = _git(repo, "rev-parse", "--abbrev-ref", "HEAD")
base = _git(repo, "merge-base", "HEAD", "origin/main")
if not base:
return None
commits = _git(repo, "log", "--oneline", f"{base}..HEAD")
if not commits:
return None
files = _git(repo, "diff", "--name-status", f"{base}..HEAD").splitlines()
stat = _git(repo, "diff", "--shortstat", f"{base}..HEAD")
changed = [line.split("\t")[-1] for line in files if line.strip()]
flags = []
if not any("test" in f.lower() for f in changed):
flags.append("no-test-changes")
for name, exts in RISK_MARKERS.items():
if any(f.endswith(exts) or any(m in f for m in exts) for f in changed):
flags.append(name)
if any(f.endswith(".py") and "test" not in f.lower() for f in changed):
flags.append("python-source")
return {
"repo": repo.name,
"path": str(repo),
"branch": branch,
"base": base[:12],
"commits_ahead": len(commits.splitlines()),
"commits": commits.splitlines(),
"shortstat": stat,
"files": files,
"risk_flags": flags,
}


def resolve_repos(task: str | None, repos: list[str]) -> list[Path]:
if task:
root = WT_BASE / task
if not root.is_dir():
return []
# Claimed repos are real directories (not symlinks) holding a .git
# file/dir — worktree_create symlinks everything unclaimed.
return sorted(
p for p in root.iterdir()
if p.is_dir() and not p.is_symlink() and (p / ".git").exists()
)
return [Path(r).resolve() for r in repos]


def emit_human(surfaces: list[dict]) -> None:
print("== ReviewSurface (review faculty — surface only; the verdict is the")
print(" reviewing agent's, per agents/faculties/review/AGENTS.md) ==")
for s in surfaces:
print(f"\n-- {s['repo']} [{s['branch']}] base {s['base']}")
print(f" {s['commits_ahead']} commit(s) ahead — {s['shortstat']}")
for c in s["commits"][:10]:
print(f" * {c}")
print(f" risk flags: {', '.join(s['risk_flags']) or 'none'}")
for f in s["files"][:40]:
print(f" {f}")
if len(s["files"]) > 40:
print(f" ... and {len(s['files']) - 40} more")
print("\nVerdict rubric: CLEAN (nothing must change) | FINDINGS (ranked,")
print("file:line, failure scenario) | BLOCKED (could not review — say why).")


def main(argv=None) -> int:
ap = argparse.ArgumentParser(prog="review")
ap.add_argument("--task", default="", help="task worktree name under ~/Code/PyAutoLabs-wt/")
ap.add_argument("--repo", action="append", default=[], help="explicit repo checkout path")
ap.add_argument("--json", action="store_true", dest="as_json")
a = ap.parse_args(argv)
if not a.task and not a.repo:
print("review: pass --task <name> or --repo <path>", file=sys.stderr)
return 5
Comment on lines +115 to +117
repos = resolve_repos(a.task or None, a.repo)
if not repos:
print("review: could not resolve any repo checkout", file=sys.stderr)
return 4
Comment on lines +118 to +121
surfaces = [s for s in (repo_surface(r) for r in repos) if s]
if not surfaces:
print("review: no reviewable diff against origin/main", file=sys.stderr)
return 4
if a.as_json:
print(json.dumps({"review_surface": surfaces}, indent=2))
else:
emit_human(surfaces)
return 0


if __name__ == "__main__":
sys.exit(main())
23 changes: 23 additions & 0 deletions agents/faculties/review/review.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# agents/faculties/review/review.sh — the review faculty (a PyAutoBrain
# read-only reasoning capability). It reviews the change.
Comment on lines +2 to +3
#
# Prepares the ReviewSurface for a task worktree / feature branch: per-repo
# merge-base against origin/main, commits ahead, diff stat, changed files and
# risk flags. The VERDICT (CLEAN / FINDINGS / BLOCKED) is produced by the
# reviewing agent following AGENTS.md in this directory — this script only
# prepares the surface, mirroring how vitals.sh reads Heart and the agent
# reasons over the result. Read-only: it never dispatches or mutates, never
# fixes a finding, and never opines on release readiness (Heart's, via the
# vitals faculty).
#
# Usage:
# review.sh --task <task-name> # resolve ~/Code/PyAutoLabs-wt/<task>/
# review.sh --repo <path> [--repo ...] # explicit repo checkouts
# review.sh ... --json # machine-readable ReviewSurface

set -uo pipefail

HERE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"

exec python3 "$HERE/_review.py" "$@"
6 changes: 5 additions & 1 deletion bin/pyauto-brain
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
# pyauto-brain health [args] (conductor) the organism's clinician: run the health
# loop with a human, dispatch by dispatch, toward green
# pyauto-brain vitals [args] (faculty) read-only: read the Heart's pulse (the verdict)
# pyauto-brain review [args] (faculty) read-only: prepare a branch's ReviewSurface for the
# ship gate's review leg (verdict is the reviewing agent's)
# pyauto-brain help [name] list agents or show one agent's docs
#
# Renamed from `pyauto-agent`; the former back-compat shim has been removed now
Expand All @@ -42,6 +44,7 @@ declare -A AGENT_SCRIPT=(
[release]="$CONDUCTORS_DIR/release/release.sh"
[health]="$CONDUCTORS_DIR/health/health.sh"
[vitals]="$FACULTIES_DIR/vitals/vitals.sh"
[review]="$FACULTIES_DIR/review/review.sh"
)
declare -A AGENT_DESC=(
[intake]="Conceive a task: turn raw input into a formal, headed PyAutoMind prompt (files it; never starts dev)"
Expand All @@ -51,11 +54,12 @@ declare -A AGENT_DESC=(
[release]="Release door → the Build Agent release mode (single gate); 'release rehearse'/'release validate' drive release validation"
[health]="The organism's clinician: run the health loop with a human, dispatch by dispatch, toward green"
[vitals]="Read-only: read the Heart's pulse — the PyAutoHeart readiness verdict (consulted by the conductors)"
[review]="Read-only: prepare the branch ReviewSurface — the reviewing agent maps it to CLEAN/FINDINGS/BLOCKED (the ship gate's review leg)"
)
# Conductors are the front doors a human drives; faculties are consulted (and
# runnable read-only). Both are dispatchable, but the menu groups them by tier.
CONDUCTOR_ORDER=(intake feature bug build release health)
FACULTY_ORDER=(vitals)
FACULTY_ORDER=(vitals review)
AGENT_ORDER=("${CONDUCTOR_ORDER[@]}" "${FACULTY_ORDER[@]}")

cmd_help() {
Expand Down
2 changes: 2 additions & 0 deletions skills/WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ per-work-type caps, and the hard invariants (merge is always human; autonomous
runs end at PR-open). Levels bind **only** under an explicit `--auto` launch;
default runs present-and-wait at every checkpoint, exactly as the steps below
describe. Do not restate checkpoint rules in a skill body — link the contract.
The gate's automatic-review leg is the **review faculty**
(`bin/pyauto-brain review --task <name>`; `agents/faculties/review/AGENTS.md`).

## Brain agent entry points

Expand Down