asterinas/.agents/skills/aster-code-review/spec/interface.md

382 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Interface & Output
## How the skill is invoked
`aster-code-review` is a **skill, not a binary**.
You trigger it from inside a Claude Code or Codex session;
the host agent then runs the orchestration in [`execution_model.md`](execution_model.md).
There is no command on your `PATH` — though the skill also ships a headless CLI (below) for running it from a plain shell or from CI.
However it is triggered, the skill understands one small argument string,
and that string begins with a **mode word**:
```
diff <base> <output> [--overwrite] [--per-persona-context=auto|yes|no]
files <path[:lines] ...> <output> [--overwrite] [--per-persona-context=auto|yes|no]
```
- **`diff`** — *review a Git change*:
the defects in what the working tree adds over a `<base>`.
- **`files`** — *review target code*:
the defects in a set of files,
optionally narrowed to line ranges.
Both modes write the same kind of review file (below) and run the same persona fan-out (see [`execution_model.md`](execution_model.md));
they differ only in *what code they put under review*.
### Running it headless — the `aster_code_review.sh` CLI
Usually the skill runs inside an agent session.
It also ships a headless CLI at the skill root, `aster_code_review.sh`,
that runs the whole skill from a plain shell by driving an agent for you:
```
ACR_AGENT_PROFILE=<name> aster_code_review.sh <mode> <args…> <output> [flags]
```
The arguments **are** the argument string above,
so the CLI is identical to an in-session invocation;
the only addition is the agent, chosen by the environment rather than an argument
(a given environment exposes only a few agents):
- `ACR_AGENT_PROFILE` (required) — the agent profile to run under
(see [`benchmark.md`](benchmark.md#agent-profiles) for the profile format and the shipped `claude` / `codex` / `codex_workflow` profiles).
- `ACR_PROFILE_VARIANT` (optional) — `smoke` for the low-effort overlay.
It reviews the current working tree, so `cd` into the repo (or a scratch worktree) first.
Under the hood it builds the invocation prompt and hands it to the shared launcher `scripts/run_agent.sh`,
which is the one thing that turns a profile into a running agent.
The benchmark and the PR-review CI both drive the skill through this one CLI.
## The rule: HEAD is the head — there is no head argument
Whichever mode you use, the skill reviews relative to the **current checkout (HEAD)**;
there is never a "head" or "right endpoint" argument.
`diff <base>` reviews the **commits** the branch added to reach HEAD
— the series `merge-base(<base>, HEAD)..HEAD`;
`files` reviews the **working-tree contents** of the named files at HEAD.
To review at a specific commit,
branch, or tag, **make it HEAD**
`git checkout <ref>`, or `git worktree add` a scratch tree and `cd` into it
— then run the skill.
This keeps the interface small and gives one mental model across PR review,
periodic quality scans, and the self-review loop:
- no head/range bookkeeping — you name a base (or some files), never two endpoints;
- `diff` reviews **committed** commits,
so commit your work first — the self-review loop commits each round,
making its work part of the series;
uncommitted edits are not in a diff review (review them with `files`, or commit them);
- `files` reviews the working tree as it stands,
so it *does* cover uncommitted edits to the files you name.
The earlier `<base..head>` range is gone:
an explicit head endpoint contradicts "HEAD is the head."
Reviewing an arbitrary historical range means checking that range's head out first.
## Mode 1 — `diff <base>`: review a Git change
`diff <base>` reviews the **commit series** the branch added over its merge-base with `<base>`
— the commits `merge-base(<base>, HEAD)..HEAD`,
oldest first, each presented as its **message** and its **diff**:
```
git log -p --reverse $(git merge-base <base> HEAD)..HEAD
```
Reviewing the series rather than one flattened diff is what lets the skill judge **per-commit intent** (does each commit do what its message claims?)
and **commit hygiene** (imperative subject, atomic commits — a Maintainability concern),
on top of the code itself.
- On a clean branch this is *what the branch added since it diverged from `<base>`*
— the pull request.
`<base>` may be any ref or SHA (`main`, `origin/main`, a tag, a short SHA).
- Using the *merge-base* (not `<base>` directly) means a `<base>` that advanced after you branched does not inject its upstream commits as spurious reversals
— only the branch's own commits are reviewed.
- **Committed commits only.**
Uncommitted edits, and brand-new untracked files,
are not in a diff review — commit them first (the self-review loop commits each round),
or review a specific file with `files <path>`.
## Mode 2 — `files <path[:lines] ...>`: review target code
`files` reviews the working-tree contents of the files you name,
optionally narrowed to line ranges.
It is for auditing existing code rather than a change
— periodically scanning select files to raise quality,
or focusing a review on one area.
Each **target** is a path, optionally followed by line ranges:
```
TARGET := PATH | PATH ":" RANGES | '"' QPATH '"' | '"' QPATH '"' ":" RANGES
RANGES := RANGE ("," RANGE)*
RANGE := N | N "-" M # 1-based, inclusive, N ≤ M
```
- `kernel/src/sched/fair.rs` — review the whole file.
- `kernel/src/sched/fair.rs:120-180` — review only lines 120180.
- `kernel/src/sched/fair.rs:120-180,240-260` — two ranges in one file.
- Repeating a path is allowed; its ranges are unioned, and overlapping ranges are merged.
The named lines are the **review scope**;
the rest of the file and the wider repository remain readable as **context**
— exactly as in `diff` mode,
where the diff is the unit of review but a pass may read around it.
### Quoting paths
A path that contains a space or a colon
— or whose final segment *looks like* a line range
— would confuse the bare grammar.
Wrap it in double quotes:
- `"a path with spaces.rs"` — whole file, spaces and all.
- `"weird: dir/mod.rs":30-60` — colon in the path, plus a range.
- `"gen/12-20"` — a file literally named `12-20`,
reviewed whole (quotes stop it being read as `path:range`).
Unquoted, the parser splits the line spec **from the right**:
it peels a trailing `:RANGES` only if the suffix fully matches the range grammar,
otherwise the whole token is the path.
So `weird:dir/mod.rs` — whose tail is not a range
— is already a valid bare path;
quotes are the explicit escape for the genuinely ambiguous rest.
## How the argument string is parsed
The skill receives its arguments as **one raw string** and tokenizes it itself,
deterministically — it does **not** rely on a shell to do so.
When you type `/aster-code-review files "a b.rs" out.md` in Claude Code,
the prompt is not a POSIX shell:
the quotes arrive intact, so the skill must own the parse.
The grammar:
1. The first token is the **mode word**, `diff` or `files`.
2. The remainder is split into tokens on whitespace,
**except inside double quotes**
— a quoted span is one token, spaces and colons included.
3. **Flags** (`--overwrite`) are pulled out.
4. The **last** positional is `<output>`;
everything before it is the target list
— one `<base>` for `diff`, one or more targets for `files`.
5. In `files` mode each target is parsed by the grammar above:
quotes strip to the bare path,
and an unquoted trailing `:RANGES` is split from the right.
Owning the parse — rather than leaning on the agent or a shell to pre-split the string
— makes it identical no matter how the skill was triggered,
and keeps it a deterministic step the benchmark exercises directly.
### The argument table
| Argument | Meaning |
|---|---|
| `diff` \| `files` | **Required, first.** The review mode. |
| `<base>` | (`diff`) **Required.** Any ref or SHA; the review covers the commit series `merge-base(<base>, HEAD)..HEAD` — each commit's message + diff. |
| `<path[:lines] ...>` | (`files`) **Required, one or more.** The files (and optional line ranges) to review, in the working tree. |
| `<output>` | **Required, last.** Where to write the review; refuses to clobber an existing file unless `--overwrite` is given. |
| `--overwrite` | Replace the output file if it already exists. |
| `--per-persona-context` | `yes` fans out one isolated agent per persona (best recall); `no` runs one combined agent over all personas (cheaper, lower recall); `auto` (default) currently resolves to `yes`. Affects orchestration only — see [`execution_model.md`](execution_model.md). |
### Rationale for the argument shape
- **A leading mode word, not a guessed argument.**
The mode is named explicitly because the alternative
— detecting whether the first token is a ref or a path
— is genuinely ambiguous (`main` vs `main.rs`, a ref that is also a filename, a path absent at HEAD) and against this interface's no-magic ethos.
One extra word buys an unambiguous,
self-documenting parse and room for a third mode later.
- **Output is the last positional, `cp`-style.**
With a variadic target list in `files` mode,
the output cannot live in a fixed second slot.
"Last positional is the destination" is the familiar `cp src... dest` convention and keeps both modes' grammars uniform.
It stays **mandatory** — there is no silent default review file,
which keeps the benchmark deterministic and never surprises a human with a file they did not name.
- **HEAD is the head, so no head argument.**
There is never a second endpoint:
`diff` reviews the commit series up to HEAD,
`files` the working tree at HEAD.
This replaces the old `<base..head>` range and the removed `--worktree` flag.
The cost is that reviewing an arbitrary historical range means checking its head out first;
the gain is one consistent model across PR review,
scans, and the self-review loop (which commits each round).
- **Quoting is the skill's, not the shell's.** Because the trigger is not a shell,
the skill self-tokenizes and treats double quotes as its own grammar
— the only robust way to let a path hold spaces or colons given that the quotes survive into the raw argument string.
- **An existing review is never clobbered by accident.**
Overwriting is opt-in via `--overwrite`:
a loop that means to replace its previous review passes the flag;
a hand-annotated review stays safe by default.
- **Cost is an explicit, opt-in knob.**
`--per-persona-context=no` collapses the five-way persona fan-out into one combined pass
— cheaper, but with measurably lower recall (a single context dilutes focus; see [`execution_model.md`](execution_model.md)).
It is never the silent default:
`auto` resolves to the recall-first fan-out today and will graduate to a benchmark-driven heuristic.
Recall-first means the cheap path is something a caller asks for,
not something the skill does behind your back.
## The review file
The output is one Markdown file:
YAML frontmatter, a constructive `# Summary`,
then a `## <Persona>` section per activated persona,
each holding one `### <location>` subsection per comment.
```markdown
---
date: 2026-06-28
mode: diff
base: a1b2c3d
head: 9f3a1c2
branch: my-feature
title: "Report real RSP before the trap in TrapFrame"
---
# Summary
<What the change does well; the top issues that need attention, ranked by
severity; any structural recommendations.>
## Maintainability
### `ostd/src/arch/x86/trap/mod.rs` line 12
> ```diff
> + let n = frame.size();
> ```
`descriptive-names` (nit): `n` is opaque at the point of use.
**Fix.** Rename it to `frame_size` so the call site reads on its own.
## Hardware
### `ostd/src/arch/x86/trap/mod.rs` line 30
> ```diff
> - _pad: usize,
> ```
`16b-align-rsp-before-call` (critical): removing `_pad` makes
`size_of::<TrapFrame>()` no longer a multiple of 16, but the CPU aligns `%rsp`
to 16 bytes on trap entry (System V AMD64 ABI). An odd-sized frame misaligns the
stack and is unsound for SSE.
**Fix.** Restore the padding and add a
`const_assert!(size_of::<TrapFrame>().is_multiple_of(16))`.
```
The frontmatter records the **`mode`** and what was reviewed:
- **`diff`** records the `base` (the merge-base) and the `head` (HEAD's short SHA).
It reviews committed commits, so `head` is never `-dirty`.
- **`files`** records the reviewed `files` (with any ranges) instead of a `base`;
`head` is HEAD's short SHA, suffixed `-dirty` when the working tree has uncommitted edits (files mode reviews the working tree).
The rest — `date`, `branch`, optional `title` — is common to both modes.
## The comment model
Comments are **grouped under their persona's `##` section**,
headed by that persona's review concern
**Maintainability**, **Correctness**,
**Security**, **Hardware**, **Documentation**
— rather than carrying a per-comment tag.
(The Development persona's section is titled **Correctness**: a bare `## Development` would read oddly next to the others, since all of this is development.)
Each comment is a `###` subsection that carries:
- a **location** heading — `` `path` line N `` (or `lines N-M`),
or a commit locus like `` `commit abc1234 message` `` for a finding about a commit message;
- a quoted **snippet** of the relevant lines
— a `diff` hunk (`diff` mode),
the source lines (`files` mode),
or the offending part of a commit message;
- a prose body opening with the grounding and severity — `` `<short-name>` (<severity>): ``
for a guideline, `<description> (<severity>):` for a bug —
then a statement of the problem, constructive and specific;
- a separate **Fix.** paragraph (see below).
The **grounding** is one of two kinds
— and this split is the heart of the subjective-plus-objective stance from [`motivation.md`](motivation.md):
- **guideline-backed** — the rule short-name it rests on (a lowercase kebab identifier,
e.g. `validate-at-boundaries`), rendered as code —
making a *subjective* call traceable to the shared standard rather than an opinion;
- **bug-described** — for an undeniable defect (logic error, UB, data race) that no rule covers,
a short plain-language description of the defect (e.g. "Off by one", "Use after free"), rendered as prose.
A *description*, not a coined short-name — so it never reads as a rule — and never the bare word `bug`.
This is a **first-class, encouraged** category:
a pass must hunt bugs by *reasoning*,
never suppress one because "no rule covers it".
Requiring a citation must never silence a real defect that no rule happens to name.
The **severity** is `critical`,
`major`, `minor`, or `nit` (`critical`: must fix; `major`: should fix; `minor`: worth fixing; `nit`: optional or stylistic).
It is the persona's judgement,
used to rank the Summary's top issues
— and is *not* used to gate benchmark recall.
**Every comment must propose a remedy.**
Pointing out a problem is not enough
— a comment that flags an issue with no path to resolving it is incomplete.
So each comment closes with a separate paragraph led by **Fix.**,
giving a concrete change or snippet a human reviewer or the Ralph loop can apply directly.
Two later orchestrator passes may revise a comment after it is written:
**verification** can flag it `(unverified)` or retract it,
and **consolidation** can repoint its **Fix.** at a shared remedy.
Both are described in [`execution_model.md`](execution_model.md).
## Two consumers
The review file is deliberately a plain Markdown artifact so it serves both audiences without adaptation:
1. **PR comments.**
Each comment is file+line anchored,
so a thin adapter posts the comments as inline review comments on a pull request:
[`scripts/post_reviews_to_github.sh`](../scripts/post_reviews_to_github.sh) parses a review file and posts it
(dropping any comment whose line is not on the PR diff, which GitHub would silently discard).
The workflow `.github/workflows/review_pr_with_codex.yml`, triggered by an `/aster-code-review` PR comment,
drives exactly this: run the skill via the CLI, then post with the adapter.
2. **The Ralph loop.**
The agent reads the review file and fixes the flagged issues,
then re-runs — the review is the loop's feedback signal.
The per-comment **Fix.** paragraph is what makes this directly actionable.
The loop **commits each round** and runs `diff <base>`,
so the review covers its commits (per-commit intent and hygiene included);
a quick edit it has not committed can still be reviewed with `files`.
### CI commands
On GitHub, a member triggers the skill on a PR with a `/aster-code-review` comment;
the CI mirrors the CLI/skill interface:
| Comment | Runs | Posts |
|---|---|---|
| `/aster-code-review` (or `… diff`) | review the PR diff | inline review comments |
| `/aster-code-review files <p1> … <pN>` | review those files (tracked; no spaces in a path, v1) | inline review comments |
| `/aster-code-review smoke [--problems="0002 0006"]` | the smoke test — does the skill run? | a ✅/❌ status comment |
| `/aster-code-review benchmark [--problems="0002 0006"]` | the benchmark — recall (informational) | a `recall X/Y` status comment |
`smoke`/`benchmark` run the **PR's** skill + guidelines against the **PR's** `problems.yaml`,
so a PR that adds a guideline and a fixture problem is verified end-to-end.
The comment is parsed by [`scripts/parse_pr_command.sh`](../scripts/parse_pr_command.sh) (strict allowlist),
and the workflow re-validates every value before it reaches a command.
### The CI contract
The PR-review workflow lives on the repository's default branch but runs the **PR's** copy of the skill
— a PR ships its own guidelines, so the review evolves with the code.
For that to stay robust as the skill changes,
the workflow depends on a **small, fixed surface** and nothing else:
- the profile name **`codex_workflow`**;
- **`aster_code_review.sh <mode> <args> <output>`** — the argument grammar above, with `ACR_AGENT_PROFILE` in the environment;
- **`post_reviews_to_github.sh --repo <r> --pr <n> --head-sha <sha> [--finalize] <review-file>`**;
- **`scripts/parse_pr_command.sh`** (a `/aster-code-review …` comment → a validated plan) and the **`make smoke` / `make benchmark`** targets.
Everything else — the prompt wording, the `--per-persona-context` policy, the persona set,
the review-file format, and the coding guidelines —
is internal to the skill and free to change without touching the workflow.