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
2 changes: 1 addition & 1 deletion .aethyme/generated/onboarding.json
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@
]
},
"freshness": {
"source_digest": "6a4bec8975cc8172bad693d32fbf33724a37dfd7958b06118ec8a665f0b01c57",
"source_digest": "1cc729348eeead667dcc0ac274bc2e9cb91df1799a0a670316fd6e3abacd14f5",
"source_commit": null,
"source_file_count": 539,
"generator": "deterministic"
Expand Down
6 changes: 6 additions & 0 deletions .aethyme/repository.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"schema_version": 1,
"applied_migrations": [
"repository-deployment-v1"
]
}
194 changes: 194 additions & 0 deletions .codex/skills/aethyme-review-rule-maker/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
---
name: aethyme-review-rule-maker
description: Write or tune this repository's Aethyme review rules -- which
changes need which reviews, who performs them, and what appears on the pull
request. Use when asked to set up, enable, debug, or change review routing,
review triggers, PR labels, or the `[review.*]` tables in
`.aethyme/config.toml`.
---

# Aethyme Review Rule Maker

Three tables in `.aethyme/config.toml` decide which reviews a change needs
(`[review.trigger]`), who performs them (`[review.routing]`), and what the pull
request shows (`[review.projection]`). All three are off by default. This skill
is the procedure for writing them against a real repository instead of from
imagination.

Full field grammar: [`references/review-rules.md`](references/review-rules.md).
Load it when you need a field's exact name, type, or default.

## The one rule that shapes everything else

**A rule is a floor an author cannot lower.** Eligibility is the union of what
the repository requires and what the author declared in commit trailers, never
the intersection. So the cost of a rule that is too broad is wasted reviews,
and the cost of one that is too narrow is a missed review. Write the narrow
guards first and widen them with evidence.

## Procedure

### 1. Read the current state before changing anything

```bash
aethyme broker advanced review plan --base <default-branch> --pr 0
```

It performs nothing. `trigger_enabled`, `routing_enabled`,
`projection_enabled` tell you what is already on; `changed_paths` and
`classification` tell you whether the command is reading the tree you expect.
If `changed_paths` is 0 against a branch with work on it, you are standing in
the wrong worktree -- compare `policy_root` and `change_root` in the report.

### 2. Find the paths that must never miss a review

Do not guess, and do not ask the user to guess. Look, then propose a list for
them to cut down. Search for the places where a change can grant itself
authority or move money:

```bash
# CI that can mint credentials, and anything that edits it
ls .github/workflows/ .gitlab-ci.yml 2>/dev/null
# authentication, authorization, secrets, crypto
rg -l --files-with-matches -i 'authenticat|authoriz|\bjwt\b|secret|credential|private_key' --glob '!**/test*' | head -30
# money and data destruction
rg -l -i 'stripe|payment|charge|refund|DROP TABLE|delete_all' --glob '!**/test*' | head -20
# schema migrations
fd -t d -d 3 'migrations?' 2>/dev/null | head
```

Present the candidates and say which you would keep. A list of three to six
paths is a working guard; a list of forty is a repository-wide review under
another name, and the schedule will end up silently dropping most of it.

### 3. Name the dimensions

`require = [...]` takes free strings, not an enum. Pick names the repository
would use in conversation -- `code`, `security`, `performance`, `data`,
`accessibility`, `i18n`. Each name is separately routed and separately
budgeted, so a dimension is worth creating exactly when it would go to a
different reviewer or deserve a different quota. Two dimensions that always
go to the same place with the same budget should be one.

### 4. Write the trigger table only, and leave the rest off

```toml
[review.trigger]
enabled = true

[[review.trigger.rule]]
name = "always-code-review"
require = ["code"]

[[review.trigger.rule]]
name = "security-sensitive-paths"
require = ["security"]
paths = [".github/workflows/**", "src/auth/**"]
```

Routing and projection stay off through this step. A wrong rule now costs a
line in a JSON report; a wrong rule with projection on costs a label on
somebody's pull request.

### 5. Replay it against real history

This is the step that separates a rule that works from one that reads well.
Take branches that actually merged and check what the policy would have said:

```bash
for base in HEAD~1 HEAD~5 HEAD~20; do
echo "=== $base ==="
aethyme broker advanced review plan --base "$base" --pr 0 \
| python3 -c 'import json,sys; d=json.load(sys.stdin); print([ (e["review_type"], e["because"]) for e in d["eligible"] ])'
done
```

Read the `because` array, not just the dimension: it names the rule that
fired. A dimension appearing with a rule you did not expect is the rule to fix.

Two failure shapes to look for specifically:

- **Every change eligible for everything.** Usually a rule with no conditions
that was meant to have one, or `paths` written with a leading `/` or a
`./` prefix -- paths are repository-relative, exactly as `git diff
--name-only` prints them.
- **A guarded path producing nothing.** Check the glob: `*` matches one
segment and `**` matches any number including zero, so `src/*/auth.rs` does
not match `src/a/b/auth.rs` but `src/**/auth.rs` does.

