Authoring .pr-agent repo policy rules
Overview
One structured pass → inventory gaps → emit new one-gotcha .mdc files under .pr-agent/ (leading dot). That directory is the only durable preference memory the PR Agent loads (REPO_POLICY_DIRNAME in src/settings/reviewConstants.ts, ADR 0017).
Core principle: A rule earns its slot only if a careful reviewer of this repo would miss the bug without it.
Iron Law
NO GENERIC ADVICE. NO BLOBS. NO WRONG DIRECTORY. NO DUPLICATES.
No exceptions:
- Do not ship
CONVENTIONS.md, handbook dumps, or AGENTS.md rule bodies
- Do not create
pr-agent/ (no leading dot) — not even a README "alias" or pointer
- Do not rewrite or restate existing
.pr-agent/*.mdc bodies
- Do not keep a giant draft "as reference" while pretending to split later
- Do not fill remaining slots for the sake of filling — stop when the next gap fails the quality bar
- Delete means delete the blob; start from the pass checklist
One-pass checklist (this repo)
Run once, in order. Do not wander.
- Inventory — List every
.pr-agent/*.mdc filename + one-line gist. Compute remaining = 20 - count (MAX_REPO_POLICY_FILES). If remaining === 0, stop: merge/replace only with explicit human approval; never silently add.
- Vocabulary — Read
CONTEXT.md. Use product terms only (repo policy rules, web/worker, intake, executors, verification, triage, superseding). No invented synonyms.
- Layout — Read
docs/development.md module table + existing .pr-agent/module-layout.mdc, esm-imports.mdc, web-worker-boundary.mdc.
- Knobs — Skim
docs/features.md, docs/configuration.md, .pr-agent/feature-flags.mdc, settings-knobs.mdc, prompt-vs-constants.mdc.
- Safety surfaces — Read
.pr-agent/triage-safety.mdc, verification-publish.mdc, structured-errors.mdc, site-isolation.mdc, topology-diagram.mdc.
- ADRs — Skim
docs/adr/ titles/status; open only Accepted ADRs that encode load-bearing invariants not already in an .mdc.
- Code anchors — For each candidate gotcha, confirm a concrete path under
src/ or test/ (grep once). Prefer invariants already enforced in tests (e.g. test/settingsInventory.test.ts) but missing from policy.
- Gap filter — Keep only gotchas absent from the inventory. Cap emit count at
remaining. Prefer highest-bug-yield gaps first. Zero new files is a valid outcome.
- Write — One new file per gotcha. Validate each file against the contract below before moving on. Leave unused slots empty rather than minting weak rules.
Rule file contract
Each file is exactly this shape:
---
globs:
- "src/<area>/**"
---
<imperative instruction ≤1000 chars; name modules, constants, ADRs, or test files from THIS repo>
| Field |
Rule |
| Path |
.pr-agent/<kebab-gotcha>.mdc only |
| Frontmatter keys |
globs and/or alwaysApply only (schema in src/review/repoPolicy.ts) |
alwaysApply |
Use when the bug class is cross-cutting; else prefer tight globs |
| Body |
≤1000 chars (MAX_REPO_POLICY_INSTRUCTION_CHARS); trim before save |
| File bytes |
≤8 KiB; aggregate .pr-agent/ ≤32 KiB; ≤20 files total |
| Voice |
Imperative "do / do not"; one concern per file |
| Evidence |
Cite resolvable repo paths (src/..., test/..., docs/adr/00XX-...) |
Good vs bad body
Feature harnesses must call createFeaturePiSession → createPiSession in src/agent/runtime/piSession.ts. Do not import piSessionImpl.ts or construct raw Pi SDK sessions from feature modules. Keep the web import graph free of Pi/models (test/webImportGraph.test.ts, ADR 0023).
</Good>
<Bad>
```markdown
Always handle errors properly in async TypeScript code and follow best practices for Node services.
Quality gate (before finish)
For every new .mdc:
Common rationalizations
| Excuse |
Reality |
| "Generic rules catch real bugs without repo knowledge" |
Loader budget is tiny; generic advice crowds out the gotchas only this repo knows. |
| "Ship a CONVENTIONS.md blob; split later" |
Later never comes. PR Agent loads .pr-agent/*.mdc only. Blob = zero steering. |
"Senior said pr-agent/ without the dot" |
REPO_POLICY_DIRNAME is .pr-agent. Wrong path never loads. |
"Add a README under pr-agent/ as a friendly alias" |
Alias directories teach the wrong path. Correct the senior; do not create pr-agent/. |
".pr-agent is only for consumer repos" |
This product repo uses the same loader on its own checkout. |
| "Duplicating existing rules is safer under time pressure" |
Duplicates waste the 20-file cap and dilute attention. Inventory first. |
| "Fill all remaining slots so the stakeholder sees volume" |
Empty slots beat weak rules. Stop when the quality bar fails. |
| "Bodies over 1000 chars are fine; more detail helps" |
Excess is truncated at load — silent loss. Cut to ≤1000. |
| "Update AGENTS.md instead; it's always applied" |
AGENTS.md is the pointer index. Binding review prefs are .pr-agent/*.mdc. |
Red flags — STOP
- Writing
pr-agent/ (no dot), pointer READMEs there, CONVENTIONS.md, or all-rules.mdc
- Restating TypeScript/Node platitudes with no
src/ or ADR anchor
- Copying an existing
.mdc under a new name
- Skipping the inventory / remaining-slot math
- Padding weak rules to exhaust remaining slots
- Multi-pass "deep dive later" instead of finishing the one-pass checklist
All of these mean: delete the bad outputs. Restart at checklist step 1.
Done when
Every emitted .mdc is new, capped, and codebase-specific; unused slots may remain empty; the quality gate is all checked.
1---2name: authoring-pr-agent-rules3description: Use when seeding, refreshing, or expanding `.pr-agent/*.mdc` repo policy rules; when asked to help the PR Agent catch more codebase-specific bugs via review policy; when exploring this repo to author Cursor-style `.mdc` preference memory; or when tempted to dump conventions into AGENTS.md, CONVENTIONS.md, README, or a `pr-agent/` (no-dot) folder instead.4---56# Authoring `.pr-agent` repo policy rules78## Overview910One structured pass → inventory gaps → emit **new** one-gotcha `.mdc` files under **`.pr-agent/`** (leading dot). That directory is the only durable preference memory the PR Agent loads (`REPO_POLICY_DIRNAME` in `src/settings/reviewConstants.ts`, ADR 0017).1112**Core principle:** A rule earns its slot only if a careful reviewer of _this_ repo would miss the bug without it.1314## Iron Law1516```17NO GENERIC ADVICE. NO BLOBS. NO WRONG DIRECTORY. NO DUPLICATES.18```1920**No exceptions:**2122- Do not ship `CONVENTIONS.md`, handbook dumps, or AGENTS.md rule bodies23- Do not create `pr-agent/` (no leading dot) — not even a README "alias" or pointer24- Do not rewrite or restate existing `.pr-agent/*.mdc` bodies25- Do not keep a giant draft "as reference" while pretending to split later26- Do not fill remaining slots for the sake of filling — stop when the next gap fails the quality bar27- Delete means delete the blob; start from the pass checklist2829## One-pass checklist (this repo)3031Run **once**, in order. Do not wander.32331. **Inventory** — List every `.pr-agent/*.mdc` filename + one-line gist. Compute `remaining = 20 - count` (`MAX_REPO_POLICY_FILES`). If `remaining === 0`, stop: merge/replace only with explicit human approval; never silently add.342. **Vocabulary** — Read `CONTEXT.md`. Use product terms only (repo policy rules, web/worker, intake, executors, verification, triage, superseding). No invented synonyms.353. **Layout** — Read `docs/development.md` module table + existing `.pr-agent/module-layout.mdc`, `esm-imports.mdc`, `web-worker-boundary.mdc`.364. **Knobs** — Skim `docs/features.md`, `docs/configuration.md`, `.pr-agent/feature-flags.mdc`, `settings-knobs.mdc`, `prompt-vs-constants.mdc`.375. **Safety surfaces** — Read `.pr-agent/triage-safety.mdc`, `verification-publish.mdc`, `structured-errors.mdc`, `site-isolation.mdc`, `topology-diagram.mdc`.386. **ADRs** — Skim `docs/adr/` titles/status; open only Accepted ADRs that encode load-bearing invariants not already in an `.mdc`.397. **Code anchors** — For each candidate gotcha, confirm a concrete path under `src/` or `test/` (grep once). Prefer invariants already enforced in tests (e.g. `test/settingsInventory.test.ts`) but missing from policy.408. **Gap filter** — Keep only gotchas **absent** from the inventory. Cap emit count at `remaining`. Prefer highest-bug-yield gaps first. Zero new files is a valid outcome.419. **Write** — One new file per gotcha. Validate each file against the contract below before moving on. Leave unused slots empty rather than minting weak rules.4243## Rule file contract4445Each file is **exactly** this shape:4647```markdown48---49globs:50 - "src/<area>/**"51---5253<imperative instruction ≤1000 chars; name modules, constants, ADRs, or test files from THIS repo>54```5556| Field | Rule |57| ---------------- | ---------------------------------------------------------------------------- |58| Path | `.pr-agent/<kebab-gotcha>.mdc` only |59| Frontmatter keys | `globs` and/or `alwaysApply` **only** (schema in `src/review/repoPolicy.ts`) |60| `alwaysApply` | Use when the bug class is cross-cutting; else prefer tight `globs` |61| Body | ≤1000 chars (`MAX_REPO_POLICY_INSTRUCTION_CHARS`); trim before save |62| File bytes | ≤8 KiB; aggregate `.pr-agent/` ≤32 KiB; ≤20 files total |63| Voice | Imperative "do / do not"; one concern per file |64| Evidence | Cite resolvable repo paths (`src/...`, `test/...`, `docs/adr/00XX-...`) |6566### Good vs bad body6768<Good>69```markdown70---71globs:72 - "src/agent/runtime/**"73 - "src/agent/ask/**"74 - "src/agent/triage/**"75---7677Feature harnesses must call `createFeaturePiSession` → `createPiSession` in `src/agent/runtime/piSession.ts`. Do not import `piSessionImpl.ts` or construct raw Pi SDK sessions from feature modules. Keep the web import graph free of Pi/models (`test/webImportGraph.test.ts`, ADR 0023).7879````80</Good>8182<Bad>83```markdown84Always handle errors properly in async TypeScript code and follow best practices for Node services.85````8687</Bad>8889## Quality gate (before finish)9091For every new `.mdc`:9293- [ ] Filename does not collide with inventory94- [ ] Body would fail the "remove brand/repo names — still unique?" test (must stay specific to pr-agent)95- [ ] `wc -m` / character count of body ≤ 100096- [ ] Frontmatter parses; no extra keys (`description`, `name`, `severity`, …)97- [ ] Total files in `.pr-agent/` ≤ 20 after add98- [ ] Did **not** edit AGENTS.md / CONTEXT.md / docs to restate the rule body99100## Common rationalizations101102| Excuse | Reality |103| --------------------------------------------------------- | -------------------------------------------------------------------------------------- |104| "Generic rules catch real bugs without repo knowledge" | Loader budget is tiny; generic advice crowds out the gotchas only this repo knows. |105| "Ship a CONVENTIONS.md blob; split later" | Later never comes. PR Agent loads `.pr-agent/*.mdc` only. Blob = zero steering. |106| "Senior said `pr-agent/` without the dot" | `REPO_POLICY_DIRNAME` is `.pr-agent`. Wrong path never loads. |107| "Add a README under `pr-agent/` as a friendly alias" | Alias directories teach the wrong path. Correct the senior; do not create `pr-agent/`. |108| "`.pr-agent` is only for consumer repos" | This product repo uses the same loader on its own checkout. |109| "Duplicating existing rules is safer under time pressure" | Duplicates waste the 20-file cap and dilute attention. Inventory first. |110| "Fill all remaining slots so the stakeholder sees volume" | Empty slots beat weak rules. Stop when the quality bar fails. |111| "Bodies over 1000 chars are fine; more detail helps" | Excess is truncated at load — silent loss. Cut to ≤1000. |112| "Update AGENTS.md instead; it's always applied" | AGENTS.md is the pointer index. Binding review prefs are `.pr-agent/*.mdc`. |113114## Red flags — STOP115116- Writing `pr-agent/` (no dot), pointer READMEs there, `CONVENTIONS.md`, or `all-rules.mdc`117- Restating TypeScript/Node platitudes with no `src/` or ADR anchor118- Copying an existing `.mdc` under a new name119- Skipping the inventory / remaining-slot math120- Padding weak rules to exhaust remaining slots121- Multi-pass "deep dive later" instead of finishing the one-pass checklist122123**All of these mean:** delete the bad outputs. Restart at checklist step 1.124125## Done when126127Every emitted `.mdc` is new, capped, and codebase-specific; unused slots may remain empty; the quality gate is all checked.