Change Review
Review and cleanup at the scope of a change-set — what's on this branch, in this PR, in the workspace diff, or in recently modified files. Type-safety and imports for .ts/.tsx, and HTML/CSS/markup conventions, all live here.
Modes
Two modes. Default to REVIEW unless the user's verb is execute-shaped or they've explicitly picked items from a prior REVIEW.
| Mode |
Trigger verbs |
Output |
Workflow |
| REVIEW |
review, evaluate, audit, analyze, breakdown, compare, "is X dead?", "is X used?", "anything I'm missing?" |
Numbered findings in chat. No edits, no commits, no GitHub comments. |
references/review.md |
| APPLY |
clean up, delete, remove, rip out, fix N, "go ahead", "implement", "wire up", deslop, tighten, refactor pass |
Edits + optional commit. |
references/apply.md |
A user who says "review this" and then replies "fix 2, 3, 5" has moved from REVIEW to APPLY. The numbered list is the bridge — REVIEW output must be cherry-pickable by number.
Scope
A change-review is change-scoped, never repo-wide. Pick scope in this order:
- Conductor workspace —
mcp__conductor__GetWorkspaceDiff with stat: true first, then specific files, when the harness exposes it; otherwise git and gh against the Conductor target branch (see /conductor).
- Open PR —
gh pr diff or gh pr view --json files,baseRefName.
- Branch vs base —
git merge-base origin/<base> HEAD, then git diff <merge-base> HEAD (or three-dot git diff origin/<base>...HEAD). Always git fetch origin <base> first; local refs go stale silently.
- Staged / uncommitted —
git diff --staged and/or git diff HEAD.
- Recently modified files — only as a last resort, and only files the user explicitly named or you edited earlier in this conversation.
Never diff the full range between two long-lived branches (dev...main) — that pulls in unrelated merged work and pollutes the review.
State which scope you used in the report's first line. The answer to "how many files changed?" differs by tool, and the user will ask.
Report change size as +added / −removed from git diff --shortstat or the PR's own counts (gh pr view --json additions,deletions), never wc -l of a raw diff — see /git-workflows.
REVIEW Mode (default)
Brief — full workflow in references/review.md.
- Read-only. No edits, no commits, no GitHub/Linear comments unless explicitly authorized. Output is chat text only. When posting is authorized, attribute each agent-authored comment per
/pr-guidelines (open with an italic *<model>:* prefix).
- Fan out across parallel subagents for any non-trivial diff — a single-pass skim cannot cover a real change-set. Standard axes: bug scan, AGENTS.md/CLAUDE.md compliance, dead code & duplication, LOC & complexity. Add a spec-conformance axis whenever the change traces to a ticket/PRD/RFC — it runs in parallel and reframes the other findings, but the spec is an input, not ground truth (the doc/ticket is often the stale side, not the code), so divergences are reconciliation items for the author, not automatic code defects. See references/review.md.
- Model tier per GLOBAL.md: review and validator subagents run on the strongest tier at high or extra-high effort; drop to a faster model only for pure retrieval (gathering files, grepping call sites) that an analysis agent then reasons over.
- Validate each finding with a second-pass subagent before reporting — a fresh context adversarially refuting another agent's finding, never an agent re-checking its own work. Validation is where filtering happens, which is what lets the finders stay open.
- Cite file path + line range on every finding. Never restate the diff.
- Numbered list with stable IDs (
#1, #2, ...) so the user can reply "fix 2, 3, 5". Findings are grouped into Clear fixes (one right solution, all get applied regardless of severity) vs Decisions needed (a product/design choice gates the fix — options + one recommendation each); run each finding through /what before it reaches the report. See references/review.md.
- High signal in the report, not in the finders. Finders report everything with confidence and severity; validation filters. The explicit false-positives list (pre-existing issues, linter-catchable, pedantic nits) is a category exclusion that binds every stage.
- End with a handoff suggestion: APPLY the clear fixes, answer the decision items, run
/pr-guidelines to refresh the description, or defer to a follow-up PR.
APPLY Mode
Brief — full workflow in references/apply.md.
Two entry paths:
- From a prior REVIEW: user picks items by number ("fix 2, 3, 5", "do all 4", "in stages, dead code first"). Execute only the picked items.
- Standalone cleanup intensity: the user names the depth.
| Intensity |
Trigger phrases |
Scope |
| Light (deslop) |
"deslop", "remove slop", "clean up the AI stuff" |
Strip AI artifacts. No structural changes. Workflow → |
| Heavy (refactor) |
"refactor pass", "tighten up", "dead code" |
Structural cleanup, dead path removal, build/test verification. Workflow → |
| Targeted |
"fix 2, 3, 5", "do all 4", "in stages" |
Execute picked items from a prior REVIEW. Workflow → |
Phased over big-bang. The user repeatedly steers toward "in stages, dead code first, then consolidation". Default to proposing a phased plan; execute one phase, pause, then continue.
Shared Principles
These apply to both modes — they shape what counts as a finding (REVIEW) and what counts as a clean diff (APPLY).
Primary outcome: net LOC reduction
After cleanup, total LOC should usually be lower than before. If cleanup increases LOC, keep it only when it clearly reduces complexity or risk, and call out that tradeoff explicitly.
Diff size is itself a finding. A version bump that produces 20k+ lines of diff, or a feature that costs 10k+ lines for a small surface, is suspect — flag it and look for the maintainer-provided codemod, an idiomatic API the project missed, or generated content that should be excluded from review.
When categorizing a large diff, split into: generated / boilerplate / moved / new logic. The user evaluates PR quality partly by the new-logic fraction.
What to remove
- Extra defensive checks or try/catch blocks abnormal for that codepath (especially if called by trusted/validated callers).
- Casts to
any or as to get around type issues (see Type safety).
- Unnecessary or redundant type annotations.
- Variable declarations only used once right after declaration — inline them.
- Style inconsistent with the surrounding file.
What NOT to remove
useCallback, useMemo, memo, or other performance primitives — only change with profiling evidence or explicit user direction.
- Intentional scaffolding. Re-export barrels, design-system primitives (
*.primitives.tsx), and framework-required exports may look "unused" to knip but exist for a reason. Ask before pruning. (No tension with Barrel files: never create new barrels; don't delete existing ones without asking.)
- Repetition that serves an argument. Callbacks, deliberate restatement, or layered comments that reinforce intent are not duplication. Only flag fully duplicated / redundant sections.
- Specific semantic intent. A
<dialog> wrapper exists for top-layer semantics; a button-styled-as-link exists for download behavior. Read the intent before flattening.
No shipped stubs, mocks, or temporary values
Stubs, mocks, hardcoded fixtures, "temporary" literals, debug values, and inline test data do not ship. If you wired a UI, query, or branch to a stand-in during development, replace it with the real source before declaring the work done. The risk isn't sloppiness — it's that a forgotten stub silently shapes behavior, and when the feature misbehaves weeks later, the cause is invisible and the debugging trail leads in the wrong direction.
If a stub must exist mid-stream (active debugging, intentional prototyping), make it greppable through its identifier (a stub/mock-prefixed name), never through a comment. Before handoff, search the touched feature for STUB, MOCK, fixture arrays, and hardcoded values that mirror enum or option labels, and remove them. Unmarked stubs are a first-class REVIEW finding.
The 1000-line ceiling
A file may not cross from below 1000 lines to above. This applies to REVIEW (flag the violation) and APPLY (decompose before letting a change push the file over).
The only valid waiver: the file is extremely uniform — a long data table, generated code, a flat enum, a list of route registrations — where any split would hurt readability. If the file has meaningful control flow or distinct sections, decompose first. Don't waive because the new code "logically belongs here" or because splitting "is a lot of work."
When APPLY is about to push a file across the line, stop and propose the decomposition (subcomponents, helpers, separate modules) before continuing.
Cleanup uncovers more cleanup
After removing a feature, branch, or component, search the codebase for sibling code that's now dead — utilities only it called, design tokens only it used, GraphQL fields only it queried, fixtures only it referenced, schema columns only it wrote. A one-shot deletion that only removes the named thing under-delivers. The user expects the cleanup to follow the thread.
Scope guard: stay within the change-set's natural boundary. "Sibling code that became dead because of this change" is in scope. "Sibling code that was always dead but you noticed in passing" is a follow-up, not this PR.
List ordering
Every list has an intrinsic best order. Match the list's purpose rather than defaulting to alphabetical:
- Alphabetical for catalogs read like a glossary (dependency blocks, env keys, allowlists, enum members consumed by humans).
- Dependency/logical when earlier entries set up later ones (import groupings, CSS declaration order, pipeline stages).
- Frequency or salience for lookup tables where readers scan for common cases first.
- Numeric/temporal for sequence-bearing data (versioned migrations, dated entries).
When adding an entry, place it in the correct position rather than appending. The only reason to break the intrinsic order is a hard syntactic or logical constraint.
For config files that combine tool defaults with project overrides, encode only the deviations: a short config that diverges meaningfully is more readable than a long one that mostly restates the defaults. Before adding an option, check whether it matches the default; if so, omit it.
Comment policy
Comments are banned. Every comment in touched code is a finding: delete it, and when it carried information, move that information into the code (a named function or constant, a better identifier, a restructure) or out of the source entirely (PR description, commit message, docs). A construct that seems to need a comment is not clear enough and gets rewritten. Machine-read directives (shebangs, codegen pragmas) are code, not comments; lint suppressions remain a last resort governed by the lint-directives rule. The single exception: TODO-style markers the user specifically requested.
Don't silence the tool; don't roll your own codemod
When a tool reports something you've already accepted as correct, run it and let downstream state settle — don't reach for ignore / exclude / skip config to silence it. When the upstream maintainers publish an official migration path (codemod, preset, framework-provided helper), prefer it over a handwritten substitute, even when the resulting diff is larger. A 20k-line maintainer codemod is more trustworthy than a 2k-line homegrown one. Parallel subagents > scripted refactors when no official codemod exists.
A lint rule targets a behavior, not a token. Switching to a sibling construct that produces the same flagged output — a wrapper or alternate API that emits exactly what a no-X rule forbids — dodges the rule without honoring it, and is still a workaround. Two honest paths: fix at the root so the rule passes on its merits, or, when the rule genuinely doesn't fit the case, write a real standard for the codebase and disable the rule deliberately. An off-the-cuff sibling swap is neither.
Inline suppression is a last resort, valid only when a rule blocks the sole viable approach and no compliant alternative exists. Exhaust the alternative first and offer the simpler-code path before suppressing: a rule against array-index list keys is satisfied by a stable id from the data, not a disable comment. Type-safety escape hatches (any, as, !, ts-ignore) are never the last resort — see Type safety.
Types, imports & tooling (.ts, .tsx)
Applies when writing or reviewing TypeScript: typecheck failures, strictness, generics, barrels, module layout — not only during cleanup passes.
Type safety
Never compromise type safety: No any, no type assertions (as Type), no non-null assertions (!), no ts-ignore/eslint-disable/lint-disable. Avoid unknown unless narrowed immediately. This holds equally in backend resolvers and services — a cast in a query layer is no more acceptable than one in a component.
A cast is a symptom: the type is too wide somewhere upstream. Fix the source, not the call site.
- Tighten the upstream type so the cast and its guard both disappear. When a parameter is declared wider than its only callers supply —
string where the caller already hands over the source's union or enum — narrow the parameter to the real type. The cast and the runtime guard that defended it both fall away.
- Invert, don't widen-then-cast. When a value doesn't fit a consumer, the fix is rarely to broaden the consumer's type and cast at the boundary. Broaden in the wrong direction and every caller inherits the looser contract. Instead, make the real type flow through from where it originates.
- Parse at the boundary, narrow within it. Validate untrusted input with a schema (zod or equivalent) at the edge so a typed value flows inward; narrow runtime variants with
instanceof or a discriminant. Both replace the assertion with a check the compiler trusts.
- Don't assert how the type system behaves. A claim that "TS widens this" or "the inference fails here" is a verification step — confirm it with a minimal repro before designing around it, never from intuition.
Component & prop style
- Order prop intersections: specific props before generic (
{ specific } & RootProps).
- Favor readability over brevity; avoid mirror variables.
- Comments only for non-obvious logic, never narration.
- Follow existing conventions: use
rg, fd, git history before adding patterns.
- Don't declare variables only used once immediately after; inline them.
Imports & dependencies
- Import order: React → runtime → external → internal → aliased → relative → local.
- Use
type keyword for type imports: import type { Foo } from './types'.
- Dependencies in
package.json: alphabetical.
Barrel files
- No barrel files — don't create
index.ts re-export files. Import directly from source modules.
Checks
Run type/lint checks yourself when relevant; don't ask the user to run them.
HTML, CSS & templates
Markup and styles for .html, .css, and templated/JSX UI. Deep dive: Web Interface Guidelines.
Semantic HTML first
Prefer built-in semantics over generic containers: structure (article, header, main, nav, section, ul/li), interactive (button, form, label), content (table, time). Avoid div/span unless necessary. Prefer real text + structure over ARIA-only shortcuts.
Layout
- Flexbox/grid with
gap for spacing between children.
margin is a code smell — prefer container padding or gap; margins break encapsulation.
- Logical properties:
block/inline, start/end over physical left/right/top/bottom where appropriate.
- Transform sub-properties (
translate, rotate, scale) over a single long transform when the stack allows it.
Declaration order
Order by concern, outside-in (not alphabetically): position & display → flex/grid container & child → sizing & spacing → overflow → typography → visual (color, background, border, shadow) → transform & animation → interaction (cursor, pointer-events, user-select). Applies to CSS-in-JS objects too.
State styling
Drive selected/active/expanded state with a data attribute and an attribute selector ([data-state="active"] {…}, [aria-pressed="true"]), not a conditional className or cx() merge in the component. The DOM stays declarative, the styling lives with the rest of the component's CSS, and the state is inspectable in devtools without reading render logic.
Layout stability
Avoid layout shift (CLS) by holding geometry constant across state and breakpoint:
- Container queries, not viewport queries, when the available width is set by a sibling. If a region's space is driven by a collapsible panel, resizable sidebar, or split pane rather than the viewport, set
container-type: inline-size on the layout that owns the width and query it (@container). Viewport queries respond to the wrong axis and break when the surrounding layout changes. Audit existing @media width queries for this case when touching responsive layout.
- Keep controls present and in the same order across every variant. A control that appears in one state should occupy the same slot in the others rather than appearing, disappearing, or reordering. Consistent placement prevents both the UX surprise of a moving target and the reflow when an element pops into the flow.
- A border that must not change box height becomes an
inset box-shadow. A 1px border adds to height; toggling it between states shifts everything below by a pixel. Use box-shadow: inset 0 0 0 1px … instead, or keep an equal transparent border (border: 1px solid transparent) in every state so the box height never changes.
Colors
Design tokens / CSS custom properties first; otherwise oklch or hex — not rgb for new work.
CSS over JavaScript
Prefer CSS for visuals and motion when it matches JS behavior — less bundle work, easier prefers-reduced-motion, better separation.
Accessibility
- Prefer visually hidden real text (
srOnly) over duplicating meaning in aria-label alone.
- Custom click targets need keyboard support (Enter/Space). Every interactive element needs a visible
:focus-visible style — never outline-none without a replacement.
- Viewport units:
dvw/dvh over vw/vh where mobile chrome matters.
Images
Explicit width and height (or constrained aspect) to limit CLS. loading="lazy" below the fold; prioritize above-the-fold / LCP images.
Markup & CSS tips
mask-image for gradient fades works across arbitrary backgrounds.
- Fix SVG
viewBox at the asset, not at every call site.
Workflow Shape (when in doubt)
Both modes share the same skeleton: explore → propose → approve → apply. The difference is what each phase produces.
| Phase |
REVIEW |
APPLY |
| Explore |
Establish scope, fan out across axes, gather findings. |
Read the picked items (or the change-set). |
| Propose |
Numbered findings, file:line cited, validated. |
Numbered plan of edits, largest-to-smallest, with scope tag. |
| Approve |
User picks items by number or replies with refinements. |
User approves all, picks by number, or denies. |
| Apply |
Hand off to APPLY mode (or stop, or refresh PR prose). |
Make approved changes. Run build/tests. Don't auto-commit. |
End every session with a short summary: what changed, what's deferred, and the next handoff (e.g. "run /pr-guidelines to refresh the description" or "the dead-code thread continues into the unused GraphQL fields").
1---2name: change-review3description: Use when reviewing or cleaning up a change-set — a branch, PR, workspace diff, staged changes, or recent commits. Triggers on "review the changes", "review this PR", "review my branch", "anything I'm missing", "is this dead?", "clean up", "deslop", "tighten", "simplify", "dedup", "reduce LOC", "refactor pass", "knip", "find dead code". Also covers TypeScript types/imports/barrels and HTML/CSS/markup conventions (semantic elements, flex/grid, declaration order, a11y, tokens, CLS). Two modes: REVIEW (read-only, numbered findings) and APPLY (make changes). Owns every review and cleanup of the user's own change-set in this setup, including when a harness ships its own review command.4---56# Change Review78Review and cleanup at the scope of a change-set — what's on this branch, in this PR, in the workspace diff, or in recently modified files. Type-safety and imports for `.ts`/`.tsx`, and HTML/CSS/markup conventions, all live here.910<!-- @> Two modes: REVIEW (read-only — numbered findings citing file:line, no edits/commits/GitHub comments unless authorized) and APPLY (execute picks from a prior review, or run cleanup intensity). Default to REVIEW unless the verb is execute-shaped -->1112## Modes1314Two modes. **Default to REVIEW** unless the user's verb is execute-shaped or they've explicitly picked items from a prior REVIEW.1516| Mode | Trigger verbs | Output | Workflow |17| ---------- | ------------------------------------------------------------------------------ | ----------------------------------------------------- | ------------------------------------- |18| **REVIEW** | review, evaluate, audit, analyze, breakdown, compare, "is X dead?", "is X used?", "anything I'm missing?" | Numbered findings in chat. No edits, no commits, no GitHub comments. | [references/review.md](references/review.md) |19| **APPLY** | clean up, delete, remove, rip out, fix N, "go ahead", "implement", "wire up", deslop, tighten, refactor pass | Edits + optional commit. | [references/apply.md](references/apply.md) |2021A user who says *"review this"* and then replies *"fix 2, 3, 5"* has moved from REVIEW to APPLY. The numbered list is the bridge — REVIEW output must be cherry-pickable by number.2223## Scope2425A change-review is **change-scoped**, never repo-wide. Pick scope in this order:26271. **Conductor workspace** — `mcp__conductor__GetWorkspaceDiff` with `stat: true` first, then specific files, when the harness exposes it; otherwise git and `gh` against the Conductor target branch (see [`/conductor`](../conductor/SKILL.md)).282. **Open PR** — `gh pr diff` or `gh pr view --json files,baseRefName`.293. **Branch vs base** — `git merge-base origin/<base> HEAD`, then `git diff <merge-base> HEAD` (or three-dot `git diff origin/<base>...HEAD`). Always `git fetch origin <base>` first; local refs go stale silently.304. **Staged / uncommitted** — `git diff --staged` and/or `git diff HEAD`.315. **Recently modified files** — only as a last resort, and only files the user explicitly named or you edited earlier in this conversation.3233<!-- @> Never diff the full range between two long-lived branches (e.g. dev...main) — pulls in unrelated merged work and pollutes the review -->3435Never diff the full range between two long-lived branches (`dev...main`) — that pulls in unrelated merged work and pollutes the review.3637State which scope you used in the report's first line. The answer to "how many files changed?" differs by tool, and the user will ask.3839Report change size as `+added / −removed` from `git diff --shortstat` or the PR's own counts (`gh pr view --json additions,deletions`), never `wc -l` of a raw diff — see `/git-workflows`.4041## REVIEW Mode (default)4243Brief — full workflow in [references/review.md](references/review.md).4445- **Read-only.** No edits, no commits, no GitHub/Linear comments unless explicitly authorized. Output is chat text only. When posting is authorized, attribute each agent-authored comment per `/pr-guidelines` (open with an italic `*<model>:*` prefix).46- **Fan out across parallel subagents** for any non-trivial diff — a single-pass skim cannot cover a real change-set. Standard axes: bug scan, AGENTS.md/CLAUDE.md compliance, dead code & duplication, LOC & complexity. Add a **spec-conformance** axis whenever the change traces to a ticket/PRD/RFC — it runs in parallel and reframes the other findings, but the spec is an input, not ground truth (the doc/ticket is often the stale side, not the code), so divergences are reconciliation items for the author, not automatic code defects. See [references/review.md](references/review.md#synthesis-let-spec-conformance-set-disposition).47- **Model tier per GLOBAL.md:** review and validator subagents run on the strongest tier at high or extra-high effort; drop to a faster model only for pure retrieval (gathering files, grepping call sites) that an analysis agent then reasons over.48- **Validate each finding** with a second-pass subagent before reporting — a fresh context adversarially refuting *another* agent's finding, never an agent re-checking its own work. Validation is where filtering happens, which is what lets the finders stay open.49- **Cite file path + line range** on every finding. Never restate the diff.50- **Numbered list** with stable IDs (`#1`, `#2`, ...) so the user can reply "fix 2, 3, 5". Findings are grouped into **Clear fixes** (one right solution, all get applied regardless of severity) vs **Decisions needed** (a product/design choice gates the fix — options + one recommendation each); run each finding through `/what` before it reaches the report. See [references/review.md](references/review.md#phase-4-report).51- **High signal in the report, not in the finders.** Finders report everything with confidence and severity; validation filters. The [explicit false-positives list](references/review.md#explicit-false-positives) (pre-existing issues, linter-catchable, pedantic nits) is a category exclusion that binds every stage.52- **End with a handoff suggestion:** APPLY the clear fixes, answer the decision items, run `/pr-guidelines` to refresh the description, or defer to a follow-up PR.5354## APPLY Mode5556Brief — full workflow in [references/apply.md](references/apply.md).5758Two entry paths:59601. **From a prior REVIEW:** user picks items by number (*"fix 2, 3, 5"*, *"do all 4"*, *"in stages, dead code first"*). Execute only the picked items.612. **Standalone cleanup intensity:** the user names the depth.6263| Intensity | Trigger phrases | Scope |64| -------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |65| **Light** (deslop) | "deslop", "remove slop", "clean up the AI stuff" | Strip AI artifacts. No structural changes. [Workflow →](references/apply.md#light-deslop) |66| **Heavy** (refactor) | "refactor pass", "tighten up", "dead code" | Structural cleanup, dead path removal, build/test verification. [Workflow →](references/apply.md#heavy-refactor-pass) |67| **Targeted** | "fix 2, 3, 5", "do all 4", "in stages" | Execute picked items from a prior REVIEW. [Workflow →](references/apply.md#targeted-picks) |6869Phased over big-bang. The user repeatedly steers toward "in stages, dead code first, then consolidation". Default to proposing a phased plan; execute one phase, pause, then continue.7071## Shared Principles7273These apply to both modes — they shape what counts as a finding (REVIEW) and what counts as a clean diff (APPLY).7475<!-- @> Cleanup should net fewer lines; if LOC increases, justify the complexity reduction. Diff size itself is a finding — flag 10k+ line diffs and look for the codemod, idiomatic API, or generated content to exclude -->7677### Primary outcome: net LOC reduction7879After cleanup, total LOC should usually be lower than before. If cleanup increases LOC, keep it only when it clearly reduces complexity or risk, and call out that tradeoff explicitly.8081Diff size is itself a finding. A version bump that produces 20k+ lines of diff, or a feature that costs 10k+ lines for a small surface, is suspect — flag it and look for the maintainer-provided codemod, an idiomatic API the project missed, or generated content that should be excluded from review.8283When categorizing a large diff, split into: **generated / boilerplate / moved / new logic**. The user evaluates PR quality partly by the new-logic fraction.8485<!-- @> Remove defensive checks, type casts, redundant annotations, single-use variables abnormal for codepath context. Don't auto-remove useCallback/useMemo/memo — only with profiling evidence or explicit user direction -->8687### What to remove8889- Extra defensive checks or try/catch blocks abnormal for that codepath (especially if called by trusted/validated callers).90- Casts to `any` or `as` to get around type issues (see [Type safety](#type-safety)).91- Unnecessary or redundant type annotations.92- Variable declarations only used once right after declaration — inline them.93- Style inconsistent with the surrounding file.9495### What NOT to remove9697- `useCallback`, `useMemo`, `memo`, or other performance primitives — only change with profiling evidence or explicit user direction.98- **Intentional scaffolding.** Re-export barrels, design-system primitives (`*.primitives.tsx`), and framework-required exports may look "unused" to knip but exist for a reason. Ask before pruning. (No tension with [Barrel files](#barrel-files): never *create* new barrels; don't *delete* existing ones without asking.)99- **Repetition that serves an argument.** Callbacks, deliberate restatement, or layered comments that reinforce intent are not duplication. Only flag *fully duplicated / redundant* sections.100- **Specific semantic intent.** A `<dialog>` wrapper exists for top-layer semantics; a button-styled-as-link exists for download behavior. Read the intent before flattening.101102<!-- @> No shipped stubs, mocks, hardcoded fixtures, or "temporary" literals. Replace stand-ins with real sources before handoff. Mid-stream stubs stay greppable through a stub/mock-prefixed identifier, never a comment -->103104### No shipped stubs, mocks, or temporary values105106Stubs, mocks, hardcoded fixtures, "temporary" literals, debug values, and inline test data **do not ship**. If you wired a UI, query, or branch to a stand-in during development, replace it with the real source before declaring the work done. The risk isn't sloppiness — it's that a forgotten stub silently shapes behavior, and when the feature misbehaves weeks later, the cause is invisible and the debugging trail leads in the wrong direction.107108If a stub must exist mid-stream (active debugging, intentional prototyping), make it greppable through its identifier (a `stub`/`mock`-prefixed name), never through a comment. Before handoff, search the touched feature for `STUB`, `MOCK`, fixture arrays, and hardcoded values that mirror enum or option labels, and remove them. Unmarked stubs are a first-class REVIEW finding.109110<!-- @> Hard rule: a file may not cross from below 1000 lines to above. Only waiver is extremely uniform content (data table, generated code, flat enum) where any split would hurt readability. Decompose first by default -->111112### The 1000-line ceiling113114A file may not cross from below 1000 lines to above. This applies to REVIEW (flag the violation) and APPLY (decompose before letting a change push the file over).115116The only valid waiver: the file is extremely uniform — a long data table, generated code, a flat enum, a list of route registrations — where any split would hurt readability. If the file has meaningful control flow or distinct sections, decompose first. Don't waive because the new code "logically belongs here" or because splitting "is a lot of work."117118When APPLY is about to push a file across the line, stop and propose the decomposition (subcomponents, helpers, separate modules) before continuing.119120<!-- @> Cleanup uncovers more cleanup — follow the thread. After removing a feature, search for sibling dead code (utilities, tokens, fixtures, resolver fields) that's now unused -->121122### Cleanup uncovers more cleanup123124After removing a feature, branch, or component, search the codebase for sibling code that's now dead — utilities only it called, design tokens only it used, GraphQL fields only it queried, fixtures only it referenced, schema columns only it wrote. A one-shot deletion that only removes the named thing under-delivers. The user expects the cleanup to **follow the thread**.125126Scope guard: stay within the change-set's natural boundary. "Sibling code that became dead because of this change" is in scope. "Sibling code that was always dead but you noticed in passing" is a follow-up, not this PR.127128<!-- @> List ordering: every list has an intrinsic best order — alphabetical, dependency, frequency, numeric — match the list's purpose. Place new entries in position; never just append. Encode deviations from tool defaults, not the defaults themselves -->129130### List ordering131132Every list has an intrinsic best order. Match the list's purpose rather than defaulting to alphabetical:133134- **Alphabetical** for catalogs read like a glossary (dependency blocks, env keys, allowlists, enum members consumed by humans).135- **Dependency/logical** when earlier entries set up later ones (import groupings, CSS declaration order, pipeline stages).136- **Frequency or salience** for lookup tables where readers scan for common cases first.137- **Numeric/temporal** for sequence-bearing data (versioned migrations, dated entries).138139When adding an entry, place it in the correct position rather than appending. The only reason to break the intrinsic order is a hard syntactic or logical constraint.140141For config files that combine tool defaults with project overrides, encode only the **deviations**: a short config that diverges meaningfully is more readable than a long one that mostly restates the defaults. Before adding an option, check whether it matches the default; if so, omit it.142143<!-- @> Comments are banned entirely: flag every comment in touched code for deletion; refactor until self-documenting (extract, rename, restructure); knowledge code can't express goes to the PR description, never the source. Sole exception: TODO-style markers the user specifically requested -->144145### Comment policy146147Comments are banned. Every comment in touched code is a finding: delete it, and when it carried information, move that information into the code (a named function or constant, a better identifier, a restructure) or out of the source entirely (PR description, commit message, docs). A construct that seems to need a comment is not clear enough and gets rewritten. Machine-read directives (shebangs, codegen pragmas) are code, not comments; lint suppressions remain a last resort governed by the lint-directives rule. The single exception: `TODO`-style markers the user specifically requested.148149### Don't silence the tool; don't roll your own codemod150151When a tool reports something you've already accepted as correct, run it and let downstream state settle — don't reach for `ignore` / `exclude` / `skip` config to silence it. When the upstream maintainers publish an official migration path (codemod, preset, framework-provided helper), prefer it over a handwritten substitute, even when the resulting diff is larger. A 20k-line maintainer codemod is more trustworthy than a 2k-line homegrown one. Parallel subagents > scripted refactors when no official codemod exists.152153<!-- @> A lint rule targets a behavior not a token: a sibling construct emitting the same flagged output still dodges it. Fix at root, or write a real standard and disable the rule deliberately. Inline-suppress only as last resort when no compliant alternative exists (stable id over array-index key); offer the simpler path first -->154155A lint rule targets a behavior, not a token. Switching to a sibling construct that produces the same flagged output — a wrapper or alternate API that emits exactly what a `no-X` rule forbids — dodges the rule without honoring it, and is still a workaround. Two honest paths: fix at the root so the rule passes on its merits, or, when the rule genuinely doesn't fit the case, write a real standard for the codebase and disable the rule deliberately. An off-the-cuff sibling swap is neither.156157Inline suppression is a last resort, valid only when a rule blocks the sole viable approach and no compliant alternative exists. Exhaust the alternative first and offer the simpler-code path before suppressing: a rule against array-index list keys is satisfied by a stable id from the data, not a disable comment. Type-safety escape hatches (`any`, `as`, `!`, `ts-ignore`) are never the last resort — see [Type safety](#type-safety).158159## Types, imports & tooling (`.ts`, `.tsx`)160161Applies when writing or reviewing TypeScript: typecheck failures, strictness, generics, barrels, module layout — not only during cleanup passes.162163<!-- @> A cast signals a too-wide upstream type: tighten the source so the cast and its guard both vanish; invert call sites instead of widen-then-cast; parse boundaries with zod, narrow with instanceof. Don't assert type-system behavior without an empirical repro -->164165### Type safety166167**Never compromise type safety**: No `any`, no type assertions (`as Type`), no non-null assertions (`!`), no `ts-ignore`/`eslint-disable`/lint-disable. Avoid `unknown` unless narrowed immediately. This holds equally in backend resolvers and services — a cast in a query layer is no more acceptable than one in a component.168169A cast is a symptom: the type is too wide somewhere upstream. Fix the source, not the call site.170171- **Tighten the upstream type so the cast and its guard both disappear.** When a parameter is declared wider than its only callers supply — `string` where the caller already hands over the source's union or enum — narrow the parameter to the real type. The cast and the runtime guard that defended it both fall away.172- **Invert, don't widen-then-cast.** When a value doesn't fit a consumer, the fix is rarely to broaden the consumer's type and cast at the boundary. Broaden in the wrong direction and every caller inherits the looser contract. Instead, make the real type flow through from where it originates.173- **Parse at the boundary, narrow within it.** Validate untrusted input with a schema (zod or equivalent) at the edge so a typed value flows inward; narrow runtime variants with `instanceof` or a discriminant. Both replace the assertion with a check the compiler trusts.174- **Don't assert how the type system behaves.** A claim that "TS widens this" or "the inference fails here" is a verification step — confirm it with a minimal repro before designing around it, never from intuition.175176<!-- @> Prop intersections: specific before generic. Inline single-use variables -->177178### Component & prop style179180- Order prop intersections: specific props before generic (`{ specific } & RootProps`).181- Favor readability over brevity; avoid mirror variables.182- Comments only for non-obvious logic, never narration.183- Follow existing conventions: use `rg`, `fd`, git history before adding patterns.184- Don't declare variables only used once immediately after; inline them.185186### Imports & dependencies187188<!-- @> Import order: React → runtime → external → internal → aliased → relative → local. type keyword for type imports -->189190- Import order: React → runtime → external → internal → aliased → relative → local.191- Use `type` keyword for type imports: `import type { Foo } from './types'`.192- Dependencies in `package.json`: alphabetical.193194<!-- @> No barrel files (index.ts re-exports). Import directly from source modules -->195196#### Barrel files197198- No barrel files — don't create `index.ts` re-export files. Import directly from source modules.199200### Checks201202Run type/lint checks yourself when relevant; don't ask the user to run them.203204## HTML, CSS & templates205206Markup and styles for `.html`, `.css`, and templated/JSX UI. Deep dive: [Web Interface Guidelines](references/web-interface-guidelines.md).207208209### Semantic HTML first210211Prefer built-in semantics over generic containers: structure (`article`, `header`, `main`, `nav`, `section`, `ul`/`li`), interactive (`button`, `form`, `label`), content (`table`, `time`). Avoid `div`/`span` unless necessary. Prefer real text + structure over ARIA-only shortcuts.212213214### Layout215216- Flexbox/grid with `gap` for spacing between children.217- `margin` is a code smell — prefer container `padding` or `gap`; margins break encapsulation.218- Logical properties: `block`/`inline`, `start`/`end` over physical `left`/`right`/`top`/`bottom` where appropriate.219- Transform sub-properties (`translate`, `rotate`, `scale`) over a single long `transform` when the stack allows it.220221222### Declaration order223224Order by concern, outside-in (not alphabetically): position & display → flex/grid container & child → sizing & spacing → overflow → typography → visual (color, background, border, shadow) → transform & animation → interaction (`cursor`, `pointer-events`, `user-select`). Applies to CSS-in-JS objects too.225226227### State styling228229Drive selected/active/expanded state with a data attribute and an attribute selector (`[data-state="active"] {…}`, `[aria-pressed="true"]`), not a conditional className or `cx()` merge in the component. The DOM stays declarative, the styling lives with the rest of the component's CSS, and the state is inspectable in devtools without reading render logic.230231232### Layout stability233234Avoid layout shift (CLS) by holding geometry constant across state and breakpoint:235236- **Container queries, not viewport queries, when the available width is set by a sibling.** If a region's space is driven by a collapsible panel, resizable sidebar, or split pane rather than the viewport, set `container-type: inline-size` on the layout that owns the width and query it (`@container`). Viewport queries respond to the wrong axis and break when the surrounding layout changes. Audit existing `@media` width queries for this case when touching responsive layout.237- **Keep controls present and in the same order across every variant.** A control that appears in one state should occupy the same slot in the others rather than appearing, disappearing, or reordering. Consistent placement prevents both the UX surprise of a moving target and the reflow when an element pops into the flow.238- **A border that must not change box height becomes an `inset box-shadow`.** A `1px` border adds to height; toggling it between states shifts everything below by a pixel. Use `box-shadow: inset 0 0 0 1px …` instead, or keep an equal transparent border (`border: 1px solid transparent`) in every state so the box height never changes.239240241### Colors242243Design tokens / CSS custom properties first; otherwise `oklch` or hex — not `rgb` for new work.244245246### CSS over JavaScript247248Prefer CSS for visuals and motion when it matches JS behavior — less bundle work, easier `prefers-reduced-motion`, better separation.249250### Accessibility251252- Prefer visually hidden real text (`srOnly`) over duplicating meaning in `aria-label` alone.253- Custom click targets need keyboard support (Enter/Space). Every interactive element needs a visible `:focus-visible` style — never `outline-none` without a replacement.254- Viewport units: `dvw`/`dvh` over `vw`/`vh` where mobile chrome matters.255256### Images257258Explicit `width` and `height` (or constrained aspect) to limit CLS. `loading="lazy"` below the fold; prioritize above-the-fold / LCP images.259260### Markup & CSS tips261262- `mask-image` for gradient fades works across arbitrary backgrounds.263- Fix SVG `viewBox` at the asset, not at every call site.264265## Workflow Shape (when in doubt)266267Both modes share the same skeleton: **explore → propose → approve → apply.** The difference is what each phase produces.268269| Phase | REVIEW | APPLY |270| -------- | --------------------------------------------------------- | -------------------------------------------------------------- |271| Explore | Establish scope, fan out across axes, gather findings. | Read the picked items (or the change-set). |272| Propose | Numbered findings, file:line cited, validated. | Numbered plan of edits, largest-to-smallest, with scope tag. |273| Approve | User picks items by number or replies with refinements. | User approves all, picks by number, or denies. |274| Apply | Hand off to APPLY mode (or stop, or refresh PR prose). | Make approved changes. Run build/tests. Don't auto-commit. |275276End every session with a short summary: what changed, what's deferred, and the next handoff (e.g. *"run `/pr-guidelines` to refresh the description"* or *"the dead-code thread continues into the unused GraphQL fields"*).