### 6. Set the budget before turning on a backend

Rules decide what a change deserves. `schedule` decides what it gets, and it
is the half that keeps an active branch from spending forty security reviews.

```toml
[review.trigger.default_schedule]
debounce_seconds = 600
max_per_pull_request = 8

[review.trigger.schedule.security]
debounce_seconds = 0
max_per_pull_request = 0
```

Ask the concrete question rather than the abstract one: *on the busiest pull
request this repository had last month, how many reviews of each dimension
would have been acceptable?* That number is `max_per_pull_request`. Exempt a
dimension from the cap only when a missed one is worse than a wasted one --
which is usually true of `security` and usually false of everything else.

### 7. Turn on projection, then routing

In that order. Projection writes a comment and labels, which a human can read
and delete. Routing starts agents and mentions bots, which a human cannot
un-start.

```toml
[review.projection]
enabled = true
label_prefix = "aethyme/"
reserved = ["skip-review"]
```

`label_prefix` is the whole safety mechanism: Aethyme never touches a label
outside it. `reserved` names labels inside it that Aethyme reads and never
writes -- that is where a human parks a decision the broker must not
recompute away.

```toml
[review.routing]
enabled = true

[review.routing.default_route]
backend = "record"

[review.routing.route.security]
backend = "chau7"
max_concurrent = 1
```

Keep `default_route` on `record`. A dimension someone adds later then gets
written down rather than silently starting an agent nobody budgeted for.

## Asking the coder for classification

Rules can read what the author declared. Trailers cost a coding agent nothing
-- it is already writing a commit message and already knows what it changed:

```
Area: backend
Surface: auth
Risk: high
Review: security
```

To get them written, add the instruction to `.aethyme/overrides/agents.json`
and rerun `aethyme deploy --repo "$PWD"`. Do not rely on them for a
guard: a trailer is one line of unverified text, which is exactly why it can
escalate a review and can never waive one.

## What to hand back

A diff of `.aethyme/config.toml`, plus the `review plan` output for two or
three real changes showing what the new rules decide. Do not report a policy as
working on the strength of it parsing.
147 changes: 147 additions & 0 deletions .codex/skills/aethyme-review-rule-maker/references/review-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# `[review.*]` field reference

Every table lives in `.aethyme/config.toml` at the repository root, is read
independently, and is off unless it says `enabled = true`. Unknown keys are
rejected at load rather than ignored, so a typo fails loudly.

## `[review.trigger]`

| Key | Type | Default | Meaning |
| --- | --- | --- | --- |
| `enabled` | bool | `false` | Off means no dimension is ever eligible. |
| `schema_version` | int | current | A version newer than the broker understands is an error, never a fallback. |
| `rule` | array of tables | `[]` | See below. |
| `default_schedule` | table | 600s / 8 / false | Budget for a dimension with no entry in `schedule`. |
| `schedule.<dimension>` | table | -- | Budget for one dimension. |

### `[[review.trigger.rule]]`

A rule fires when **every** condition it names holds. A rule that names no
condition matches every change.

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `require` | list of strings | **yes** | Dimensions this rule demands. An empty or missing `require` is rejected at load. |
| `name` | string | no | Appears in the decision's `because`. Name every rule; the report is unreadable otherwise. |
| `on` | list | no | Lifecycle transitions. Empty means any. |
| `paths` | list of globs | no | Repository-relative. Matching any one path satisfies the condition. |
| `areas` | list | no | `Area:` values the author declared. |
| `surfaces` | list | no | `Surface:` values the author declared. |
| `min_risk` | string | no | Declared `Risk:` at or above this level. |
| `from_fork` | bool | no | Only when the change comes from a fork. |
| `first_time_contributor` | bool | no | Only when the author has not landed here before. |
| `authored_by_model` | bool | no | `true` only when a `Model:` trailer names one; `false` only when none does. |
| `models` | list | no | Only when the declared `Model:` is one of these, compared case-insensitively. |

**Globs.** `*` matches exactly one path segment. `**` matches any number of
segments including zero. Paths are repository-relative with no leading `/` or
`./`, exactly as `git diff --name-only` prints them.

**`on` values.** `pull_request_opened`, `ready_for_review`, `reopened`,
`replacement_commit` (force-push, amend, rebase), `additional_commit`,
`base_retargeted`, `review_dismissed`, `scheduled`, `manual`.

`replacement_commit` and `additional_commit` are separate on purpose: a
force-push that rewrites the same logical change and a commit stacked on top of
already-reviewed work justify different responses.

`merge_queue_entered` is **rejected at load**: nothing a tick can ask the
provider distinguishes it, so a rule waiting for it would never fire. Do not
write it.

The transition is derived by comparing the pull request against the last
observation of it, not delivered by a webhook, so it is available to any tick
and needs no inbound endpoint.

**`min_risk` ranking.** `none` < `low` < `high` = `critical`. An unrecognised
value ranks *above* `low`, so a typo in a risk trailer escalates rather than
silently downgrading.

