agent-ecosystem/eslint.config.js

1048 lines
32 KiB
JavaScript

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,
]);