import { defineConfig, globalIgnores } from 'eslint/config'; import js from '@eslint/js'; import tseslint from 'typescript-eslint'; import reactPlugin from 'eslint-plugin-react'; import reactHooks from 'eslint-plugin-react-hooks'; import reactRefresh from 'eslint-plugin-react-refresh'; import jsxA11y from 'eslint-plugin-jsx-a11y'; import tailwindcss from 'eslint-plugin-tailwindcss'; import sonarjs from 'eslint-plugin-sonarjs'; import simpleImportSort from 'eslint-plugin-simple-import-sort'; import importPlugin from 'eslint-plugin-import'; import security from 'eslint-plugin-security'; import boundaries from 'eslint-plugin-boundaries'; import eslintComments from '@eslint-community/eslint-plugin-eslint-comments'; import eslintConfigPrettier from 'eslint-config-prettier/flat'; import globals from 'globals'; export default defineConfig([ // Global ignores globalIgnores([ 'dist/**', 'dist-electron/**', 'build/**', 'node_modules/**', '*.config.js', '*.config.cjs', '*.config.ts', 'out/**', ]), // Base ESLint recommended rules js.configs.recommended, // TypeScript-ESLint recommended with type checking + stylistic // Using recommended (not strict) for a balanced approach ...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.stylisticTypeChecked, // SonarJS - Code quality and bug detection rules sonarjs.configs.recommended, // Security - Catch common security mistakes in AI-generated code security.configs.recommended, // TypeScript parser options for type-aware linting { name: 'typescript-parser-options', languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname, }, }, }, // Import plugin configuration - Main/Preload (uses tsconfig.node.json) { name: 'import-plugin-main', files: ['src/main/**/*.ts', 'src/preload/**/*.ts'], plugins: { import: importPlugin, }, settings: { 'import/resolver': { typescript: { alwaysTryTypes: true, project: './tsconfig.node.json', }, }, }, rules: { 'import/no-cycle': ['error', { maxDepth: 3, ignoreExternal: true }], 'import/no-unresolved': 'error', 'import/no-default-export': 'warn', }, }, // Import plugin configuration - Renderer (uses tsconfig.json) { name: 'import-plugin-renderer', files: ['src/renderer/**/*.{ts,tsx}', 'src/features/**/*.{ts,tsx}'], plugins: { import: importPlugin, }, settings: { 'import/resolver': { typescript: { alwaysTryTypes: true, project: './tsconfig.json', }, }, }, rules: { 'import/no-cycle': ['error', { maxDepth: 3, ignoreExternal: true }], 'import/no-unresolved': 'error', 'import/no-default-export': 'warn', }, }, // Feature-specific architecture guard rails - recent-projects { name: 'feature-recent-projects-public-entrypoints', files: ['src/**/*.{ts,tsx}'], ignores: ['src/features/recent-projects/**/*'], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: [ '@features/recent-projects/contracts/**', '@features/recent-projects/core/**', '@features/recent-projects/main/**', '@features/recent-projects/preload/**', '@features/recent-projects/renderer/**', ], message: 'Import recent-projects only through its public entrypoints: @features/recent-projects/contracts, @features/recent-projects/main, @features/recent-projects/preload, or @features/recent-projects/renderer.', }, ], }, ], }, }, { name: 'feature-recent-projects-core-domain-guards', files: ['src/features/recent-projects/core/domain/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: [ '@features/recent-projects/core/application/**', '@features/recent-projects/main/**', '@features/recent-projects/preload/**', '@features/recent-projects/renderer/**', '@main/**', '@renderer/**', '@preload/**', 'electron', 'fastify', 'child_process', 'node:child_process', ], message: 'recent-projects core/domain must stay side-effect free and cannot depend on application, adapters, infrastructure, or platform code.', }, ], }, ], }, }, { name: 'feature-recent-projects-core-application-guards', files: ['src/features/recent-projects/core/application/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: [ '@features/recent-projects/main/**', '@features/recent-projects/preload/**', '@features/recent-projects/renderer/**', '@renderer/**', 'electron', 'fastify', 'child_process', 'node:child_process', ], message: 'recent-projects core/application may depend only on domain, contracts, and application ports - not on adapters or runtime frameworks.', }, ], }, ], }, }, { name: 'feature-recent-projects-preload-guards', files: ['src/features/recent-projects/preload/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: [ '@features/recent-projects/main/**', '@main/**', '@renderer/**', ], message: 'recent-projects preload may depend only on contracts and preload-local bridge helpers.', }, ], }, ], }, }, { name: 'feature-recent-projects-renderer-ui-guards', files: ['src/features/recent-projects/renderer/ui/**/*.tsx'], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: [ '@renderer/api', '@renderer/api/**', '@renderer/store', '@renderer/store/**', '@main/**', 'electron', ], message: 'recent-projects renderer/ui must stay presentational. Move transport, store access, and navigation logic into hooks or adapters.', }, ], }, ], }, }, { name: 'feature-agent-graph-public-entrypoints', files: ['src/**/*.{ts,tsx}'], ignores: ['src/features/agent-graph/**/*'], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: [ '@features/agent-graph/core/**', '@features/agent-graph/renderer/**', ], message: 'Import agent-graph only through its public entrypoint: @features/agent-graph/renderer.', }, ], }, ], }, }, { name: 'feature-agent-graph-core-domain-guards', files: ['src/features/agent-graph/core/domain/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: [ '@features/agent-graph/renderer/**', '@main/**', '@renderer/**', '@preload/**', 'electron', 'fastify', 'child_process', 'node:child_process', ], message: 'agent-graph core/domain must stay pure and cannot depend on renderer, main, preload, or platform code.', }, ], }, ], }, }, { name: 'feature-agent-graph-renderer-boundaries', files: ['src/features/agent-graph/renderer/**/*.{ts,tsx}'], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: [ '@main/**', '@preload/**', 'electron', ], message: 'agent-graph renderer may depend on shared, renderer, package, and feature-local modules, but not on main/preload or Electron APIs directly.', }, ], }, ], }, }, // Import plugin configuration - Feature main/preload slices { name: 'import-plugin-features-node', files: ['src/features/**/main/**/*.ts', 'src/features/**/preload/**/*.ts'], plugins: { import: importPlugin, }, settings: { 'import/resolver': { typescript: { alwaysTryTypes: true, project: ['./tsconfig.node.json', './tsconfig.json'], }, }, }, rules: { 'import/no-cycle': ['error', { maxDepth: 3, ignoreExternal: true }], 'import/no-unresolved': 'error', 'import/no-default-export': 'warn', }, }, // Import plugin configuration - Feature contracts/core/renderer slices { name: 'import-plugin-features-web', files: [ 'src/features/**/contracts/**/*.ts', 'src/features/**/core/**/*.ts', 'src/features/**/renderer/**/*.{ts,tsx}', ], plugins: { import: importPlugin, }, settings: { 'import/resolver': { typescript: { alwaysTryTypes: true, project: ['./tsconfig.json', './tsconfig.node.json'], }, }, }, rules: { 'import/no-cycle': ['error', { maxDepth: 3, ignoreExternal: true }], 'import/no-unresolved': 'error', 'import/no-default-export': 'warn', }, }, // Module boundaries - Enforce Electron three-process architecture { name: 'module-boundaries', files: ['src/**/*.{js,jsx,ts,tsx}'], plugins: { boundaries: boundaries, }, settings: { 'boundaries/elements': [ { type: 'main', pattern: 'src/main/**', mode: 'folder' }, { type: 'preload', pattern: 'src/preload/**', mode: 'folder' }, { type: 'renderer', pattern: 'src/renderer/**', mode: 'folder' }, { type: 'shared', pattern: 'src/shared/**', mode: 'folder' }, ], 'boundaries/ignore': ['**/*.test.ts', '**/*.spec.ts'], }, rules: { // Enforce strict module boundaries for Electron architecture 'boundaries/element-types': [ 'warn', { default: 'disallow', rules: [ // Renderer can only import from renderer and shared { from: 'renderer', allow: ['renderer', 'shared'] }, // Main process can only import from main and shared { from: 'main', allow: ['main', 'shared'] }, // Preload can only import from preload and shared { from: 'preload', allow: ['preload', 'shared'] }, // Shared can import from shared and main (for type re-exports) { from: 'shared', allow: ['shared', 'main'] }, ], }, ], // Prevent importing private modules 'boundaries/no-private': 'error', }, }, // ESLint Comments { name: 'eslint-comments', files: ['src/**/*.{js,jsx,ts,tsx}'], plugins: { '@eslint-community/eslint-comments': eslintComments, }, rules: { // Prevents blanket-disabling rules '@eslint-community/eslint-comments/no-unlimited-disable': 'error', // Require description for disable comments '@eslint-community/eslint-comments/require-description': [ 'error', { ignore: [] }, ], // Re-enable rules after disabling '@eslint-community/eslint-comments/disable-enable-pair': 'error', // No duplicate disable comments '@eslint-community/eslint-comments/no-duplicate-disable': 'error', // Unused disable comments '@eslint-community/eslint-comments/no-unused-disable': 'error', }, }, // Import sorting for all JS/TS files { name: 'import-sorting', files: ['src/**/*.{js,jsx,ts,tsx}'], plugins: { 'simple-import-sort': simpleImportSort, }, rules: { 'simple-import-sort/imports': [ 'error', { groups: [ // Side effect imports (e.g., import './styles.css') ['^\\u0000'], // Node.js builtins (fs, path, etc.) ['^node:'], // React and related packages ['^react', '^react-dom'], // External packages from node_modules ['^@?\\w'], // Internal aliases (@/ paths) ['^@/'], // Parent imports (../) ['^\\.\\.(?!/?$)', '^\\.\\./?$'], // Same-folder imports (./) ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'], // Type imports ['^.+\\u0000$'], ], }, ], 'simple-import-sort/exports': 'error', }, }, // Main process (Electron Node.js) { name: 'electron-main', files: ['src/main/**/*.ts'], languageOptions: { globals: { ...globals.node, }, }, rules: { // Allow console in main process for logging 'no-console': 'off', }, }, { name: 'electron-main-safe-renderer-send-guard', files: ['src/main/**/*.ts'], ignores: ['src/main/utils/safeWebContentsSend.ts'], rules: { 'no-restricted-syntax': [ 'error', { selector: "CallExpression[callee.type='MemberExpression'][callee.property.name='send'][callee.object.type='MemberExpression'][callee.object.property.name='webContents']", message: 'Use safeSendToRenderer(...) instead of direct webContents.send(...) in the main process.', }, { selector: "CallExpression[callee.type='MemberExpression'][callee.property.name='send'][callee.object.type='MemberExpression'][callee.object.property.name='sender']", message: 'Use safeSendToRenderer(BrowserWindow.fromWebContents(event.sender), ...) instead of direct event.sender.send(...) in the main process.', }, { selector: "CallExpression[callee.type='MemberExpression'][callee.property.name='send'][callee.object.name='contents']", message: 'Use safeSendToRenderer(...) instead of aliasing webContents and calling contents.send(...) in the main process.', }, ], }, }, { name: 'team-transcript-project-resolver-sonar-override', files: ['src/main/services/team/TeamTranscriptProjectResolver.ts'], rules: { 'sonarjs/no-identical-functions': 'off', }, }, // Preload script (Electron bridge) { name: 'electron-preload', files: ['src/preload/**/*.ts'], languageOptions: { globals: { ...globals.node, ...globals.browser, }, }, }, // Renderer process (React + A11y + Tailwind) { name: 'renderer-react', files: ['src/renderer/**/*.{ts,tsx}'], languageOptions: { globals: { ...globals.browser, }, }, plugins: { react: reactPlugin, 'react-hooks': reactHooks, 'react-refresh': reactRefresh, 'jsx-a11y': jsxA11y, tailwindcss: tailwindcss, }, settings: { react: { version: 'detect', }, tailwindcss: { // Tailwind config path (relative to cwd) config: 'tailwind.config.js', // Allow custom classnames (e.g., from CSS modules) callees: ['classnames', 'clsx', 'cn'], }, }, rules: { // React recommended rules ...reactPlugin.configs.recommended.rules, // JSX runtime (React 17+) - no need to import React ...reactPlugin.configs['jsx-runtime'].rules, // React Hooks rules ...reactHooks.configs.recommended.rules, // Accessibility rules (recommended) ...jsxA11y.configs.recommended.rules, // Tailwind CSS rules ...tailwindcss.configs.recommended.rules, // React Refresh for HMR 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], // Disable prop-types since we use TypeScript 'react/prop-types': 'off', // A11y rule adjustments for this project // Allow click handlers on divs when keyboard handlers also present 'jsx-a11y/click-events-have-key-events': 'warn', 'jsx-a11y/no-static-element-interactions': 'warn', 'jsx-a11y/label-has-associated-control': 'warn', 'jsx-a11y/no-noninteractive-tabindex': 'warn', // Allow autofocus for search inputs in desktop apps 'jsx-a11y/no-autofocus': 'off', // Tailwind CSS rule adjustments // Warn on class order (Prettier plugin handles sorting) 'tailwindcss/classnames-order': 'off', // Prettier plugin handles this // Warn on conflicting classes 'tailwindcss/no-contradicting-classname': 'error', // Warn on custom classnames that don't exist 'tailwindcss/no-custom-classname': 'warn', // === React-Specific Rules === // Consistent component definition 'react/function-component-definition': [ 'error', { namedComponents: 'arrow-function', unnamedComponents: 'arrow-function', }, ], // Strengthen exhaustive-deps 'react-hooks/exhaustive-deps': 'warn', // Conditional hooks — warn instead of error for gradual fix 'react-hooks/rules-of-hooks': 'warn', // React Compiler rules — downgraded to warn for existing code 'react-hooks/refs': 'warn', 'react-hooks/set-state-in-effect': 'warn', 'react-hooks/preserve-manual-memoization': 'warn', 'react-hooks/immutability': 'warn', // Prevent prop spreading 'react/jsx-props-no-spreading': [ 'warn', { exceptions: ['input', 'button', 'Input', 'Button', 'textarea', 'select'], }, ], // Ensure key props 'react/jsx-key': [ 'error', { checkFragmentShorthand: true, checkKeyMustBeforeSpread: true, }, ], // Prevent unnecessary fragments 'react/jsx-no-useless-fragment': 'warn', // Self-closing components for consistency 'react/self-closing-comp': [ 'error', { component: true, html: true, }, ], }, }, // Test files { name: 'test-files', files: ['test/**/*.ts', '**/*.test.ts', '**/*.spec.ts'], languageOptions: { globals: { ...globals.node, }, parserOptions: { projectService: false, project: './tsconfig.json', }, }, rules: { // Relax TypeScript strict rules for tests '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-unsafe-argument': 'off', '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/unbound-method': 'off', // Relax function/export rules for tests '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', // Relax naming conventions for tests (allow describe, it, expect patterns) '@typescript-eslint/naming-convention': 'off', // Allow magic numbers in tests 'sonarjs/no-hardcoded-ip': 'off', // Allow floating promises in tests (common with async test helpers) '@typescript-eslint/no-floating-promises': 'off', }, }, // Custom rule overrides for all TypeScript files { name: 'custom-rules', files: ['src/**/*.{ts,tsx}'], rules: { // === Core JavaScript rules === 'prefer-const': 'error', 'no-var': 'error', eqeqeq: ['error', 'always', { null: 'ignore' }], // === TypeScript Import/Export rules === '@typescript-eslint/consistent-type-imports': [ 'error', { prefer: 'type-imports', fixStyle: 'inline-type-imports', }, ], '@typescript-eslint/consistent-type-exports': [ 'error', { fixMixedExportsWithInlineTypeSpecifier: true }, ], // === Unused variables === '@typescript-eslint/no-unused-vars': [ 'warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', }, ], // === Relaxed strict rules for practical use === // Allow empty functions (useful for callbacks and stubs) '@typescript-eslint/no-empty-function': 'off', // Allow numbers/booleans in template literals (common pattern) '@typescript-eslint/restrict-template-expressions': [ 'error', { allowNumber: true, allowBoolean: true, allowNullish: false, }, ], // Allow async functions without await (IPC handlers often need this) '@typescript-eslint/require-await': 'off', // Allow floating promises in event handlers (common in Electron) '@typescript-eslint/no-floating-promises': [ 'error', { ignoreVoid: true, ignoreIIFE: true, }, ], // Allow promises in places that don't expect them (event handlers) '@typescript-eslint/no-misused-promises': [ 'error', { checksVoidReturn: { attributes: false, arguments: false, }, }, ], // Allow void expression in arrow functions shorthand '@typescript-eslint/no-confusing-void-expression': [ 'error', { ignoreArrowShorthand: true, ignoreVoidOperator: true, }, ], // Prefer nullish coalescing but don't error on logical or '@typescript-eslint/prefer-nullish-coalescing': 'off', // Allow inferrable types (style preference) '@typescript-eslint/no-inferrable-types': 'off', // === Anti-Hallucination Rules === // Explicit return types '@typescript-eslint/explicit-function-return-type': [ 'warn', { allowExpressions: true, allowTypedFunctionExpressions: true, allowHigherOrderFunctions: true, allowDirectConstAssertionInArrowFunctions: true, }, ], // Explicit types for exported functions (minimum requirement) '@typescript-eslint/explicit-module-boundary-types': 'warn', // Prevent variable shadowing '@typescript-eslint/no-shadow': 'error', // === Naming Conventions === '@typescript-eslint/naming-convention': [ 'warn', // Imports can be camelCase or PascalCase (React, ReactDOM, App, etc.) { selector: 'import', format: ['camelCase', 'PascalCase'], }, // Default: variables and parameters in camelCase { selector: 'default', format: ['camelCase'], leadingUnderscore: 'allow', }, // Static readonly class properties can be UPPER_CASE { selector: 'classProperty', modifiers: ['static', 'readonly'], format: ['camelCase', 'UPPER_CASE'], }, // Variables: camelCase or UPPER_CASE for constants { selector: 'variable', format: ['camelCase', 'UPPER_CASE', 'PascalCase'], leadingUnderscore: 'allow', }, // Functions: camelCase (includes type guards like isXxx, builders like buildXxx) { selector: 'function', format: ['camelCase', 'PascalCase'], }, // Parameters: camelCase, allow leading underscore for unused { selector: 'parameter', format: ['camelCase'], leadingUnderscore: 'allow', }, // Types and interfaces in PascalCase { selector: 'typeLike', format: ['PascalCase'], }, // Interfaces should NOT start with I (modern convention) { selector: 'interface', format: ['PascalCase'], custom: { regex: '^I[A-Z]', match: false }, }, // Enum members in PascalCase or UPPER_CASE { selector: 'enumMember', format: ['PascalCase', 'UPPER_CASE'], }, // Object literal properties: allow any format (for API compatibility) { selector: 'objectLiteralProperty', format: null, }, // Type properties: allow any format (for type definitions matching APIs) { selector: 'typeProperty', format: null, }, ], // === Import Restrictions === // Note: boundaries/element-types handles main/renderer separation 'no-restricted-imports': 'off', // === Mutation Prevention === 'no-param-reassign': 'warn', // === SonarJS rule adjustments === // Cognitive complexity - warn instead of error for gradual adoption 'sonarjs/cognitive-complexity': 'off', // Allow some duplication in similar but not identical code 'sonarjs/no-duplicate-string': 'off', // Relax for Electron IPC patterns (many similar switch cases) 'sonarjs/no-small-switch': 'off', // Allow nested ternaries in JSX (common React pattern) 'sonarjs/no-nested-conditional': 'off', // === Downgraded to warn — existing code, fix incrementally === 'sonarjs/slow-regex': 'warn', 'sonarjs/pseudo-random': 'warn', 'sonarjs/different-types-comparison': 'warn', 'sonarjs/deprecation': 'warn', 'sonarjs/no-dead-store': 'warn', 'sonarjs/unused-import': 'warn', 'sonarjs/no-unused-vars': 'warn', 'sonarjs/no-commented-code': 'warn', 'sonarjs/function-return-type': 'warn', 'sonarjs/use-type-alias': 'warn', 'sonarjs/no-nested-template-literals': 'warn', 'sonarjs/no-alphabetical-sort': 'warn', 'sonarjs/no-misleading-array-reverse': 'warn', 'sonarjs/no-os-command-from-path': 'warn', 'sonarjs/link-with-target-blank': 'warn', 'sonarjs/no-unused-collection': 'warn', 'sonarjs/todo-tag': 'warn', 'sonarjs/reduce-initial-value': 'warn', 'sonarjs/concise-regex': 'warn', 'sonarjs/void-use': 'warn', 'sonarjs/anchor-precedence': 'warn', 'sonarjs/no-control-regex': 'warn', 'sonarjs/no-nested-functions': 'warn', 'sonarjs/no-all-duplicated-branches': 'warn', '@typescript-eslint/no-shadow': 'warn', '@typescript-eslint/no-unsafe-member-access': 'warn', '@typescript-eslint/no-unsafe-call': 'warn', '@typescript-eslint/no-unsafe-assignment': 'warn', '@typescript-eslint/no-unsafe-return': 'warn', '@typescript-eslint/no-unsafe-argument': 'warn', '@typescript-eslint/restrict-template-expressions': 'warn', '@typescript-eslint/no-base-to-string': 'warn', '@typescript-eslint/no-redundant-type-constituents': 'warn', '@typescript-eslint/prefer-promise-reject-errors': 'warn', '@typescript-eslint/no-require-imports': 'warn', '@typescript-eslint/consistent-type-imports': 'warn', '@typescript-eslint/prefer-optional-chain': 'warn', '@typescript-eslint/no-floating-promises': 'warn', '@typescript-eslint/array-type': 'warn', 'no-useless-escape': 'warn', 'no-unsafe-finally': 'warn', 'no-control-regex': 'warn', '@eslint-community/eslint-comments/require-description': 'warn', '@typescript-eslint/unbound-method': 'warn', // === Security rule adjustments (Code Protection) === // These catch common security mistakes 'security/detect-eval-with-expression': 'error', // Disabled: This is a desktop file reader app - file system access is expected 'security/detect-non-literal-fs-filename': 'off', // Disabled: Dynamic patterns are intentional in this app 'security/detect-non-literal-regexp': 'off', // Disabled: Often false positives with typed code 'security/detect-object-injection': 'off', 'security/detect-child-process': 'warn', 'security/detect-non-literal-require': 'warn', 'security/detect-possible-timing-attacks': 'warn', }, }, { name: 'feature-public-entrypoints-only', files: [ 'src/main/**/*.{ts,tsx}', 'src/preload/**/*.{ts,tsx}', 'src/renderer/**/*.{ts,tsx}', 'src/shared/**/*.{ts,tsx}', ], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: [ '@features/*/contracts/*', '@features/*/core/**', '@features/*/main/*', '@features/*/preload/*', '@features/*/renderer/*', ], message: 'Import feature public entrypoints only.', }, ], }, ], }, }, { name: 'feature-core-domain-guards', files: ['src/features/*/core/domain/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { paths: [ { name: 'electron', message: 'core/domain must stay Electron-free.' }, { name: 'fastify', message: 'core/domain must stay transport-free.' }, { name: 'child_process', message: 'core/domain must stay side-effect free.' }, { name: 'node:child_process', message: 'core/domain must stay side-effect free.' }, ], patterns: [ { group: ['@main/*', '@preload/*', '@renderer/*'], message: 'core/domain must stay process-agnostic.', }, { group: ['@features/*/main/**', '@features/*/preload/**', '@features/*/renderer/**'], message: 'core/domain must not import runtime or transport layers.', }, ], }, ], }, }, { name: 'feature-core-application-guards', files: ['src/features/*/core/application/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { paths: [ { name: 'electron', message: 'core/application must stay Electron-free.' }, { name: 'fastify', message: 'core/application must stay transport-free.' }, { name: 'child_process', message: 'core/application must not spawn processes directly.' }, { name: 'node:child_process', message: 'core/application must not spawn processes directly.', }, ], patterns: [ { group: ['@main/*', '@preload/*', '@renderer/*'], message: 'core/application must stay framework-agnostic.', }, { group: ['@features/*/main/**', '@features/*/preload/**', '@features/*/renderer/**'], message: 'core/application must depend on ports, not runtime adapters.', }, ], }, ], }, }, { name: 'feature-preload-guards', files: ['src/features/*/preload/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: ['@main/*'], message: 'Feature preload should not import app-shell main modules.', }, { group: ['@features/*/main/**'], message: 'Feature preload must not reach into feature main internals.', }, ], }, ], }, }, { name: 'feature-renderer-ui-guards', files: ['src/features/*/renderer/ui/**/*.{ts,tsx}'], rules: { 'no-restricted-imports': [ 'error', { paths: [ { name: '@renderer/api', message: 'renderer/ui must stay presentational.' }, { name: '@renderer/store', message: 'renderer/ui must stay store-free.' }, { name: 'electron', message: 'renderer/ui must stay Electron-free.' }, ], patterns: [ { group: ['@main/*'], message: 'renderer/ui must not import main modules.' }, { group: ['@renderer/store/*'], message: 'renderer/ui must stay store-free.' }, ], }, ], }, }, // === IMPORTANT: eslint-config-prettier MUST be LAST === // This disables all ESLint rules that conflict with Prettier // Prettier handles formatting, ESLint handles code quality eslintConfigPrettier, ]);