import tsPlugin from '@typescript-eslint/eslint-plugin'; import tsParser from '@typescript-eslint/parser'; import unusedImports from 'eslint-plugin-unused-imports'; import reactHooks from 'eslint-plugin-react-hooks'; import prettierConfig from 'eslint-config-prettier'; import requireSafeParse from './eslint-rules/require-safe-parse.mjs'; // Local plugin hosting custom rules that enforce GitNexus-specific invariants // (currently: the Windows-SIGSEGV-safe parser entrypoint). const gitnexusLocalPlugin = { rules: { 'require-safe-parse': requireSafeParse, }, }; // Selectors that protect MCP-reachable code from corrupting the JSON-RPC // stdio frame stream. The MCP-reachable block below uses these directly; // the lbug-adapter file-specific block must spread them in too because // ESLint flat config REPLACES (not merges) `no-restricted-syntax` when // multiple matching configs target the same file. Extracting to a const // makes the dependency mechanical instead of documentation-enforced. const mcpStdoutWriteSelectors = [ { selector: "MemberExpression[object.type='MemberExpression'][object.object.name='process'][object.property.name='stdout'][property.name='write']", message: 'Direct process.stdout.write is forbidden in MCP-reachable code. Route diagnostics through console.error or process.stderr.write — the MCP stdio transport owns stdout for JSON-RPC frames.', }, { selector: "CallExpression[callee.type='MemberExpression'][callee.object.type='MemberExpression'][callee.object.object.name='process'][callee.object.property.name='stdout'][callee.property.name='write']", message: 'Direct process.stdout.write is forbidden in MCP-reachable code. Route diagnostics through console.error or process.stderr.write — the MCP stdio transport owns stdout for JSON-RPC frames.', }, { // Catches the canonical destructuring shape: // const { write } = process.stdout; // (and any other ObjectPattern destructure rooted at process.stdout) // which would otherwise capture a reference to the original write // and bypass the sentinel. selector: "VariableDeclarator[init.type='MemberExpression'][init.object.name='process'][init.property.name='stdout'] > ObjectPattern", message: 'Destructuring process.stdout is forbidden in MCP-reachable code — bypasses the sentinel. Use process.stderr.write for diagnostics.', }, ]; export default [ // Global ignores { ignores: [ '**/dist/**', '**/node_modules/**', '**/coverage/**', 'gitnexus/vendor/**', 'gitnexus-web/src/vendor/**', 'gitnexus/test/fixtures/**', 'gitnexus-web/test/fixtures/**', 'gitnexus-web/playwright-report/**', 'gitnexus-web/test-results/**', '**/*.d.ts', '.claude/**', '.history/**', ], }, // Base TypeScript config for all packages { files: ['**/*.{ts,tsx}'], languageOptions: { parser: tsParser, parserOptions: { ecmaVersion: 2022, sourceType: 'module', }, }, plugins: { '@typescript-eslint': tsPlugin, 'unused-imports': unusedImports, }, rules: { // Unused imports — auto-fixable 'unused-imports/no-unused-imports': 'error', 'unused-imports/no-unused-vars': [ 'warn', { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' }, ], // TypeScript quality '@typescript-eslint/no-unused-vars': 'off', // handled by unused-imports plugin 'no-unused-vars': 'off', // handled by unused-imports plugin '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-non-null-assertion': 'warn', // General quality 'no-debugger': 'error', 'prefer-const': 'error', 'no-var': 'error', eqeqeq: ['error', 'always', { null: 'ignore' }], }, }, // CLI/server packages — `console.log` IS the contract (CLI tool data output // on stdout, e.g. `gitnexus query | jq`; server pretty-printed banners). // Diagnostic logging (`warn`/`error`/`debug`/`info`) goes through pino like // the rest of the codebase. { files: ['gitnexus/src/cli/**/*.ts', 'gitnexus/src/server/**/*.ts'], rules: { 'no-console': ['error', { allow: ['log'] }], }, }, // Forcing function for the pino migration. Severity is `error` — the // codebase-wide migration is complete; new `console.*` in core source // must fail lint. CLI/server are exempt above (legitimate stdout output). // Tests, bin scripts, and the logger module itself remain exempt. { files: ['gitnexus/src/**/*.ts'], ignores: ['gitnexus/src/cli/**', 'gitnexus/src/server/**', 'gitnexus/src/core/logger.ts'], rules: { 'no-console': 'error', }, }, // MCP-reachable code: forbid stdout-corrupting writes. The MCP stdio // transport writes JSON-RPC frames to stdout; per the spec, the server // MUST NOT write anything to stdout that is not a valid MCP message. // Diagnostics must go to stderr (console.error). Direct process.stdout.write // bypasses the gate and is also forbidden in these dirs. // cli/mcp.ts is included here even though it lives under cli/ — it is the // MCP entrypoint and inherits stricter discipline than the rest of cli/. { files: [ 'gitnexus/src/mcp/**/*.ts', 'gitnexus/src/core/lbug/**/*.ts', 'gitnexus/src/core/embeddings/**/*.ts', 'gitnexus/src/core/tree-sitter/**/*.ts', 'gitnexus/src/cli/mcp.ts', ], rules: { 'no-console': ['error', { allow: ['error'] }], 'no-restricted-syntax': ['error', ...mcpStdoutWriteSelectors], }, }, // Windows SIGSEGV protection: every tree-sitter parse in `core/` must route // through parseSourceSafe. Direct `.parse(content, ...)` crashes on // Windows for inputs > 32 767 chars (V8 string-conversion bug, uncatchable // from JS). The rule auto-fixes the call site; the developer adds the // missing import after the fix runs. Out of scope: tests (skipped by the // rule), the helper itself (`safe-parse.ts`), and the `grpc-patterns/proto.ts` // grammar-load smoke test (filtered by string-literal-arg skip in the rule). { files: ['gitnexus/src/core/**/*.ts'], plugins: { gitnexus: gitnexusLocalPlugin, }, rules: { 'gitnexus/require-safe-parse': 'error', }, }, // React-specific rules for gitnexus-web { files: ['gitnexus-web/src/**/*.{ts,tsx}'], plugins: { 'react-hooks': reactHooks, }, rules: { 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', }, }, // Prevent direct conn.close() / db.close() in the LadybugDB adapter (#1376). // All close operations must go through safeClose() so the WAL is always // flushed before the connection is released. The sole authorised call site // inside safeClose itself uses an eslint-disable-next-line override. // // ESLint flat config REPLACES (not merges) `no-restricted-syntax` when // multiple matching configs target the same file. lbug-adapter.ts is also // covered by the MCP-reachable block above, so we spread the shared // mcpStdoutWriteSelectors here alongside the safeClose selectors. Without // this, lbug-adapter would silently lose its MCP stdout-write protection. { files: ['gitnexus/src/core/lbug/lbug-adapter.ts'], rules: { 'no-restricted-syntax': [ 'error', ...mcpStdoutWriteSelectors, { selector: "CallExpression[callee.object.name='conn'][callee.property.name='close']", message: 'Use safeClose() instead of calling conn.close() directly (#1376).', }, { selector: "CallExpression[callee.object.name='db'][callee.property.name='close']", message: 'Use safeClose() instead of calling db.close() directly (#1376).', }, ], }, }, // Disable formatting rules (prettier handles those) prettierConfig, ];