### `[review.trigger.schedule.<dimension>]`

| Key | Type | Default | Meaning |
| --- | --- | --- | --- |
| `debounce_seconds` | int | `600` | Minimum gap between two reviews of this dimension on one pull request. Inside the window the decision is `defer`, which a later tick reconsiders -- not `skip`, which is settled. |
| `max_per_pull_request` | int | `8` | Cap per pull request. `0` means no cap. |
| `always_on_new_head` | bool | `false` | Re-review when the head moves even past the cap. |

A dimension already reviewed at the current head is skipped before either of
these is consulted.

## `[review.routing]`

| Key | Type | Default | Meaning |
| --- | --- | --- | --- |
| `enabled` | bool | `false` | Off means every dimension is recorded and nothing is performed. |
| `workspace_root` | string | `.aethyme/reviews` | Where a Chau7 review's workspace is created. Relative to the repository root. |
| `default_route` | table | `backend = "record"` | Route for a dimension with no entry in `route`. |
| `route.<dimension>` | table | -- | Route for one dimension. |

### `[review.routing.route.<dimension>]`

| Key | Type | Default | Meaning |
| --- | --- | --- | --- |
| `backend` | `chau7` \| `provider_comment` \| `record` | **required** | Who performs it. |
| `mention` | string | -- | **Required for `provider_comment`.** Stored without the `@`. |
| `instructions` | string | -- | Appended to the generated Chau7 prompt. The generated part is not replaceable. |
| `max_concurrent` | int | `2` | Reviews of this dimension in flight at once, repository-wide. `0` is unbounded. |
| `stale_after_minutes` | int | `360` | Give up on an unfinished review after this long without an update, mark it `abandoned`, and ask again. Stops a dead reviewer holding a slot forever. `0` is never. |

Budgets are per dimension, so a saturated security queue does not stop a code
review being requested.

A Chau7 review whose workspace already holds a live tab is deferred rather than
started twice. The check is on the directory, not the branch: a tab that
wandered onto another branch still occupies that workspace.

## `[review.projection]`

| Key | Type | Default | Meaning |
| --- | --- | --- | --- |
| `enabled` | bool | `false` | Off means no comment and no label. |
| `comment` | bool | `true` | Maintain one Aethyme-owned comment, found by the marker `<!-- aethyme:review -->` and edited in place. |
| `label_prefix` | string | `aethyme/` | Every label Aethyme writes starts with this. A label outside it is somebody else's and is never touched. Empty is rejected at load. |
| `label_areas` | bool | `true` | `aethyme/area:backend` |
| `label_surfaces` | bool | `true` | `aethyme/surface:auth` |
| `label_risk` | bool | `true` | `aethyme/risk:high` |
| `label_reviews` | bool | `true` | `aethyme/review:security`, while one is outstanding. |
| `reserved` | list of strings | `[]` | Label suffixes inside the prefix that Aethyme reads and never writes or removes. |
| `create_missing_labels` | bool | `true` | `gh pr edit --add-label` fails outright on an unknown label. |

`reserved` is the difference between a recomputable label and a decided one.
`aethyme/area:backend` can be deleted and rederived from a trailer still in the
commit. A human's `aethyme/skip-review` has no source to rederive from -- once
written, it *is* the source, and without `reserved` reconciliation would see an
unrecognised label under its own prefix and delete the only record of that
judgement.

## Commit trailers

| Trailer | Read by | Projected as |
| --- | --- | --- |
| `Area:` | a rule's `areas` | `aethyme/area:<value>` |
| `Surface:` | a rule's `surfaces` | `aethyme/surface:<value>` |
| `Risk:` | a rule's `min_risk` | `aethyme/risk:<value>` |
| `Review:` | nothing -- asks for a dimension directly | `aethyme/review:<value>` |
| `Model:` | a rule's `authored_by_model` and `models` | nothing -- who wrote a change is the author's to disclose |

Values are comma-separated and case-insensitive. Trailers may appear anywhere
in the body and are merged across every commit in the change: areas and
surfaces union, the highest risk wins, and the first `Model:` declared wins.

`Model:` is a declaration, not a detection. `Co-Authored-By` is deliberately not
read: it is written by convention, carries a display name rather than a stable
identifier, and appears on commits a model only helped with.

A declaration can add a review and can never remove one.

## Errors at load

| Error | Cause |
| --- | --- |
| `rule N requires no review types` | A rule with an empty `require`. |
| `rule N waits for <trigger>, which no tick can report` | `on` names `merge_queue_entered`. Remove it; the rule would never fire. |
| `unknown field` | A typo, or a field from a newer broker. |
| `schema_version N is newer than this broker understands` | Upgrade `aethyme`, or pin the policy. It refuses rather than falling back, because silently reviewing nothing is the failure this must not have. |
| `route <name> uses provider_comment without a mention` | `provider_comment` with no bot to mention. |
| `label_prefix must not be empty` | An empty prefix would make every label in the repository Aethyme's. |
Loading
Loading