@deslop/coding-standards 0.1.1083 → 0.1.1086

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,29 @@
1
1
  ---
2
2
  name: engineering
3
- description: 'Use before writing or changing code, tests, scripts or configuration, before choosing how to build a feature, and before proving or testing behavior.'
3
+ description: "Use before writing or changing code, tests, scripts or configuration, before choosing how to build a feature, before a repository's fix, check, lint or test command, and before proving or testing behavior."
4
4
  ---
5
5
 
6
6
  # Engineering
7
7
 
8
8
  Every file, flag, option, branch, abstraction and line answers one question: what breaks for the user without it? If nothing, remove it.
9
9
 
10
- Agents write and maintain the code; people review it from the PR body, the proof and a scan of the diff. Write for that reader: idiomatic, effectful, testable code that reads in place, explicit and self-contained like Tailwind classes, so editing one part cannot break another. Fewer lines is never the goal: an abstraction stays only when it lowers what a reader must hold, never because it saves lines.
10
+ Agents write and maintain the code; people review it from the PR body, the proof and a scan of the diff. Write for that reader: idiomatic, effectful, testable code that reads in place, explicit and self-contained like Tailwind classes, so editing one part cannot break another. An abstraction stays only when it lowers what a reader must hold, never because it saves lines.
11
11
 
12
- Oxlint and Oxfmt check every change and stay authoritative; this skill holds what they cannot check. Apply it with the repository's `CODING_STANDARDS.md`; a cleanup request applies every rule to every line of the owned files. Scratch code follows no style rule.
12
+ Oxlint and Oxfmt check every change and stay authoritative; this skill holds what they cannot check. Apply it with the repository's standards: its `AGENTS.md` and the files it links, such as a `CODING_STANDARDS.md`; a cleanup request applies every rule to every line of the files the task touches. Scratch code follows no style rule.
13
13
 
14
14
  ## Loop
15
15
 
