Export the whole overrides array from project-lint and document the lint config
Check / check (pull_request) Successful in 1m28s

This commit is contained in:
Danny Kim
2026-10-19 16:49:57 +09:00
parent df66bea5e4
commit f2de92067f
9 changed files with 77 additions and 120 deletions
@@ -1,21 +0,0 @@
---
description: bot-agents lint has prefer-readonly-parameter-types and the size/complexity rules off (Deno had no limits) and no overrides; nothing else
---
The repo follows [[lint-config-shape]] with one recorded exception, present
when the migration PR was approved:
- `typescript/prefer-readonly-parameter-types` is `"off"` in
`scripts/project-lint.ts`, as in the explicit-types template, because
Node/web types (`Response`, `Stats`, `RequestInit`) can never satisfy it.
- `vite.config.ts` has no `overrides`. The `scripts/**` override for
`typescript/no-unsafe-enum-comparison` was removed (2026-10-16):
`scripts/project-lint.ts` compares node types with `AST_NODE_TYPES` members
from `@typescript-eslint/types` instead of string literals
([[lint-overrides-policy]]).
Size and complexity limits match Deno, which had none here, so every Oxlint
limit rule is `"off"` ([[lint-config-shape]]). `src/run/index.ts` and `src/post/index.ts` keep
main's single-file layout (2026-10-17 revert of the size splits). Sequential
awaits in loops became recursion or a `reduce` chain (`no-await-in-loop`) to
stay sequential. Add no other opt-out without the user.
+1 -1
View File
@@ -72,7 +72,7 @@ import { x } from "../x.ts"; // no: no ../ anywhere
- An area used from outside exposes its API through its alias target, and - An area used from outside exposes its API through its alias target, and
callers import the alias (`#ui`), not files inside it. callers import the alias (`#ui`), not files inside it.
- Barrels are also the standard fix for `import/max-dependencies`: one barrel - Barrels are also the standard fix for `import/max-dependencies`: one barrel
import replaces many direct ones ([[lint-overrides-policy]]). import replaces many direct ones ([[lint-config]]).
- `import/no-cycle` is on; a barrel must not create a cycle. - `import/no-cycle` is on; a barrel must not create a cycle.
Relative imports are written with the `.ts` extension. Relative imports are written with the `.ts` extension.
-48
View File
@@ -1,48 +0,0 @@
---
description: vite.config.ts lint uses Oxlint categories at error with Deno size limits, names no rule itself, and spreads configs from the plugins and scripts/project-lint.ts
---
The lint config is short and category-driven (user decisions 2026-10-14/15):
turn on whole categories, then list only what parity or a real need adds.
```ts
lint: {
plugins: ["typescript", "unicorn", "oxc", "import"],
jsPlugins: ["eslint-plugin-explicit-types", "./scripts/project-lint.ts"],
categories: { correctness: "error", suspicious: "error", pedantic: "error", perf: "error" },
rules: { ...explicitTypes.configs.concrete.rules, ...projectLint.configs.project.rules },
ignorePatterns: ["src/generated/**"], // generated files only, see below
options: { typeAware: true, typeCheck: true, maxWarnings: 0, reportUnusedDisableDirectives: "error" },
},
```
- **Categories:** `correctness`, `suspicious`, `pedantic`, `perf` at `error`.
`style` and `restriction` are never turned on wholesale; pick single rules
from them in `configs.project`. Extra rules that categories bring are fine.
- **Size limits match Deno** (2026-10-17, replaces the 2026-10-16 "default
limits stay"): repos behave as they did on Deno. bot-agents ran `deno lint`
with no config, so it had no size or complexity limit, and every Oxlint
limit rule (`complexity`, `max-lines`, `max-params`, `id-length`,
`no-nested-ternary`, `max-lines-per-function`, `max-classes-per-file`,
`max-depth`, `max-nested-callbacks`, `max-statements`,
`import/max-dependencies`, `unicorn/max-nested-calls`) is `"off"` in
`configs.project`. No file is split for size. Do not add new opt-outs
anywhere without asking the user.
- **`vite.config.ts` names no rule.** Every rule id, built-in ones included,
lives in `configs.project` of `scripts/project-lint.ts`
([[project-lint-plugin]]). `vite.config.ts` only spreads configs.
- **Published plugins are consumed through their configs**: ESLint flat config
uses `plugin.configs.<name>`; Oxlint lists the package in `jsPlugins` and
spreads `plugin.configs.<name>.rules`, since Oxlint plugins carry no configs.
Use the configs equivalent to what the repo enabled under Deno.
- **`maxWarnings: 0`** keeps Deno's "every diagnostic fails" behaviour.
- **`ignorePatterns`** is only for generated files (compiled output, codegen).
Hand-written code is never ignored.
- **No lint disable comments**: [[lint-no-disable-comments]].
- **Parity:** [[lint-parity-never-shrinks]]. **Naming:** [[naming-id-match]].
- Don't add a second import sorter; the import-structure plugin's
`enforce-import-order` is the only one, and Oxfmt's `sortImports` stays off.
`perf` came last: in the template it flagged nine `.includes` on constant
arrays, fixed as `ReadonlySet` + `.has()` rather than by turning a rule off.
That is the expected response to any new rule: change the code.
+51
View File
@@ -0,0 +1,51 @@
---
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,
```
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]].
-33
View File
@@ -1,33 +0,0 @@
---
description: No lint overrides for source files; narrow ones only for scripts/**; generated files go in ignorePatterns; size limits match Deno, so nothing is split for size
---
Final policy (2026-10-16). The user considers per-file `overrides` bad
practice.
- **Source files: never.** No `overrides` entry may target `src/**` or any
other hand-written production or test code.
- **The one exception** is in lint-plugin repos: `import/max-dependencies`
off for `src/rules/index.ts`, the rule registry, which must import every
rule. It is not a precedent for apps.
- **`scripts/**`:** a narrow override is allowed when a tooling script truly
needs it, e.g. `no-console` in a CLI script.
- **Generated files:** list them in `ignorePatterns`, never `overrides`.
- **Anything else needs the user's approval first.** When the user is away,
don't add it: refactor, or leave the violation failing and log the question.
## Fix the code instead
Category rule violations are fixed in the code without behavior change,
covered by the tests, inside the same migration PR; e.g. `prefer-set-has`
becomes `const K: ReadonlySet<string> = new Set([...])` with `.has`.
Size and complexity limits are not refactored for: they match what Deno
enforced (2026-10-17, replacing the earlier "refactor to Oxlint's defaults"),
which for bot-agents is none, so those rules are `"off"`
([[lint-config-shape]]).
Alias rule: an alias may point anywhere and owns its target (a folder's
`index.ts` owns the folder and below, any other file owns only itself); no
two aliases may own the same file, and `../` is banned everywhere
([[imports-aliases-barrels]]).
+2 -2
View File
@@ -52,5 +52,5 @@ export default plugin;
and every repo should look the same. and every repo should look the same.
`scripts/` is tooling, not production code: never packed or published, and `scripts/` is tooling, not production code: never packed or published, and
no tests. Narrow `overrides` for `scripts/**` are allowed when a script truly no tests. Its offs sit in the Node code entry of `projectOverrides`; see
needs one (e.g. `no-console`); see [[lint-overrides-policy]]. [[lint-config]].
+5 -3
View File
@@ -50,9 +50,11 @@ relative imports. `scripts/` is flat: one file per tool, no subfolders and no `i
([.agents/memory/imports-aliases-barrels.md](.agents/memory/imports-aliases-barrels.md)). ([.agents/memory/imports-aliases-barrels.md](.agents/memory/imports-aliases-barrels.md)).
The only config files are `package.json`, `tsconfig.json`, and The only config files are `package.json`, `tsconfig.json`, and
`vite.config.ts`, plus `package-lock.json`. Repo-local lint rules and the rule `vite.config.ts`, plus `package-lock.json`. Repo-local lint rules, the rule
list live in `scripts/project-lint.ts`. Lint disable comments are not allowed; list, and the `overrides` array (`projectOverrides`) live in
fix the source instead. `scripts/project-lint.ts`
([.agents/memory/lint-config.md](.agents/memory/lint-config.md)). Lint disable
comments are not allowed; fix the source instead.
The composite action uses npm only to install the externally distributed The composite action uses npm only to install the externally distributed
Claude Code, Codex, and OpenCode command-line tools; do not treat those Claude Code, Codex, and OpenCode command-line tools; do not treat those
+16 -5
View File
@@ -10,7 +10,7 @@
import type { LanguageOptions, RuleDefinition, RuleVisitor, TextSourceCode } from "@eslint/core"; import type { LanguageOptions, RuleDefinition, RuleVisitor, TextSourceCode } from "@eslint/core";
import { AST_NODE_TYPES, type TSESTree } from "@typescript-eslint/types"; import { AST_NODE_TYPES, type TSESTree } from "@typescript-eslint/types";
import type { DummyRuleMap } from "vite-plus/lint"; import type { DummyRuleMap, OxlintOverride } from "vite-plus/lint";
/** The source code both hosts hand a rule, with the comment list. */ /** The source code both hosts hand a rule, with the comment list. */
interface ProjectSourceCode extends TextSourceCode { interface ProjectSourceCode extends TextSourceCode {
@@ -291,7 +291,7 @@ const rules: DummyRuleMap = {
"no-continue": "off", "no-continue": "off",
}; };
/** Node code (`vite.config.ts` and `scripts/`), spread into `overrides`. */ /** Node code (`vite.config.ts` and `scripts/`): the first `projectOverrides` entry. */
const nodeRules: DummyRuleMap = { const nodeRules: DummyRuleMap = {
// Vite and the Oxlint plugin host read a default export. // Vite and the Oxlint plugin host read a default export.
"import/no-default-export": "off", "import/no-default-export": "off",
@@ -303,10 +303,21 @@ const nodeRules: DummyRuleMap = {
"no-console": "off", "no-console": "off",
}; };
/**
* The whole `overrides` array of `vite.config.ts`: the Node code entry first,
* then each per-file exception, each with a comment saying why. Later entries
* win over earlier ones, and all of them over `rules`.
*/
export const projectOverrides: OxlintOverride[] = [
{
files: ["vite.config.ts", "scripts/**"],
rules: nodeRules,
},
];
/** /**
* The repo-local plugin, namespace `project`: its own rules, enabled as * The repo-local plugin, namespace `project`: its own rules, enabled as
* `project/<rule>`, `configs.project`, the rule list above, and * `project/<rule>`, and `configs.project`, the rule list above.
* `configs.node`, the Node code offs.
*/ */
const plugin = { const plugin = {
meta: { name: "project" }, meta: { name: "project" },
@@ -314,7 +325,7 @@ const plugin = {
"no-disable-comments": noDisableCommentsRule, "no-disable-comments": noDisableCommentsRule,
"no-asset-imports": noAssetImportsRule, "no-asset-imports": noAssetImportsRule,
}, },
configs: { project: { rules }, node: { rules: nodeRules } }, configs: { project: { rules } },
}; };
export default plugin; export default plugin;
+2 -7
View File
@@ -2,7 +2,7 @@ import explicitTypes from "eslint-plugin-explicit-types";
import importStructure from "eslint-plugin-import-structure"; import importStructure from "eslint-plugin-import-structure";
import { defineConfig } from "vite-plus"; import { defineConfig } from "vite-plus";
import projectLint from "./scripts/project-lint.ts"; import projectLint, { projectOverrides } from "./scripts/project-lint.ts";
export default defineConfig({ export default defineConfig({
lint: { lint: {
@@ -25,12 +25,7 @@ export default defineConfig({
...importStructure.configs.all.rules, ...importStructure.configs.all.rules,
...projectLint.configs.project.rules, ...projectLint.configs.project.rules,
}, },
overrides: [ overrides: projectOverrides,
{
files: ["vite.config.ts", "scripts/**"],
rules: projectLint.configs.node.rules,
},
],
options: { options: {
typeAware: true, typeAware: true,
typeCheck: true, typeCheck: true,