diff --git a/.agents/memory/bot-agents-lint-opt-outs.md b/.agents/memory/bot-agents-lint-opt-outs.md deleted file mode 100644 index 309ab3d..0000000 --- a/.agents/memory/bot-agents-lint-opt-outs.md +++ /dev/null @@ -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. diff --git a/.agents/memory/imports-aliases-barrels.md b/.agents/memory/imports-aliases-barrels.md index 3a944f4..e93f298 100644 --- a/.agents/memory/imports-aliases-barrels.md +++ b/.agents/memory/imports-aliases-barrels.md @@ -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 callers import the alias (`#ui`), not files inside it. - 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. Relative imports are written with the `.ts` extension. diff --git a/.agents/memory/lint-config-shape.md b/.agents/memory/lint-config-shape.md deleted file mode 100644 index eb2b255..0000000 --- a/.agents/memory/lint-config-shape.md +++ /dev/null @@ -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.`; Oxlint lists the package in `jsPlugins` and - spreads `plugin.configs..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. diff --git a/.agents/memory/lint-config.md b/.agents/memory/lint-config.md new file mode 100644 index 0000000..c546420 --- /dev/null +++ b/.agents/memory/lint-config.md @@ -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]]. diff --git a/.agents/memory/lint-overrides-policy.md b/.agents/memory/lint-overrides-policy.md deleted file mode 100644 index d745855..0000000 --- a/.agents/memory/lint-overrides-policy.md +++ /dev/null @@ -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 = 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]]). diff --git a/.agents/memory/project-lint-plugin.md b/.agents/memory/project-lint-plugin.md index f0af358..27df2fd 100644 --- a/.agents/memory/project-lint-plugin.md +++ b/.agents/memory/project-lint-plugin.md @@ -52,5 +52,5 @@ export default plugin; and every repo should look the same. `scripts/` is tooling, not production code: never packed or published, and -no tests. Narrow `overrides` for `scripts/**` are allowed when a script truly -needs one (e.g. `no-console`); see [[lint-overrides-policy]]. +no tests. Its offs sit in the Node code entry of `projectOverrides`; see +[[lint-config]]. diff --git a/AGENTS.md b/AGENTS.md index ec3bf99..fb855e5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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)). The only config files are `package.json`, `tsconfig.json`, and -`vite.config.ts`, plus `package-lock.json`. Repo-local lint rules and the rule -list live in `scripts/project-lint.ts`. Lint disable comments are not allowed; -fix the source instead. +`vite.config.ts`, plus `package-lock.json`. Repo-local lint rules, the rule +list, and the `overrides` array (`projectOverrides`) live in +`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 Claude Code, Codex, and OpenCode command-line tools; do not treat those diff --git a/scripts/project-lint.ts b/scripts/project-lint.ts index 0c747b7..d913ba3 100644 --- a/scripts/project-lint.ts +++ b/scripts/project-lint.ts @@ -10,7 +10,7 @@ import type { LanguageOptions, RuleDefinition, RuleVisitor, TextSourceCode } from "@eslint/core"; 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. */ interface ProjectSourceCode extends TextSourceCode { @@ -291,7 +291,7 @@ const rules: DummyRuleMap = { "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 = { // Vite and the Oxlint plugin host read a default export. "import/no-default-export": "off", @@ -303,10 +303,21 @@ const nodeRules: DummyRuleMap = { "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 - * `project/`, `configs.project`, the rule list above, and - * `configs.node`, the Node code offs. + * `project/`, and `configs.project`, the rule list above. */ const plugin = { meta: { name: "project" }, @@ -314,7 +325,7 @@ const plugin = { "no-disable-comments": noDisableCommentsRule, "no-asset-imports": noAssetImportsRule, }, - configs: { project: { rules }, node: { rules: nodeRules } }, + configs: { project: { rules } }, }; export default plugin; diff --git a/vite.config.ts b/vite.config.ts index 24bb629..c6a5295 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,7 +2,7 @@ import explicitTypes from "eslint-plugin-explicit-types"; import importStructure from "eslint-plugin-import-structure"; import { defineConfig } from "vite-plus"; -import projectLint from "./scripts/project-lint.ts"; +import projectLint, { projectOverrides } from "./scripts/project-lint.ts"; export default defineConfig({ lint: { @@ -25,12 +25,7 @@ export default defineConfig({ ...importStructure.configs.all.rules, ...projectLint.configs.project.rules, }, - overrides: [ - { - files: ["vite.config.ts", "scripts/**"], - rules: projectLint.configs.node.rules, - }, - ], + overrides: projectOverrides, options: { typeAware: true, typeCheck: true,