Files
deepseek-harness/eslint.config.mjs
kingwl 53f6a959e2 chore(lint): one shared project service; ignore harness-local .claude state
Two independent multipliers were pushing bare `pnpm run lint` past node's
default heap:

1. parserOptions.project expanded to every package tsconfig plus the root
   one — each matched config built its OWN ts.Program, and the dev paths
   map pulls sibling package SOURCES (plus the vendored Cordis stack) into
   every such program, so resident memory grew as sum-of-closures, not
   repo size: ~4.6 GB peak for 425 repo files. projectService shares one
   tsserver-style graph: ~2.0 GB peak, ~28 s → ~14 s wall.

2. `eslint .` traversed .claude/ harness-local state — stale worktree
   checkouts there carry tens of thousands of additional .ts files
   (whole-repo copies), roughly tripling the work again even under the
   project service. Other checkouts are not this one's sources; ignore
   them like node_modules. (#169 carries the identical ignore line inside
   its chain; the hunks dedupe on its next rebase.)

Type-aware rules verified live under the service: a floating-promise
probe still trips no-floating-promises.
2026-07-10 22:13:40 +08:00

147 lines
6.0 KiB
JavaScript

import stylistic from '@stylistic/eslint-plugin'
import tseslint from 'typescript-eslint'
/**
* ESLint flat config. Two layers:
*
* 1. typescript-eslint strict-type-checked — correctness rules that need the
* type checker. The headline rules for this codebase: no-floating-promises
* and no-misused-promises (an un-awaited promise in the agent loop is our
* primary bug class), switch-exhaustiveness-check (we switch over
* merge-extensible unions everywhere).
* 2. @stylistic — formatting (2-space, no semicolons, single quotes, trailing
* commas), so style is enforced rather than drifting between agents.
*
* vendor/ is linted lightly (style only stays OFF — vendored code keeps
* upstream style; only a few safety rules apply there) and examples/tests are
* linted with relaxed unsafe-* rules where mocks intentionally bend types.
*/
export default tseslint.config(
{
ignores: [
'**/lib/**',
'**/node_modules/**',
'**/.sessions/**',
'.claude/**', // harness-local state (worktrees, skills) — other checkouts, not this one's sources
'**/.doc-typecheck-*/**',
'vendor/**', // vendored source keeps upstream style and idioms
'**/*.js',
'**/*.mjs',
'*.config.ts', // root tool configs (vitest, tsdown) — no project service
],
},
// --- our packages: full strictness -------------------------------------
{
files: ['packages/*/*/src/**/*.ts', 'examples/**/*.ts', 'scripts/**/*.ts'],
extends: [
...tseslint.configs.strictTypeChecked,
],
languageOptions: {
parserOptions: {
// One shared tsserver-style project service instead of 60+ standalone
// per-package programs: the old `project` glob built every package's
// full dependency closure (sibling sources via the dev `paths` map +
// the vendored Cordis stack) as its own program and kept them all
// resident — ~5 GB peak, an OOM past node's default heap. The service
// resolves each file to its nearest owning tsconfig and shares the
// graph.
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// The bug class this repo cares most about: lost promises in the loop.
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/require-await': 'error',
'@typescript-eslint/switch-exhaustiveness-check': ['error', {
considerDefaultExhaustiveForUnions: true,
}],
'@typescript-eslint/no-unnecessary-condition': ['error', {
allowConstantLoopConditions: true,
}],
// `any` requires a justification comment — enforced as: no bare casts.
'@typescript-eslint/no-explicit-any': 'error',
// Style points where the codebase intentionally diverges from preset:
'@typescript-eslint/no-namespace': 'off', // Cordis Config-namespace idiom
'@typescript-eslint/no-empty-object-type': 'off', // merge-extensible maps
'@typescript-eslint/no-invalid-void-type': 'off', // event signatures
'@typescript-eslint/restrict-template-expressions': ['error', {
allowNumber: true,
allowBoolean: true,
}],
// `void foo()` in arrow listeners is our idiom for intentional fire-and-forget
'no-void': 'off',
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
}],
},
},
// --- examples: demo code conforms to async interfaces without awaiting ---
{
files: ['examples/**/*.ts'],
rules: {
'@typescript-eslint/require-await': 'off',
},
},
// --- tests: same rules, minus the friction that fights test ergonomics --
{
files: ['packages/*/*/tests/**/*.ts', 'examples/*/tests/**/*.ts'],
extends: [
...tseslint.configs.strictTypeChecked,
],
languageOptions: {
parserOptions: {
// Same shared project service as the src block: test files resolve
// through the root tsconfig (its include covers every tests/ tree).
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-non-null-assertion': 'off', // assertions follow expect()s
'@typescript-eslint/no-unnecessary-condition': 'off',
'@typescript-eslint/require-await': 'off', // mock execute() signatures
'@typescript-eslint/no-empty-function': 'off', // stub agents
'@typescript-eslint/only-throw-error': 'off', // testing non-Error throws
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
}],
},
},
// --- formatting (everything we own) -------------------------------------
{
files: ['packages/**/*.ts', 'examples/**/*.ts', 'scripts/**/*.ts', 'eslint.config.mjs'],
plugins: { '@stylistic': stylistic },
rules: {
'@stylistic/indent': ['error', 2],
'@stylistic/semi': ['error', 'never'],
'@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
'@stylistic/comma-dangle': ['error', 'always-multiline'],
'@stylistic/eol-last': ['error', 'always'],
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/object-curly-spacing': ['error', 'always'],
'@stylistic/arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }],
'@stylistic/member-delimiter-style': ['error', {
multiline: { delimiter: 'none' },
singleline: { delimiter: 'semi', requireLast: false },
}],
'@stylistic/max-len': ['error', { code: 140, ignoreUrls: true, ignoreStrings: true, ignoreTemplateLiterals: true }],
},
},
)