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
59 changes: 59 additions & 0 deletions scripts/review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
You are a code reviewer for a draft-managed change about to be committed.

Branch: {DRAFT_BRANCH}
Base branch: {DRAFT_BASE_BRANCH}

Run `git diff {DRAFT_BASE_BRANCH}...HEAD` in the repository at `{DRAFT_REPO_DIR}` to get the diff.
Read the spec from `{DRAFT_SPEC_FILE}`.

## Review rules

- The spec is the source of truth.
- Flag errors or bugs related to the changed files (anything wrong inside the diff).
- Flag mismatches between the diff and the spec (the spec asks for X, the diff does Y).
- Do NOT flag nice-to-have features. If the spec doesn't ask for it, don't suggest adding it.
- Do NOT flag refactoring opportunities. If the existing code is not buggy, leave it alone, even if it could be cleaner.
- Do NOT flag stylistic preferences without a correctness impact.
- Report at most 3 items. If more real issues exist, pick the most severe (severity = potential to cause incorrect runtime behaviour or spec violation) and drop the rest.

## Output

There are exactly two valid outputs. Choose one. Never both. Never something else.

(a) If no issues meet the criteria above, output nothing at all (empty output).

(b) Otherwise, output up to 3 items in the format below. No preamble. Items separated by a blank line.

## <name (1-4 words)>

**Summary**: <one to three sentences>

**Details**:
<free-form, as long as needed: file/line references, code excerpts, spec excerpts, failure scenarios>

**Proposed fix**:
<free-form, as long as needed: concrete change recommendation>

Example of a single well-formed item:

## Missing null check

**Summary**: `parse_config` dereferences the result of `os.environ.get("DRAFT_SPEC_FILE")` without checking for None, which crashes the step when the env var is unset.

**Details**:
At `src/draft/steps/review_implementation/__init__.py:42`, the diff introduces:

spec_path = os.environ.get("DRAFT_SPEC_FILE")
with open(spec_path) as f:
...

The spec says `DRAFT_SPEC_FILE` is always set, but `open(None)` raises `TypeError`, which surfaces as an opaque crash.

**Proposed fix**:
Guard the lookup:

spec_path = os.environ.get("DRAFT_SPEC_FILE")
if not spec_path:
raise StepError("review-implementation", "DRAFT_SPEC_FILE is required")
with open(spec_path) as f:
...
138 changes: 0 additions & 138 deletions scripts/review.sh

This file was deleted.

130 changes: 130 additions & 0 deletions scripts/run_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env python3
"""Runs a prompt template against auggie.

Usage: run_agent.py <template> <model>

<template> is a path to a prompt template file — absolute or relative to
DRAFT_REPO_DIR. The template may contain these placeholders which are
substituted from the corresponding environment variables:
{DRAFT_REPO_DIR} {DRAFT_BRANCH} {DRAFT_BASE_BRANCH} {DRAFT_SPEC_FILE}

<model> is forwarded to auggie (e.g. gpt-4.1).

Verdict contract: stdout empty → approval; stdout non-empty → rejection;
rc != 0 → infra failure.
"""

import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

TEMPLATE_VARS = ("DRAFT_REPO_DIR", "DRAFT_BRANCH", "DRAFT_BASE_BRANCH", "DRAFT_SPEC_FILE")


def die(msg: str) -> None:
print(f"run_agent.py: {msg}", file=sys.stderr)
sys.exit(1)


def resolve_template_path(template: str, repo_dir: str) -> Path:
p = Path(template)
return p if p.is_absolute() else Path(repo_dir) / p


def fill_template(text: str, env: dict[str, str]) -> str:
for var in TEMPLATE_VARS:
text = text.replace(f"{{{var}}}", env.get(var, ""))
return text


def check_auggie_auth() -> None:
if not shutil.which("auggie"):
die("auggie not found on PATH")
result = subprocess.run(
["auggie", "token", "print"], capture_output=True, text=True
)
out = result.stdout + result.stderr
if result.returncode != 0 or "SESSION=" not in out:
print(
"run_agent.py: auggie pre-flight auth check failed;"
" run 'auggie login' or export AUGMENT_SESSION_AUTH",
file=sys.stderr,
)
if out.strip():
print("--- auggie token print output ---", file=sys.stderr)
print(out, file=sys.stderr)
sys.exit(1)


def run_auggie(prompt: str, model: str, workspace_root: str) -> str:
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
f.write(prompt)
prompt_file = f.name
try:
result = subprocess.run(
[
"auggie",
"--print",
"--quiet",
"--ask",
"--workspace-root",
workspace_root,
"--allow-indexing",
"--max-turns",
"10",
"--model",
model,
"--instruction-file",
prompt_file,
],
stdout=subprocess.PIPE,
text=True,
)
finally:
os.unlink(prompt_file)

if result.returncode != 0:
print(
f"run_agent.py: auggie call failed with rc={result.returncode}",
file=sys.stderr,
)
sys.exit(result.returncode)

return result.stdout


def main() -> None:
if len(sys.argv) != 3:
die("usage: run_agent.py <template> <model>")

template_arg, model = sys.argv[1], sys.argv[2]

repo_dir = os.environ.get("DRAFT_REPO_DIR", "")
if not repo_dir:
die("DRAFT_REPO_DIR is unset or empty")

env: dict[str, str] = {var: os.environ.get(var, "") for var in TEMPLATE_VARS}

template_path = resolve_template_path(template_arg, env["DRAFT_REPO_DIR"])
if not template_path.is_file():
die(f"template not found: {template_path}")

template_text = template_path.read_text()
if not template_text.strip():
die(f"template is empty: {template_path}")

prompt = fill_template(template_text, env)

check_auggie_auth()

stdout = run_auggie(prompt, model, env["DRAFT_REPO_DIR"])

if stdout.strip():
print(stdout, end="")


if __name__ == "__main__":
main()
Loading
Loading