16
- - **Normalize.** Run the repository's fix command at the root before you analyze or check; a read-only reviewer never runs it.
17
- - **First pass.** Write the forms in [Lint rules](#lint-rules) from the start. Fix a diagnostic at its source with the form its message names; never a cast, a weakened rule or a rewrite that trades one diagnostic for another.
18
- - **Type early.** Check and test the package you change after each small batch of edits; the repository's full gate runs at the root before every push. Read an unfamiliar API's types in the installed version's source first. Fix the first error in each file first: one broken import turns into hundreds of implicit `any` errors downstream.
16
+ - **Verify** at the root once the whole change is in, and again after each round of fixes: the repository's fix command (lint fixes, then format), then its check, then its tests, chained in one command so a failure stops the rest; fix what fails and rerun the chain until it passes. Never a package-level or per-edit check: one root run costs fewer steps than many partial ones, and the cache replays unchanged work. Fix the first error in each file first: one broken import turns into hundreds of implicit `any` errors downstream. A read-only reviewer never runs fix.
17
+ - **First pass.** Write the forms in [Lint rules](#lint-rules) from the start. Fix a diagnostic at its source with the form its message or row names; never a cast, a weakened rule or a rewrite that trades one diagnostic for another.
18
+ - **Fix the code, not the rule.** A diagnostic whose named form does not fit points at the model upstream: change the model, not the line that trips the rule. A diagnostic on code the rules allow is a rule bug: fix the rule with a rejected and an accepted case.
19
+
20
+ ```ts
21
+ // good: absence is an optional value, cleared by "All"
22
+ const [kind, setKind] = useState<Kind>()
23
+ // bad: a sentinel that only exists because `undefined` is banned as a type
24
+ const [kind, setKind] = useState<Kind | ''>('')
25
+ ```
26
+
19
27
  - **Disables** need an unavoidable conflict with an outside contract and a `--` reason naming it. Never disable ordering or weaken file configuration.
20
28
 
21
29
  ## Mechanism
@@ -24,7 +32,7 @@ Start from a real caller and the outcome, not a pattern. Trace one successful op
24
32
 
25
33
  - **Direct.** The most direct code that meets today's request: extend the canonical example per [Patterns](#patterns), else a function or an existing service method. A new service only for a distinct capability or lifetime; reimplementing a library's job is a new mechanism. Keep the direct version unless a probe proves a concrete contract rules it out.
26
34
  - **Callers.** Every function, parameter, option, export, Layer, check and config value has a caller that varies it. Handle reachable inputs only: no futureproofing or impossible cases. Explicit variants for genuinely different behaviors; no boolean combinations, registries, generic configuration or factories for hypothetical callers.
27
- - **Reuse.** Search existing code, installed modules and Effect's modules by the domain concept, not only the request's words, before you write traversal, state, configuration or platform code; replace dependencies Effect covers. Identical logic twice (a computation, a query, an Effect pipeline) is one function in its owner's `lib/utils`. Similar code stays separate: three similar lines beat an abstraction, and a shared function never branches on its caller. Values, class lists and small expressions stay inline at each use, never in shared constants; reuse lives at the component, service or function level. Compose with Effect's own combinators, pipelines, Layers and Schema instead of writing a wrapper.
35
+ - **Reuse.** Search existing code, installed modules and Effect's modules by the domain concept, not only the request's words, before you write traversal, state, configuration or platform code; replace dependencies Effect covers. Before a hand-written type, guard, table, wrapper or dependency, list the owning module's exports in its locked source checkout per explore: `Schema.Literals([...]).pick([...])` replaces a second literal list, `Schema.TaggedUnion` a hand-written matcher. Identical logic twice (a computation, a query, an Effect pipeline) is one function in its owner's `lib/utils`. Similar code stays separate: three similar lines beat an abstraction, and a shared function never branches on its caller. Class lists and small expressions stay inline at each use, never in shared constants. Compose with Effect's own combinators, pipelines, Layers and Schema instead of writing a wrapper.
28
36
  - **One owner.** Derive values from their owner; never mirror server state, request state, caches or pending results in another store. A persisted value has one write boundary, a domain enum one schema, a cache key the full identity it caches. No duplicate, subset or superset fields, methods, options or exports; remove states no caller produces.
29
37
  - **Lifetime.** Finite work stays in its request scope; a stream serves progress without a job system. Background ownership only when work must outlive its caller.
30
38
  - **Failure.** Failures flow through the error channel with their cause; never turn one into empty success. No catch, retry, fallback, idempotency or defensive check without a contract that needs it.
@@ -37,7 +45,7 @@ Start from a real caller and the outcome, not a pattern. Trace one successful op
37
45
  - **Clarity.** Pure functions over immutable data, effects only inside Effect. Code reads top to bottom; rename or split instead of narrating; a comment only for an outside constraint the code cannot express.
38
46
  - **Performance** only when the request needs it: locate the cost with a profiler or trace, measure one real action before and after with run count and variation, and treat a gap within the variation as no change.
39
47
  - **Domain.** Implement the domain's definition, such as a cycle for recursion, never a syntactic proxy.
40
- - **Dependencies.** A new one is `latest`; pin only what a consumer requires. Read its locked source before you wrap it; extend through its public API, never a patch.
48
+ - **Dependencies.** A new one is `latest`; pin only what a consumer requires. Extend it through its public API, never a patch.
41
49
  - **Dead code.** In the same change, delete every superseded file, export, type, branch, config, doc, test, dependency and empty directory.
42
50
 
43
51
  ```ts
@@ -45,19 +53,17 @@ Start from a real caller and the outcome, not a pattern. Trace one successful op
45
53
  const cycles = Graph.stronglyConnectedComponents(graph)
46
54
  upload({scope: input.scope, type: input.type}) // input also holds the bearer token
47
55
  // bad
48
- Schema.decodeEffect(LedgerDraft)(draft) // draft is already a LedgerDraft
56
+ Schema.decodeEffect(LedgerDraft)(draft) // draft is already a decoded LedgerDraft
49
57
  function save(kind: 'note' | 'tag', value: Note | Tag) {} // one helper branching per caller: saveNote and saveTag
50
58
  import {glob} from 'glob' // Effect's FileSystem has glob
51
- const digits = BigDecimal.scale(BigDecimal.abs(amount), 2).value.toString().padStart(3, '0') // BigDecimal.format
59
+ const unique = Array.fromIterable(HashSet.fromIterable(items)) // Array.dedupe(items)
52
60
  ```
53
61
 
54
62
  ## Patterns
55
63
 
56
- The repository stays consistent without a reminder; a standard changes everywhere at once.
57
-
58
- - **Canonical example.** Before you write a recurring shape (page layout, list, form, empty or loading state, dialog, service, route, error mapping, file layout, name), find its canonical example: the Patterns row in `CODING_STANDARDS.md`, else the variant most consumers use, the newest on a tie. Copy its structure, components, names, copy and states.
64
+ - **Canonical example.** Before you write a recurring shape (page layout, list, form, empty or loading state, dialog, service, route, error mapping, file layout, name), find its canonical example: the Patterns row in the repository's standards, else the variant most consumers use, the newest on a tie. Copy its structure, components, names, copy and states.
59
65
  - **Register.** A shape two consumers share without a row gets one: pattern, owner and example, a search listing its consumers, and what enforces it.
60
- - **Taste.** The user's taste decides layout, file organization, boundaries, interfaces and names. Follow the Taste rows of `CODING_STANDARDS.md`; when the user picks, rejects or corrects an uncovered choice, add a row with the user's words in the same change.
66
+ - **Taste.** The user's taste decides layout, file organization, boundaries, interfaces and names. Follow the Taste rows of the repository's standards; when the user picks, rejects or corrects an uncovered choice, add a row with the user's words in the same change.
61
67
  - **Pattern change.** The user's change to a pattern, style or name changes the standard: update its owner (component, template, generator, lint rule, row), migrate every consumer its search lists, delete the old variant and search again. Another repository or a published contract needs the user's approval first.
62
68
  - **Enforce.** The same correction twice becomes impossible at the highest level that works: an owner component or type, then a lint rule naming the replacement.
63
69
 
@@ -65,7 +71,7 @@ The repository stays consistent without a reminder; a standard changes everywher
65
71
 
66
72
  **Functions.** `Effect.fn('Service.method')` for a traced function with arguments, `Effect.fnUntraced` for an internal helper, `Effect.gen` for statements without arguments, a single effect directly. `Effect.fn.Return` only for a generator that calls itself.
67
73
 
68
- **Errors.** One `Schema.TaggedError` per service. Map in `Effect.fn`'s pipeline argument, redact secrets, map SDK errors at the adapter.
74
+ **Errors.** One `Schema.TaggedError` per service. Map in `Effect.fn`'s pipeline argument, redact secrets, map SDK errors at the adapter so no library failure leaks into a service signature.
69
75
 
70
76
  ```ts
71
77
  // good
@@ -75,13 +81,8 @@ const prompt = Effect.fn('Ai.prompt')(
75
81
  },
76
82
  Effect.mapError(cause => AiError.make({cause, message: 'Cannot prompt the agent'}))
77
83
  )
78
- const pages = Effect.tryPromise({
79
- catch: cause => NotionError.make({cause, message: 'Cannot list pages'}),
80
- try: () => client.pages.list()
81
- })
82
- // bad
83
- Effect.mapError(failure => new LedgerError({reason: failure.message})) // cause dropped
84
- load: (path: string) => Effect.Effect<void, PlatformError | Schema.SchemaError> // library failures leak
84
+ // bad: library failures leak
85
+ load: (path: string) => Effect.Effect<void, PlatformError | Schema.SchemaError>
85
86
  ```
86
87
 
87
88
  **Services.** Inline a sole implementation in its Layer; name layers only for several backends. `Context.Reference` for a default; build the shape with `Service.of` or `satisfies`.
@@ -99,13 +100,14 @@ export class Ledger extends Context.Service<Ledger, Ledger.Shape>()('@app/ledger
99
100
  export const Verbose = Context.Reference<boolean>('@app/ai/Verbose', {defaultValue: () => false})
100
101
  ```
101
102
 
102
- - **Primitives.** RPCs and services already trace. One scope owns each lifetime, with acquisition and release in one `Effect.acquireRelease`.
103
- - **Results.** Sort with `Order`; a fallible pure value returns a `Result`.
103
+ - **Tracing.** RPC handlers and `Effect.fn` already trace: add no span.
104
+ - **Lifetimes.** One scope owns each lifetime, with acquisition and release in one `Effect.acquireRelease`.
105
+ - **Results.** A fallible pure value returns a `Result`.
104
106
 
105
107
  ## Types and Schema
106
108
 
107
- - **Derive** types from their source, such as `ReturnType<typeof fromSdk>`; use schema types and indexed members in signatures. Annotate a return type only on a guard, assertion, recursive function or `Schema.suspend` thunk.
108
- - **Schema pairs.** `export type X = typeof X.Type` directly before `export const X = Schema.Struct(...)`; reuse fields by spreading `.fields`. `Schema.optionalKey` for an absent field; `Schema.optional` only for an explicit `undefined`.
109
+ - **Derive** types from their source: the library's exported type first, else `ReturnType<typeof fromSdk>` in the signature that needs it, never a private alias; use schema types and indexed members in signatures.
110
+ - **Optionality.** `Schema.optionalKey` for an absent field; `Schema.optional` only for an explicit `undefined`. Reuse fields by spreading `.fields`.
109
111
  - **Construction.** `X.make` validates and throws: trusted values only. Input that can fail uses `X.makeEffect`, mapped to the domain error.
110
112
  - **Error predicates** are static members of their tagged error; check a struct inline with `Schema.is`.
111
113
  - **Opaque** SDK input stays `unknown` until decoded at its boundary.
@@ -113,6 +115,7 @@ export const Verbose = Context.Reference<boolean>('@app/ai/Verbose', {defaultVal
113
115
  ```ts
114
116
  export type LedgerDraft = typeof LedgerDraft.Type
115
117
  export const LedgerDraft = Schema.Struct({...LedgerEntry.fields, id: Schema.optionalKey(Schema.NonEmptyString)})
118
+ // recursive: Schema.suspend needs the type written by hand, readonly as Schema.Array decodes it
116
119
  export type CallStep = {readonly do: readonly Step[]; readonly kind: 'call'}
117
120
  export const CallStep = Schema.Struct({
118
121
  do: Schema.Array(Schema.suspend((): Schema.Codec<Step> => Step)),
@@ -123,16 +126,16 @@ export const CallStep = Schema.Struct({
123
126
  ## Shape
124
127
 
125
128
  - **Linked state** moves in one pure updater, never a setter that calls another.
126
- - **Files.** Kebab-case role names, one substantive file over near-empty fragments, `<name>.test.ts` beside its source.
127
- - **Package surface.** Exports for imported subpaths, import aliases for internal paths. Extend the shared lint preset with generated ignores only.
129
+ - **Files.** One substantive file over near-empty fragments, named by role (`home`, `utils`), never by kind or app; `<name>.test.ts` beside its source.
130
+ - **Package surface.** Extend the shared lint preset with generated ignores only.
128
131
  - **Generated code** follows these rules as closely as it can; lint ignores it.
129
132
 
130
133
  ## React
131
134
 
132
135
  - **Atoms.** Data, shared state and every Effect a component needs live in atoms; components read them with hooks and never run an Effect. Import `Atom` and `AsyncResult` from `effect/reactivity`, hooks from `@effect/atom-react`. Supply services through `Atom.runtime(layer)` and build reads and writes with its `atom` and `fn`; tie writes to reads with reactivity keys. Never wrap a write that already has an owner, such as an existing mutation, in a second state holder.
133
- - **Outcomes.** Read with `useAtomSuspense`; the screen's owner wraps it in one `Suspense` and one error boundary with retry. Components never branch on loading or failure. A write's pending and failure state come from its atom; a reversible write updates at once with `Atom.optimistic`.
136
+ - **Outcomes.** Read with `useAtomSuspense`; the screen's owner wraps it in one `Suspense` and one error boundary with retry. Components never branch on loading or failure. A write's pending and failure state come from its atom; a reversible write uses `Atom.optimistic`.
134
137
  - **Ownership.** A component owning atoms, writes or a lifetime gets its own file.
135
- - **Pure render.** Derive display values in render; event handlers own actions; `useEffect` only syncs external systems. Fix a rerender in the atom graph.
138
+ - **Pure render.** Derive display values in render; event handlers own actions. Fix a rerender in the atom graph.
136
139
 
137
140
  ```tsx
138
141
  const appsRuntime = Atom.runtime(DualApps.layer)
@@ -145,12 +148,12 @@ function AppFrame(props: {appId: AppId}) {
145
148
 
146
149
  ## Proof
147
150
 
148
- - **Probe first.** Answer a question or check an assumption with inline code against the public interface or real data: `vp node -e '<script>'`, `vp env exec bun -e '<script>'`. Never a test file, worktree, seam or `/tmp` script for one question.
149
- - **Done** means each changed behavior and each of its consumers ran through its real interface on the final code: a screen in the browser, an API through a request, a CLI by running it; for a stack, on every layer's head. Never claim what you did not run. A green suite does not replace that run; zero collected tests or exit 0 alone prove nothing.
151
+ - **Probe first.** Answer a question or check an assumption with inline code against the public interface or real data, run by the repository's script runner, such as `vp node -e '<script>'` or `bun -e '<script>'`. Never a test file, seam, worktree or clone for one question.
152
+ - **Done** means each changed behavior and each of its consumers ran through its real interface on the final code: a screen in the browser, an API through a request, a CLI by running it; for a stack, on every layer's head. A green suite does not replace that run; zero collected tests or exit 0 alone prove nothing.
150
153
  - **Bugs.** Reproduce before you fix, then shrink the reproduction until every remaining input, setting and step is needed. One hypothesis per probe. After two failed fixes, write down the premise they share and test it before a third. Rerun the original scenario after the fix.
151
154
  - **Tests** only for complex logic you own, such as parsing, state transitions, concurrency, scheduling, money or data conversion: a black box through its public interface, expected values from the contract, never the implementation. One representative input per behavior; reachable distinctions only (empty, duplicate, concurrent, dependency failure).
152
155
  - **No tests** for UI (the browser proves a screen); external libraries, APIs or SDKs; what types or schemas guarantee; inputs the architecture makes impossible; simple code (a filter, a mapping, wiring, a constant); an assumption (a probe answers it); a fixed bug (no regression tests); anything that changes when the implementation changes. Delete touched tests that break these rules.
153
- - **Effect tests.** `assert` from `@effect/vitest`. `it.effect` runs virtual services, `it.live` real ones, `it.layer` caches a block's context. `TestClock` and `TestConsole` come from `effect/testing`: fork, await a `Deferred` at the dependency boundary, adjust time, join. Concurrency: an entered and release pair at the dependency. Expected failure: `Effect.flip`; both outcomes: `Effect.exit`; assert domain identity and preserved state.
156
+ - **Effect tests.** `it.effect` runs virtual services, `it.live` real ones, `it.layer` caches a block's context. `TestClock` and `TestConsole` come from `effect/testing`: fork, await a `Deferred` at the dependency boundary, adjust time, join. Concurrency: block the dependency on a `Deferred`, assert the entered state, then release it. Expected failure: `Effect.flip`; both outcomes: `Effect.exit`; assert domain identity and preserved state.
154
157
  - **Boundaries.** Replace dependencies through Layers or public parameters, never module mocks. A claim about an SDK, process, filesystem, HTTP, browser or package needs the real boundary. `FileSystem.layerNoop` is not storage; `RpcTest` bypasses serialization. A public tool is proved packed, not from source.
155
158
  - **Evidence.** Keep the exact command, result and revision of each claim; rerun only proof whose inputs changed.
156
159
 
@@ -180,83 +183,85 @@ Once it works, reread the diff as a reviewer who knows only the request, and act
180
183
 
181
184
  ## Lint rules
182
185
 
183
- Lint rejects the right column without an autofix. Write the left column from the start.
184
-
185
- | Write | Never | Rule |
186
- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
187
- | An early `return`, `Boolean.match(x, {onFalse, onTrue})`, `Option.match` or a lookup record | `a ? b : c`, in JSX too | `no-ternary` |
188
- | `options?.root ?? '.'` | `root !== undefined ? root : '.'` | `no-nullish-ternary`, `prefer-nullish-coalescing` |
189
- | `pipe(Match.value(event), Match.tag('Text', text), Match.exhaustive)` | `switch` | `no-switch-statement` |
190
- | `selected === true` on a value that can be `undefined`; `ref.current === null`, `editor === undefined` | `if (selected)` on an optional, `if (!ref.current)`, `if (editor)` | `strict-boolean-expressions` |
191
- | `Array.filter` before the loop, an early `return` in a callback | `continue` | `no-continue` |
192
- | At most 3 nested blocks, 6 nested callbacks and 3 nested `pipe` calls | Deeper nesting | `max-depth`, `max-nested-callbacks`, `no-deep-pipe` |
193
- | `pipe(value, f, g)` | `value.pipe(f, g)` | `no-restricted-properties` |
194
- | `const total = Array.reduce(values, 0, Number.sum)` | `let` with a loop | `no-let` |
195
- | `Array.join(parts, ', ')`, `Array.map(items, f)`, `String.split(text, ',')`, `Record.keys(record)` | `parts.join(', ')`, `items.map(f)`, `text.split(',')`, `Object.keys(record)` | `no-native-method-call`, `no-restricted-globals`; the `Path` service's `path.join` stays |
196
- | `Predicate.isString(v)`, `Predicate.hasProperty(node, 'tag')`, `Predicate.isNotNullish(v)` | `typeof`, `'tag' in node`, `v !== null && v !== undefined` | `no-typeof`, `no-in-operator`, `no-double-nullish-check` |
197
- | `Predicate.isNotNullish`, `Predicate.isObject`, `Predicate.isDate` passed directly | A hand-written guard `(v): v is T => v !== null` | `no-hand-written-guard` |
198
- | `Array.isReadonlyArrayEmpty(items)`, `String.isNonEmpty(text)` | `items.length === 0`, `text.length > 0` | `no-native-emptiness-check` |
199
- | `Array.ensure(value)` | `Array.isArray(value) ? value : [value]` | `no-array-wrap-ternary` |
200
- | `Option.map(option, f)`, `Option.match`, `Option.getOrElse` | `option.value` after `Option.isSome` | `no-option-value-access` |
201
- | `undefined`, `Option.getOrUndefined` | `null`, `Option.getOrNull`, except for an outside contract | `unicorn/no-null`, `no-restricted-properties` |
202
- | `Number.parse(text)`, `Number.max`, `HashMap`, `HashSet`, `DateTime` | `parseInt`, `Math.max`, `Map`, `Set`, `Date` | `no-restricted-globals`, `no-restricted-properties`, `no-restricted-types` |
203
- | `Clock`, `Config`, `Console`, `Random`, `Effect.sleep`, `HttpClient` | `Date.now()`, `process.env`, `console`, `setTimeout`, `fetch` | `effecttsgo/global-*`, `effecttsgo/process-env` |
204
- | `FileSystem`, `Path`, `ChildProcess`, `Crypto`'s `randomUUIDv4` | `node:fs`, `node:path`, `node:child_process`, `crypto.randomUUID()` | `effecttsgo/node-builtin-import`, `effecttsgo/crypto-random-uuid` |
205
- | `#lib/utils` through the package's `imports` map, named imports, named exports | `../lib/utils`, `import * as utils from '#lib/utils'`, `export default` outside config files, a `namespace` | `import/no-relative-parent-imports`, `import/no-namespace`, `import/no-default-export`, `typescript/no-namespace` |
206
- | `import {Folder} from 'lucide-react'` from the defining module | `export * from 'lucide-react'`, a file that only re-exports | `no-barrel-module` |
207
- | Kebab-case file names | `LedgerView.tsx`, `ledger_view.ts` | `unicorn/filename-case` |
208
- | A name an outer scope does not use | An inner `entry` shadowing an outer `entry` | `no-shadow` |
209
- | A new value | Assigning to a parameter or its properties | `no-param-reassign` |
210
- | `/pattern/u` | A regular expression without the `u` or `v` flag | `require-unicode-regexp` |
211
- | Strings, numbers and booleans in a template literal | An object, `Option` or `unknown` value interpolated | `typescript/restrict-template-expressions`, `typescript/no-base-to-string` |
212
- | A check only where the type allows both outcomes | `?.`, `??` or a condition on a value whose type rules one outcome out | `typescript/no-unnecessary-condition` |
213
- | A type parameter that links two positions | A type parameter used once, `string \| 'id'`, `void` in a union or parameter | `typescript/no-unnecessary-type-parameters`, `typescript/no-redundant-type-constituents`, `typescript/no-invalid-void-type` |
214
- | `items => service.load(items)` | `service.load` passed unbound | `typescript/unbound-method` |
215
- | An Effect (`Effect.fn`, `Effect.tryPromise`); in `.tsx` only, an `async` handler that awaits | An `async` function in `.ts`, `async` without `await`, `void promise`, an unhandled promise | `effecttsgo/async-function`, `typescript/require-await`, `no-void`, `typescript/no-floating-promises` |
216
- | The current API its deprecation names | A deprecated API | `typescript/no-deprecated` |
217
- | Values decoded with Schema before use | Member access, calls, assignments, arguments or returns on `any` | `typescript/no-unsafe-*` |
218
- | `function name() {}` for a named function; an arrow only in place | `const name = () => {}` | `func-style` |
219
- | `server => ({...server, warmup})`, `for (const schema of schemas)`, `pair => pair[0]` | `({host, port}) =>`, `for (const {name} of schemas)` | `no-destructured-parameter`; labeled tuples, React state and refs can destructure |
220
- | `Array.sort(entries, Order.mapInput(Order.Number, (entry: Entry) => entry.tokens))` | `entries.sort((left, right) => left.tokens - right.tokens)` | `no-native-method-call`; a third-party object's own method stays |
221
- | The source value where it is used | A one-line wrapper, alias or argument-free constant function | `no-trivial-indirection`, `no-constant-function` |
222
- | `import {Schema} from 'effect'` | `import {Schema as S} from 'effect'` | `no-renamed-import` |
223
- | Properties built with `Effect.fn` or `Effect.fnUntraced` | `fetch: url => Effect.tryPromise(...)` as a property arrow | `no-effect-property-arrow` |
224
- | Inferred return and variable types, `satisfies`, `Array.empty<T>()` | `function f(): T`, `const items: T[] = []` | `no-return-type`, `no-redundant-variable-annotation` |
225
- | `satisfies T`, `as const` | `as T`, `any`, `value!` | `consistent-type-assertions`, `no-explicit-any`, `no-non-null-assertion` |
226
- | `useRef(initial)` for lifecycle state | `const [box] = useState(() => ({current: initial}))` with no setter | `no-fake-ref-state` |
227
- | `Ref.make(0)`, `useState(false)`, `useRef<HTMLDivElement>(null)` | `Ref.make<number>(0)`, `useRef<HTMLDivElement \| null>(null)` | `no-redundant-state-type`, `no-redundant-use-ref-null-type` |
228
- | `T[]`, an optional property | `Array<T>`, `Promise<T>`, `x: T \| undefined` | `no-restricted-types` |
229
- | `Effect.tryPromise({catch: cause => X.make({cause, message}), try})`, each failure typed by its tagged error | `unknown` or `any` in the error or requirements channel, a `catch` returning the raw cause | `effecttsgo/any-unknown-in-error-context`, `effecttsgo/unknown-in-effect-catch` |
230
- | `Effect.mapError(cause => X.make({cause, message}))` | `Effect.mapError(failure => X.make({message}))`, `catch: failure => X.make({message})` | `preserve-error-cause` |
231
- | The failure in the error channel; `Effect.option` for a value that is optional by design | `Effect.orElseSucceed(() => [])`, `Effect.catch(() => Effect.void)`, `Effect.succeed('')` in a catch | `no-empty-recovery` |
232
- | `Effect.forEach(items, load, {concurrency: 'unbounded'})`; `{concurrency: 1}` when order or a resource matters | `Effect.forEach(items, load)`, `Effect.all([a, b])`, `Effect.all(effects, {discard: true})` | `explicit-effect-concurrency` |
233
- | `Effect.fn('Service.method')` for a function returning an Effect | A plain function returning `Effect.gen` | `effecttsgo/effect-fn-opportunity` |
234
- | Layers provided once at the entry point; `Layer.provide` between layers | `Effect.provide(layer)` inside services or per call | `effecttsgo/strict-effect-provide` |
235
- | `Layer.provideMerge` when one layer needs another | `Layer.mergeAll` of layers that depend on each other | `effecttsgo/layer-merge-all-with-dependencies` |
236
- | One `pipe(value, f, g, h)` | `pipe(pipe(value, f), g)`, `pipe(value)` | `effecttsgo/unnecessary-pipe-chain`, `effecttsgo/unnecessary-pipe` |
237
- | Service keys `@<package>/<path>/<Name>`; an error's tag is its class name | A service key that does not match the package, path and class name | `effecttsgo/deterministic-keys` |
238
- | `return yield* NotFound.make({...})` | `yield* Effect.fail(NotFound.make({...}))` | `effecttsgo/unnecessary-fail-yieldable-error` |
239
- | `Schema.decodeEffect(S)(typed)`, `Schema.fromJsonString(S)` for JSON text | `Schema.decodeUnknownEffect` on typed input, `JSON.parse` in an Effect | `effecttsgo/prefer-typed-schema-decoder`, `prefer-schema-over-json` |
240
- | `Schema.fromJsonString(Protocol)` with the protocol's schema | `Schema.UnknownFromJsonString`, `Schema.fromJsonString(Schema.Unknown)` | `no-unvalidated-json-decode` |
241
- | `export type X = typeof X.Type` directly before `export const X`; an inferred schema | A type elsewhere, `Schema.Schema<X>` restated, an unannotated `suspend` thunk | `schema-type-pair` |
242
- | `X.make(...)`, `Schema.is(X)(value)`, `Schema.decodeEffect` inside an Effect, `Schema.Finite` for numbers | `new X(...)` on a schema class, `instanceof X`, `Schema.decodeSync` in a generator, `Schema.Number` | `effecttsgo/new-schema-class`, `effecttsgo/instance-of-schema`, `effecttsgo/schema-sync-in-effect`, `effecttsgo/schema-number` |
243
- | The Schema operation inline at its use | `const decode = Schema.decodeEffect(S)` stored for later | `no-stored-schema-operation` |
244
- | `Schema.Struct`, `Schema.TaggedError`, `Schema.NonEmptyString` | `Schema.Class`, `Data.TaggedError`, `Error`, a rebuilt refinement | `no-restricted-properties`, `no-restricted-globals`, `no-reinvented-schema` |
245
- | The failure mapped to the service's error | `Effect.ignore`, `Effect.orDie` outside tests | `no-discarded-failure` |
246
- | `NodeRuntime.runMain(program)`; atoms in React | `Effect.runPromise`, `ManagedRuntime.make` | `no-restricted-properties` |
247
- | `import {useState} from 'react'`; `const [open, setOpen] = useState(false)` | `React.useState`, `const state = useState(false)` | `no-restricted-properties`, `no-undestructured-use-state` |
248
- | React Compiler memoization, a `ref` prop | `useCallback`, `useMemo`, `memo`, `forwardRef` | `no-restricted-imports` |
249
- | Code the compiler accepts | `'use no memo'`, an inline disable of a React rule | `no-compiler-opt-out`, `react/rule-suppression` |
250
- | `const countAtom = Atom.make(0)` at module level, `Atom.family(id => ...)` per prop | `Atom.make` inside a component or hook, `useState(() => Atom.make(0))` | `no-component-atom-allocation` |
251
- | `<Editor key={draft.id} />` to reset a draft; a value derived in render | A `useEffect` that copies a prop into state | `react-doctor/no-adjust-state-on-prop-change`, `react-doctor/no-derived-state` |
252
- | `<Label htmlFor="name">`, `aria-label` on a field without a visible label, a `<Button>` for a click | A placeholder as the only label, `<div onClick>` | `jsx-a11y/*`, `react-doctor/no-placeholder-only-field` |
253
- | `bg-card`, `border` | `bg-linear-to-b from-muted`, `bg-[radial-gradient(...)]` | `no-gradient` |
254
- | `duration-200`, `zoom-in-95`, `starting:scale-95`, `motion-safe:animate-spin` | `duration-500`, `zoom-in`, `starting:scale-50`, an ungated `animate-bounce` | `motion-limits`, `react-doctor/no-ungated-tailwind-animation` |
255
- | Tailwind's scale and the theme's tokens: `w-64`, `text-sm`, `bg-primary`; `className` on a primitive for layout only | `w-[312px]`, `text-blue-500`, `style={{...}}`, an unknown class, a primitive restyled | `shadcn/no-arbitrary-values`, `shadcn/no-raw-colors`, `shadcn/no-inline-styles`, `shadcn/no-unknown-classes`, `shadcn/no-restyle` |
256
- | The UI package's primitive | A styled native `<button>` or `<input>` | `no-native-control` |
257
- | `assert` from `@effect/vitest`, the failure's identity | `expect`, `vi`, an assertion on message wording | `no-restricted-imports`, `no-error-message-assertion` |
258
- | One comment line for an outside constraint | A multiline comment | `no-multiline-comment` |
259
- | Object keys in ascending order; a blank line starts a new group | Unsorted keys, a `sort-keys` disable | `sort-keys`, `no-unexplained-disable` |
186
+ Lint rejects the Never column; write the Write column from the start. The fix command rewrites the rows whose rule has an autofix.
187
+
188
+ | Write | Never | Rule |
189
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
190
+ | An early `return`, `Boolean.match(x, {onFalse, onTrue})`, `Option.match`, a lookup record; `options?.root ?? '.'` for a default; `Array.ensure(value)` | `a ? b : c`, in JSX too | `no-ternary` |
191
+ | `selected === true` on a value that can be `undefined`; `ref.current === null`, `editor === undefined` | `if (selected)` on an optional, `if (!ref.current)`, `if (editor)` | `strict-boolean-expressions` |
192
+ | `Array.filter` before the loop, an early `return` in a callback | `continue` | `no-continue` |
193
+ | At most 3 nested blocks, 6 nested callbacks and 3 nested `pipe` calls; a named function for a deeper branch | Deeper nesting | `max-depth`, `max-nested-callbacks`, `no-deep-pipe` |
194
+ | `pipe(value, f, g)` | `value.pipe(f, g)` | `no-restricted-properties` |
195
+ | `const total = Array.reduce(values, 0, Number.sum)` | `let` with a loop | `no-let` |
196
+ | `return consume(String.trim(text))` | `const trimmed = String.trim(text); return consume(trimmed)` | `no-single-use-value` |
197
+ | `Array.join(parts, ', ')`, `Array.findFirst(items, f)`, `String.split(text, ',')`, `Record.keys(record)` | `parts.join(', ')`, `items.find(f)`, `text.split(',')`, `Object.keys(record)` | `effecttsgo/prefer-effect-array`, `effecttsgo/prefer-effect-record`, `no-native-method-call`; the `Path` service's `path.join` and a third-party object's own method stay |
198
+ | `Array.sort(entries, Order.mapInput(Order.Number, (entry: Entry) => entry.tokens))` | `entries.sort((left, right) => left.tokens - right.tokens)` | `no-native-method-call` |
199
+ | `Predicate.isString(v)`, `Predicate.hasProperty(node, 'tag')`, `Predicate.isNotNullish` passed directly | `typeof`, `'tag' in node`, `v !== null && v !== undefined`, a hand-written guard `(v): v is T => v !== null` | `no-typeof`, `no-in-operator`, `no-double-nullish-check`, `no-hand-written-guard` |
200
+ | `Array.isReadonlyArrayEmpty(items)`, `String.isNonEmpty(text)` | `items.length === 0`, `text.length > 0` | `no-native-emptiness-check` |
201
+ | `Option.map(option, f)`, `Option.match`, `Option.getOrElse` | `option.value` after `Option.isSome` | `no-option-value-access` |
202
+ | `undefined`, `Option.getOrUndefined`; `null` only in `.tsx`, such as `useRef<HTMLDivElement>(null)`, and for an outside contract | `null`, `Option.getOrNull` | `unicorn/no-null`, `no-restricted-properties` |
203
+ | `Number.parse(text)`, `Number.max`, `HashMap`, `HashSet`, `DateTime` | `parseInt`, `Math.max`, `Map`, `Set`, `Date` | `no-restricted-globals`, `no-restricted-properties`, `no-restricted-types` |
204
+ | `Clock`, `Config`, `Console`, `Random`, `Effect.sleep`, `HttpClient` | `Date.now()`, `process.env`, `console`, `setTimeout`, `fetch` | `effecttsgo/global-*`, `effecttsgo/process-env` |
205
+ | `FileSystem`, `Path`, `ChildProcess`, `Crypto`'s `randomUUIDv4` | `node:fs`, `node:path`, `node:child_process`, `crypto.randomUUID()` | `effecttsgo/node-builtin-import`, `effecttsgo/crypto-random-uuid` |
206
+ | `#lib/utils` through the package's `imports` map, named imports, named exports | `../lib/utils`, `import * as utils from '#lib/utils'`, `export default` outside config files, a `namespace` outside a `declare` | `import/no-relative-parent-imports`, `import/no-namespace`, `import/no-default-export`, `typescript/no-namespace` |
207
+ | `import {Folder} from 'lucide-react'` from the defining module | `export * from 'lucide-react'`, a file that only re-exports | `no-barrel-module` |
208
+ | A name an outer scope does not use | An inner `entry` shadowing an outer `entry` | `no-shadow` |
209
+ | A new value | Assigning to a parameter or its properties | `no-param-reassign` |
210
+ | `/pattern/u` | A regular expression without the `u` or `v` flag | `require-unicode-regexp` |
211
+ | Strings, numbers and booleans in a template literal | An object, `Option` or `unknown` value interpolated | `typescript/restrict-template-expressions`, `typescript/no-base-to-string` |
212
+ | A check only where the type allows both outcomes | `?.`, `??` or a condition on a value whose type rules one outcome out | `typescript/no-unnecessary-condition` |
213
+ | A type parameter that links two positions | A type parameter used once, `string \| 'id'`, `void` in a union or parameter | `typescript/no-unnecessary-type-parameters`, `typescript/no-redundant-type-constituents`, `typescript/no-invalid-void-type` |
214
+ | `items => service.load(items)` | `service.load` passed unbound | `typescript/unbound-method` |
215
+ | An Effect (`Effect.fn`, `Effect.tryPromise`); in `.tsx` only, an `async` handler that awaits | An `async` function in `.ts`, `async` without `await`, `void promise`, an unhandled promise | `effecttsgo/async-function`, `typescript/require-await`, `no-void`, `typescript/no-floating-promises` |
216
+ | Values decoded with Schema before use | Member access, calls, assignments, arguments or returns on `any` | `typescript/no-unsafe-*` |
217
+ | `function name() {}` for a named function; an arrow only in place | `const name = () => {}` | `func-style` |
218
+ | `server => ({...server, warmup})`, `for (const schema of schemas)`, `pair => pair[0]` | `({host, port}) =>`, `for (const {name} of schemas)` | `no-destructured-parameter`; labeled tuples, React state and refs can destructure |
219
+ | The source value where it is used | A one-line wrapper, alias or argument-free constant function | `no-trivial-indirection`, `no-constant-function` |
220
+ | `props.note.title` in place; an alias only for a value a check narrows and a callback reads | `const note = props.note`, `export const server = extra.server` | `no-property-alias` |
221
+ | `const MAX_WHISPER_SECONDS = 2 * 60 * 60` directly under the imports; a one-line literal read once in place | `const maxSize = 10` below a function, `const pattern = /x/u` read once | `module-constant` |
222
+ | Definitions above their first use | A `const` read above its declaration | `no-use-before-define` |
223
+ | The element where it renders; a component such as `function DeleteButton(props)` when several places render it | `const remove = <Button label="Delete" />` | `no-stored-jsx` |
224
+ | The library's own type, such as `import type {AnyNode} from 'domhandler'`, else `ReturnType<typeof make>` in the signature that needs it | `type Options = Parameters<typeof make>[0]` kept as a module alias | `no-floating-derived-type` |
225
+ | `export type Kind = typeof Kind.Type` above `export const Kind = Schema.Literals([...])`; `Kind.pick([...])` for a subset | `(typeof kinds)[number]['kind']`, `keyof typeof table` | `no-type-from-value` |
226
+ | `import {Schema} from 'effect'` | `import {Schema as S} from 'effect'` | `no-renamed-import` |
227
+ | Properties built with `Effect.fn` or `Effect.fnUntraced` | `fetch: url => Effect.tryPromise(...)` as a property arrow | `no-effect-property-arrow` |
228
+ | Inferred return and variable types, `satisfies`, `Array.empty<T>()` | `function f(): T`, `const items: T[] = []` | `no-return-type`, `no-redundant-variable-annotation` |
229
+ | `{values: string[]}`; `readonly` only where the fix adds it, on service shapes | `readonly string[]`, `Readonly<T>` | `no-readonly-type-syntax` |
230
+ | `satisfies T`, `as const` | `as T`, `any`, `value!` | `typescript/consistent-type-assertions`, `typescript/no-explicit-any`, `typescript/no-non-null-assertion` |
231
+ | `useRef(initial)` for lifecycle state | `const [box] = useState(() => ({current: initial}))` with no setter | `no-fake-ref-state` |
232
+ | `Ref.make(0)`, `useState(false)`, `useRef<HTMLDivElement>(null)` | `Ref.make<number>(0)`, `useRef<HTMLDivElement \| null>(null)` | `no-redundant-state-type`, `no-redundant-use-ref-null-type` |
233
+ | `T[]`, an optional property | `Array<T>`, `Promise<T>`, `x: T \| undefined` | `typescript/no-restricted-types` |
234
+ | `Effect.tryPromise({catch: cause => X.make({cause, message}), try})`, each failure typed by its tagged error | `unknown` or `any` in the error or requirements channel, a `catch` returning the raw cause | `effecttsgo/any-unknown-in-error-context` |
235
+ | `Effect.mapError(cause => X.make({cause, message}))` | `Effect.mapError(failure => X.make({message}))`, `catch: failure => X.make({message})` | `preserve-error-cause` |
236
+ | The failure in the error channel; `Effect.option` for a value that is optional by design | `Effect.orElseSucceed(() => [])`, `Effect.catch(() => Effect.void)`, `Effect.succeed('')` in a catch | `no-empty-recovery` |
237
+ | `Effect.forEach(items, load, {concurrency: 'unbounded'})`; `{concurrency: 1}` when order or a resource matters | `Effect.forEach(items, load)`, `Effect.all([a, b])`, `Effect.all(effects, {discard: true})` | `explicit-effect-concurrency` |
238
+ | `Effect.fn('Service.method')` for a function returning an Effect | A plain function returning `Effect.gen` | `effecttsgo/effect-fn-opportunity` |
239
+ | Layers provided once at the entry point; `Layer.provide` between layers | `Effect.provide(layer)` inside services or per call | `effecttsgo/strict-effect-provide` |
240
+ | `Layer.provideMerge` when one layer needs another | `Layer.mergeAll` of layers that depend on each other | `effecttsgo/layer-merge-all-with-dependencies` |
241
+ | One `pipe(value, f, g, h)` | `pipe(pipe(value, f), g)`, `pipe(value)` | `effecttsgo/unnecessary-pipe-chain`, `effecttsgo/unnecessary-pipe` |
242
+ | Service keys `@<package>/<path>/<Name>`; an error's tag is its class name | A service key that does not match the package, path and class name | `effecttsgo/deterministic-keys` |
243
+ | `return yield* NotFound.make({...})` | `yield* Effect.fail(NotFound.make({...}))` | `effecttsgo/unnecessary-fail-yieldable-error` |
244
+ | `Schema.decodeEffect(S)(encoded)` on an `S.Encoded` value, `Schema.fromJsonString(S)` for JSON text | `Schema.decodeUnknownEffect(S)` on typed input, `JSON.parse` in an Effect | `effecttsgo/prefer-typed-schema-decoder`, `effecttsgo/prefer-schema-over-json` |
245
+ | `Schema.fromJsonString(Protocol)` with the protocol's schema | `Schema.UnknownFromJsonString`, `Schema.fromJsonString(Schema.Unknown)` | `no-unvalidated-json-decode` |
246
+ | `export type X = typeof X.Type` directly before `export const X`; an inferred schema | A type elsewhere, `Schema.Schema<X>` restated, an unannotated `suspend` thunk | `schema-type-pair` |
247
+ | `X.make(...)`, `Schema.is(X)(value)`, `Schema.decodeEffect` inside an Effect, `Schema.Finite` for numbers | `new X(...)` on a schema class, `instanceof X`, `Schema.decodeSync` in a generator, `Schema.Number` | `effecttsgo/new-schema-class`, `effecttsgo/instance-of-schema`, `effecttsgo/schema-sync-in-effect`, `effecttsgo/schema-number` |
248
+ | The Schema operation inline at its use | `const decode = Schema.decodeEffect(S)` stored for later | `no-stored-schema-operation` |
249
+ | `Schema.Struct`, `Schema.TaggedError`, `Schema.NonEmptyString`, `Schema.Literals([...])` | `Schema.Class`, `Data.TaggedError`, `Error`, a rebuilt refinement, `Schema.Union` of literals | `no-restricted-properties`, `no-restricted-globals`, `no-reinvented-schema`, `effecttsgo/schema-union-of-literals` |
250
+ | The failure mapped to the service's error; best-effort work as `Effect.ignore({log: 'Warn', message})` | A bare `Effect.ignore` or `Effect.ignoreCause`, `Effect.orDie` outside tests | `no-discarded-failure` |
251
+ | `NodeRuntime.runMain(program)`; atoms in React | `Effect.runPromise`, `ManagedRuntime.make` | `no-restricted-properties` |
252
+ | `import {useState} from 'react'`; `const [open, setOpen] = useState(false)` | `React.useState`, `const state = useState(false)` | `no-restricted-properties`, `no-undestructured-use-state` |
253
+ | React Compiler memoization, a `ref` prop | `useCallback`, `useMemo`, `memo`, `forwardRef` | `no-restricted-imports` |
254
+ | Code the compiler accepts | `'use no memo'`, an inline disable of a React rule | `no-compiler-opt-out`, `react/rule-suppression` |
255
+ | `const countAtom = Atom.make(0)` at module level, `Atom.family(id => ...)` per prop | `Atom.make` inside a component or hook, `useState(() => Atom.make(0))` | `no-component-atom-allocation` |
256
+ | `<Editor key={draft.id} />` to reset a draft; a value derived in render | A `useEffect` that copies a prop into state | `react-doctor/no-adjust-state-on-prop-change`, `react-doctor/no-derived-state` |
257
+ | `<Label htmlFor="name">`, `aria-label` on a field without a visible label, a `<Button>` for a click | A placeholder as the only label, `<div onClick>` | `jsx-a11y/*`, `react-doctor/no-placeholder-only-field` |
258
+ | `bg-card`, `border` | `bg-linear-to-b from-muted`, `bg-[radial-gradient(...)]` | `no-gradient` |
259
+ | `duration-200`, `zoom-in-95`, `starting:scale-95`, `motion-safe:animate-spin` | `duration-300`, `zoom-in`, `starting:scale-50`, an ungated `animate-bounce` | `motion-limits`, `react-doctor/no-ungated-tailwind-animation` |
260
+ | Tailwind's scale and the theme's tokens: `w-64`, `text-sm`, `bg-primary`; `className` on a primitive for layout only | `w-[312px]`, `text-blue-500`, `style={{...}}`, an unknown class, a primitive restyled | `shadcn/no-arbitrary-values`, `shadcn/no-raw-colors`, `shadcn/no-inline-styles`, `shadcn/no-unknown-classes`, `shadcn/no-restyle` |
261
+ | The UI package's primitive | A styled native `<button>` or `<input>` | `no-native-control` |
262
+ | `assert` from `@effect/vitest` on the failure's identity; a screen proved in the browser | `expect`, `vi`, an assertion on message wording, `@testing-library/*`, `jsdom` | `no-restricted-imports`, `no-error-message-assertion` |
263
+ | One comment line for an outside constraint | A multiline comment | `no-multiline-comment` |
264
+ | Object keys in ascending order; a blank line starts a new group | Unsorted keys, a `sort-keys` disable | `sort-keys`, `no-unexplained-disable` |
260
265
 
261
266
  Forms that satisfy every rule where the obvious fix trades one diagnostic for another:
262
267
 
@@ -5,7 +5,7 @@ description: 'Use for any review, audit, critique or review round of code, a PR,
5
5
 
6
6
  # Review
7
7
 
8
- Do what a short seller does to a position: build the strongest case that the work is wrong from raw evidence (the code, real runs, data), never from the author's summary or rationale. Before you report, name what a top expert in the area would check that the brief did not ask for, and check it. Load every skill the work needed (engineering for code, design for visible output, and the other skills the checkout has for the task) with the repository's `CODING_STANDARDS.md` and agent instructions; last, answer each loaded skill's Critique against the change, as its author should have: a question the change fails is a finding.
8
+ Do what a short seller does to a position: build the strongest case that the work is wrong from raw evidence (the code, real runs, data), never from the author's summary or rationale. Load every skill the work needed (engineering for code, design for visible output, and the other skills the checkout has for the task) with the repository's `AGENTS.md` files and the standards they link; last, answer each loaded skill's Critique against the change, as its author should have: a question the change fails is a finding.
9
9
 
10
10
  ## Findings
11
11
 
@@ -17,20 +17,19 @@ Each finding: severity, `path:line`, the reaching input, actual and required out
17
17
  | P2 | Wrong owner, boundary or organization, rule violation, duplicated or dead code, or a mechanism, option or element the request does not need |
18
18
  | P3 | Naming, wording or style |
19
19
 
20
- - **Classes.** Report a defect class once, with every instance's `path:line`; never repeat a finding an earlier round raised and the author answered.
20
+ - **One pass.** Report everything the first round can find: every class once, with every instance's `path:line`, across every layer, so no later round discovers what this one could have. A later round checks each earlier fix and reads only the commits since; it never repeats a finding the author answered.
21
21
  - **Rank honestly.** Never inflate a P3 to keep it alive; never pad a report with P3 to look thorough. Return P1 first; "Clean" with the covered scope when nothing reaches P2.
22
- - **Not findings:** taste without a rule, hypothetical inputs no caller sends, defects the branch neither made nor relies on, what lint reports outside legacy lists.
22
+ - **Not findings:** taste without a rule, hypothetical inputs no caller sends, defects the branch neither made nor relies on, what lint already reports.
23
23
  - **Probe** a suspicion inline per engineering's Proof; never start a service or test suite in a read-only review.
24
24
 
25
25
  ## Code
26
26
 
27
27
  - **Architecture first.** Before the lines, rebuild the change from the request alone: who owns each piece of state, the boundaries, the file and module organization, the data flow and the mechanism. The first pass usually gets this wrong, so attack it: wherever the change's shape differs from the simplest idiomatic one (fewer parts, the existing pattern, Effect's own building blocks), report it with that shape as the fix, even when every line is correct.
28
- - **Depth.** Read every hand-written changed line; for generated output, the generator change and a sample. Trace each contract through its real consumers, failure paths, concurrency and resource ownership against the merge base. A whole-file refactor keeps every behavior the file had.
28
+ - **Depth.** Read every hand-written changed line; for generated output, the generator change and a sample. Trace each contract through its real consumers, failure paths, concurrency and resource ownership against the merge base.
29
29
  - **Simplicity.** Name each part the request does not need: a script, flag, option, wrapper, fallback, reimplemented library job, UI control or test, and each abstraction (helper, shared constant, generic, factory) that saves lines without lowering what a reader must hold. Name each block that duplicates code already in the repository, with both locations.
30
30
  - **Requirements.** Map each of the user's requirements to the hunk and the proof that meet it; a requirement with neither is a finding, even when no line changed for it.
31
- - **Production data.** A changed persisted shape without a migration that converts existing data is P1.
32
31
  - **Beyond the diff.** A contradiction with an unchanged file, a stale reference the change leaves, or a false assumption it rests on.
33
- - **Rounds.** Read the pushed head, never the author's working tree, which keeps changing: `gh pr diff <n>`, `glab mr diff <n>`, `git show <head>:<path>`. Judge the layer as one change; spend depth on commits since the last reviewed one. A stack overview reports what crosses layers: duplication, a contract one layer breaks for another, code in the wrong layer.
32
+ - **Head.** Read the pushed head, never the author's working tree, which keeps changing: `gh pr diff <n>`, `glab mr diff <n>`, `git show <head>:<path>`. Judge a stack as one change and report what crosses layers: duplication, a contract one layer breaks for another, code in the wrong layer.
34
33
  - **Browser.** Inspect the existing preview, captures, console and requests without changing page state; name the smallest missing check for the author.
35
34
 
36
35
  ## Cleanup audit
package/tsconfig.json CHANGED
@@ -1,11 +1,9 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "strict": true,
4
- "noUnusedLocals": true,
5
4
  "noImplicitReturns": true,
6
5
  "noErrorTruncation": true,
7
6
  "allowUnusedLabels": false,
8
- "noUnusedParameters": true,
9
7
  "noImplicitOverride": true,
10
8
  "allowUnreachableCode": false,
11
9
  "noUncheckedIndexedAccess": true,