--- description: Lint config shape: every non-nursery Oxlint category on, plugins via `all`, a blacklist of offs in configs.project, per-file exceptions only in projectOverrides, ignorePatterns only for generated output --- Policy (2026-10-19): a blacklist, not a whitelist. Replaces the older category-subset and "no overrides" notes. - **Categories:** `correctness`, `suspicious`, `pedantic`, `perf`, `style`, `restriction` at `error`. `nursery` stays off (no line for it). - **Plugins** are enabled whole through their `all` config (`explicitTypes.configs.all.rules`, `importStructure.configs.all.rules`). - **Blacklist:** repo-wide offs and option tweaks live in `projectLint.configs.project.rules` in `scripts/project-lint.ts`, spread last in `rules`, each with a one-line reason. Never re-list a rule a category already turns on. bot-agents runs on Node everywhere, so `import/no-nodejs-modules`, `unicorn/no-process-exit`, and `no-console` are off repo-wide. - **Per-file exceptions** only via `projectOverrides`, the whole `overrides` array exported from `scripts/project-lint.ts`: the Node code entry (`vite.config.ts`, `scripts/**`, `nodeRules`) first, then one entry per exception with a why-comment. `vite.config.ts` has exactly `overrides: projectOverrides`. - **No `allowFiles`** options in custom rules, and no disable comments ([[lint-no-disable-comments]]). - **`ignorePatterns`** only for generated or build output, never hand-written code. A hand-written file is linted, with override entries for only what it legitimately breaks. bot-agents has no generated code, so none. - **Precedence:** categories < `rules` (a later spread wins) < `overrides` (a later entry wins). - Size and complexity limits (`complexity`, `max-lines`, ...) are set to the smallest value the current code passes. ```ts // scripts/project-lint.ts export const projectOverrides: OxlintOverride[] = [ { files: ["vite.config.ts", "scripts/**"], rules: nodeRules }, // Why: src/example.ts must ... (a per-file exception, if one is ever needed) // { files: ["src/example.ts"], rules: { "some-rule": "off" } }, ]; // vite.config.ts rules: { ...explicitTypes.configs.all.rules, ...importStructure.configs.all.rules, ...projectLint.configs.project.rules, }, overrides: projectOverrides, ``` ## Rule decisions (2026-10-19, all repos) - `typescript/promise-function-async` is on with `require-await` and `typescript/return-await`. A function that returns a promise is `async` and awaits it: `async f() { const x = await g(); return x; }`. Test doubles do the same (`const resolved = await Promise.resolve(v); return resolved;`), and `expect(promise).rejects` takes the promise itself. `src/processes/child.ts` is exempt in `projectOverrides`: `exitCode` must return the very promise it marks handled, or a failed spawn would surface as an unhandled rejection before callers await it. - `init-declarations` is on: an uninitialized `let` becomes `let x: T | null = null;` (`unknown` already includes `null`); a `try` that assigns and a `catch` that exits still narrow it. - `no-use-before-define` is on with only `{ functions: false }`. - `no-await-in-loop` is off (sequential awaits are intentional) and `unicorn/no-array-reduce` is on: sequential steps are plain `for` loops, never `reduce` chains or recursion. - `consistent-return`, `typescript/switch-exhaustiveness-check`, and `default-case` are off: TypeScript's checks cover them, and no `default:` branch is added to an exhaustive switch. - `unknown` is allowed; `any` is banned and the `typescript/no-unsafe-*` rules are on. Untrusted data is `unknown` plus a type guard. - `unicorn/no-array-for-each` (use `for...of`), `unicorn/prefer-structured-clone`, and `typescript/prefer-nullish-coalescing` (defaults) stay on. - explicit-types `upper-snake-string-unions` is on ([[enum-values-upper-snake]]); `unicorn/custom-error-definition` is on, so a custom error has a constructor that sets `this.name`. - `src/post/` and `src/profiles/` (code and tests) are exempt from `project/prefer-ascii` in `projectOverrides`: posted Gitea text shows emoji verdict marks and a middle dot. ## Rule decisions (2026-10-20, all repos) - `unicorn/number-literal-case` is off: Oxfmt lowercases hex digits back. - `typescript/consistent-return` is off too: it misfires on complete switches, like `consistent-return`. - On with defaults, no offs: `prefer-object-spread`, `unicorn/prefer-spread`, `unicorn/no-useless-spread`, `typescript/no-misused-spread`, `no-plusplus` (`x += 1`), `unicorn/no-array-method-this-argument`, `typescript/method-signature-style` ("property"), `typescript/unified-signatures`. - `unicorn/import-style` uses its default: `import nodePath from "node:path"` and `nodePath.join(...)` (`nodePath`, since `path` is a common variable). - `no-template-curly-in-string` is on; only `tests/workflow.test.ts`, which checks literal Gitea `${{ }}` expressions, is exempt in `projectOverrides`. - Rule defaults are preferred over option tweaks unless a tweak is needed. - `fmt` matches `deno fmt`: `printWidth: 80`, `quoteProps: "preserve"`, `embeddedLanguageFormatting: "off"`. `max-lines-per-function` is 124 after the narrower width. A new category hit is fixed in the code; an off or exception is added only for a real collision or churn, with its reason. Rules: [[project-lint-plugin]].