{"_id":"@boring-stack-pkg/eslint-plugin-module-boundaries","_rev":"3-f707dbce236bc3245a7be92431e36c64","name":"@boring-stack-pkg/eslint-plugin-module-boundaries","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.1":{"name":"@boring-stack-pkg/eslint-plugin-module-boundaries","version":"0.1.1","keywords":["eslint","eslintplugin","typescript","architecture","module-boundaries","semantic-cohesion"],"author":"","license":"MIT","_id":"@boring-stack-pkg/eslint-plugin-module-boundaries@0.1.1","maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"homepage":"https://github.com/AI-Starter-Templates/eslint-plugins#readme","bugs":{"url":"https://github.com/AI-Starter-Templates/eslint-plugins/issues"},"dist":{"shasum":"22f68483cdca33b1d2173f81f1550e508133229a","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-module-boundaries/-/eslint-plugin-module-boundaries-0.1.1.tgz","fileCount":18,"integrity":"sha512-FUSEI5c9sESwGTjF8MrNKdh4kBTfmwUgEJhMl8tVB0MdXsSzn5O7q9YImsh79igzBaIL5WMt31SCQv43t/K6+g==","signatures":[{"sig":"MEYCIQDgrfc5rSmXMEf3Pz02kPoT+/SpmTmdJJiSQriAxTmLRgIhAMOzvsWAIH8e2yjYiMOSTVfY5EKm1fiEHIAZfNDpsw9d","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@boring-stack-pkg%2feslint-plugin-module-boundaries@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1307875},"main":"./dist/index.cjs","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-module-boundaries-0.1.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"agjs","email":"hi@aleksandar.xyz"},"_resolved":"/tmp/01141f5e71f76b941bacb3d4f0a55bcf/boring-stack-pkg-eslint-plugin-module-boundaries-0.1.1.tgz","_integrity":"sha512-FUSEI5c9sESwGTjF8MrNKdh4kBTfmwUgEJhMl8tVB0MdXsSzn5O7q9YImsh79igzBaIL5WMt31SCQv43t/K6+g==","repository":{"url":"git+https://github.com/AI-Starter-Templates/eslint-plugins.git","type":"git","directory":"eslint-plugin-module-boundaries"},"_npmVersion":"10.9.7","description":"ESLint plugin that enforces one semantic top-level concern per TypeScript module.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.2","dependencies":{"@typescript-eslint/utils":"8.0.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.0.0","eslint":"9.0.0","vitest":"2.0.0","@eslint/js":"9.0.0","typescript":"6.0.3","@types/node":"22.0.0","@typescript-eslint/parser":"8.0.0","@typescript-eslint/rule-tester":"8.0.0"},"peerDependencies":{"eslint":"8.57.0 || ^9.0.0","typescript":">=5.0.0","@typescript-eslint/parser":"8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-module-boundaries_0.1.1_1779219598565_0.40334145702346524","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@boring-stack-pkg/eslint-plugin-module-boundaries","version":"0.1.2","keywords":["eslint","eslintplugin","typescript","architecture","module-boundaries","semantic-cohesion"],"author":"","license":"MIT","_id":"@boring-stack-pkg/eslint-plugin-module-boundaries@0.1.2","maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"homepage":"https://github.com/boringstack-xyz/eslint-plugins#readme","bugs":{"url":"https://github.com/boringstack-xyz/eslint-plugins/issues"},"dist":{"shasum":"1da531c265ca6ffb34d01148541adf786da9c2b2","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-module-boundaries/-/eslint-plugin-module-boundaries-0.1.2.tgz","fileCount":18,"integrity":"sha512-ReaOUjLiZ6lKFP9sLJVJ/lRsi/8hznT6k6VXSCuGPreJNeZhMaoJvMNmhuYItqIIPrYA5NvKiC8te58jjGc3gg==","signatures":[{"sig":"MEUCIQCWrYEJawq+tB5XT5X2LQqJb2HMAGmJbFhRDh5Ze1AoIQIgMcAgS3Q8Bj7xrd1iu/GBozJ/Flaz9czWmK3Ln5qV6YI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1308277},"main":"./dist/index.cjs","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-module-boundaries-0.1.2.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"agjs","email":"hi@aleksandar.xyz"},"_resolved":"/tmp/ed3e4a1374dcaded5e6afede87e29596/boring-stack-pkg-eslint-plugin-module-boundaries-0.1.2.tgz","_integrity":"sha512-ReaOUjLiZ6lKFP9sLJVJ/lRsi/8hznT6k6VXSCuGPreJNeZhMaoJvMNmhuYItqIIPrYA5NvKiC8te58jjGc3gg==","repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-module-boundaries"},"_npmVersion":"11.15.0","description":"ESLint plugin that enforces one semantic top-level concern per TypeScript module.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"@typescript-eslint/utils":"8.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.0.0","eslint":"9.0.0","vitest":"2.0.0","@eslint/js":"9.0.0","typescript":"6.0.3","@types/node":"22.0.0","@typescript-eslint/parser":"8.0.0","@typescript-eslint/rule-tester":"8.0.0"},"peerDependencies":{"eslint":"8.57.0 || ^9.0.0","typescript":">=5.0.0","@typescript-eslint/parser":"8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-module-boundaries_0.1.2_1779695634611_0.6582663291293531","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"_id":"@boring-stack-pkg/eslint-plugin-module-boundaries@0.2.0","bugs":{"url":"https://github.com/boringstack-xyz/eslint-plugins/issues"},"dist":{"shasum":"6132900433c286683dddafc45d0ba5eb393a8c73","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-module-boundaries/-/eslint-plugin-module-boundaries-0.2.0.tgz","fileCount":18,"integrity":"sha512-fn13/5eGIs0RFyBIW2j2sopi2XXDYtyCzy3EwGaXQZFeJUt2caf9QKRTcJ5cHTm3lDJ36JofAhKVRLnOywUllA==","signatures":[{"sig":"MEUCIB3er7H/t0EqugOi0qIVZFP2n//gwtUDiuL1RZsn7F5oAiEAtQKoFdEAwiS9lZfmvA+Y36magYHVtHa3jMn12eodGww=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCaCANkTUvZA0BvmGCq5RVsmrsn859VNOjGG+9kXNaDzQIhAPLWY7iarll27058JVgu/6J0FGEp4EbctbM52OBlp5kI"}],"unpackedSize":1309827},"main":"./dist/index.cjs","name":"@boring-stack-pkg/eslint-plugin-module-boundaries","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-module-boundaries-0.2.0.tgz","types":"./dist/index.d.ts","author":"","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"license":"MIT","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest"},"version":"0.2.0","_npmUser":{"name":"agjs","email":"hi@aleksandar.xyz"},"homepage":"https://github.com/boringstack-xyz/eslint-plugins#readme","keywords":["eslint","eslintplugin","typescript","architecture","module-boundaries","semantic-cohesion"],"_resolved":"/tmp/c69c0543b5ac6b2195219e7110659870/boring-stack-pkg-eslint-plugin-module-boundaries-0.2.0.tgz","_integrity":"sha512-fn13/5eGIs0RFyBIW2j2sopi2XXDYtyCzy3EwGaXQZFeJUt2caf9QKRTcJ5cHTm3lDJ36JofAhKVRLnOywUllA==","repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-module-boundaries"},"_npmVersion":"11.19.1","description":"ESLint plugin that enforces one semantic top-level concern per TypeScript module.","directories":{},"maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"sideEffects":false,"_nodeVersion":"22.23.2","dependencies":{"@typescript-eslint/utils":"8.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.0.0","eslint":"9.0.0","vitest":"2.0.0","@eslint/js":"9.0.0","typescript":"6.0.3","@types/node":"22.0.0","@typescript-eslint/parser":"8.0.0","@typescript-eslint/rule-tester":"8.0.0"},"peerDependencies":{"eslint":"8.57.0 || ^9.0.0","typescript":">=5.0.0","@typescript-eslint/parser":"8.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/eslint-plugin-module-boundaries_0.2.0_1789412313614_0.5451732822943032"}}},"time":{"created":"2026-05-19T19:39:58.436Z","modified":"2026-09-14T18:58:33.937Z","0.1.1":"2026-05-19T19:39:58.782Z","0.1.2":"2026-05-25T07:53:54.757Z","0.2.0":"2026-09-14T18:58:33.749Z"},"bugs":{"url":"https://github.com/boringstack-xyz/eslint-plugins/issues"},"license":"MIT","homepage":"https://github.com/boringstack-xyz/eslint-plugins#readme","keywords":["eslint","eslintplugin","typescript","architecture","module-boundaries","semantic-cohesion"],"repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-module-boundaries"},"description":"ESLint plugin that enforces one semantic top-level concern per TypeScript module.","maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"readme":"# eslint-plugin-module-boundaries\n\n![Banner: eslint-plugin-module-boundaries — one semantic concern per TypeScript module](./docs/readme-banner.png)\n\n[![npm](https://img.shields.io/npm/v/@boring-stack-pkg/eslint-plugin-module-boundaries?logo=npm)](https://www.npmjs.com/package/@boring-stack-pkg/eslint-plugin-module-boundaries) [![source](https://img.shields.io/badge/source-github-blue?logo=github)](https://github.com/boringstack-xyz/eslint-plugins/tree/main/eslint-plugin-module-boundaries)\n\nProduction-oriented ESLint rules for deterministic TypeScript module cohesion.\n\nThe main rule, `module-boundaries/single-semantic-module`, enforces one semantic top-level concern per file. It analyzes TypeScript and TSX AST nodes, not filenames, so a mixed module fails regardless of whether it is named `types.ts`, `utils.ts`, `anything.ts`, or something else.\n\n## Why\n\nAI-generated codebases often accumulate unrelated declarations in whichever file is already open: types beside constants, schemas beside functions, hooks beside components. This plugin makes that architectural drift fail during linting.\n\n## Install\n\n```sh\nnpm install --save-dev @boring-stack-pkg/eslint-plugin-module-boundaries @typescript-eslint/parser\n```\n\n## Flat config\n\n```js\nimport tsParser from \"@typescript-eslint/parser\";\nimport moduleBoundaries from \"@boring-stack-pkg/eslint-plugin-module-boundaries\";\n\nexport default [\n  {\n    files: [\"**/*.{ts,tsx}\"],\n    languageOptions: {\n      parser: tsParser,\n      parserOptions: {\n        ecmaVersion: \"latest\",\n        sourceType: \"module\",\n        ecmaFeatures: { jsx: true },\n      },\n    },\n    plugins: {\n      \"module-boundaries\": moduleBoundaries,\n    },\n    rules: {\n      \"module-boundaries/single-semantic-module\": \"error\",\n    },\n  },\n];\n```\n\nYou can also use the built-in flat config:\n\n```js\nimport moduleBoundaries from \"@boring-stack-pkg/eslint-plugin-module-boundaries\";\n\nexport default [moduleBoundaries.configs.recommended];\n```\n\n## Rule behavior\n\nThe rule walks top-level module declarations and classifies each declaration into one semantic category. Imports, re-exports, export lists without declarations, type-only imports, type-only exports, comments, empty statements, and nested declarations are ignored.\n\nDefault categories:\n\n| Category          | Examples                                                                         |\n| ----------------- | -------------------------------------------------------------------------------- |\n| `type`            | `interface`, `type`, ambient declarations, TypeScript namespaces                 |\n| `constant`        | top-level runtime values, literals, objects, arrays, templates, computed values  |\n| `function`        | function declarations, function expressions, arrow functions                     |\n| `class`           | class declarations and class expressions                                         |\n| `react-component` | PascalCase functions or variables returning JSX, `React.FC`, `FunctionComponent` |\n| `hook`            | functions/variables matching `^use[A-Z0-9].*`                                    |\n| `schema`          | zod, yup, and valibot schema builder expressions                                 |\n| `enum`            | TypeScript enum declarations by default                                          |\n\nIf more than one category is detected, the rule reports:\n\n```txt\nMixed semantic categories detected in module:\n- type\n- constant\n\nA module must contain only one semantic concern.\nMove declarations into separate files/modules.\n```\n\n## Configuration\n\n```js\n{\n  rules: {\n    \"module-boundaries/single-semantic-module\": [\n      \"error\",\n      {\n        allow: [\n          [\"type\", \"schema\"],\n          [\"react-component\", \"hook\"]\n        ],\n        enumCategory: \"enum\",\n        debug: false,\n        ignoreAmbientDeclarations: false,\n        schemaLibraries: [\"zod\", \"yup\", \"valibot\"],\n        reactComponentDetection: {\n          enabled: true\n        },\n        hookDetection: {\n          enabled: true,\n          namePattern: \"^use[A-Z0-9].*\"\n        }\n      }\n    ]\n  }\n}\n```\n\n### `allow`\n\nAllows specific combinations of semantic categories. For example, `[\"type\", \"schema\"]` permits modules containing both types and schemas, while all other mixed combinations still fail.\n\n### `enumCategory`\n\nControls enum classification:\n\n- `\"enum\"`: enums are their own runtime category.\n- `\"type\"`: enums are allowed in type-only modules.\n\n### `debug`\n\nIncludes declaration names and classification reasons in the lint error.\n\n### `ignorePrivateDeclarations`\n\n`false` by default. When `true`, only exported declarations are classified: a non-exported config object, render helper or private class next to the hook or component that uses it does not give the module a second category. Two exported categories still conflict, and an exported constant is still a constant.\n\n### `ignoreAmbientDeclarations`\n\nWhen `true`, ambient declarations such as `declare global` are ignored instead of classified as `type`.\n\n## Examples\n\nValid:\n\n```ts\nexport interface User {}\nexport type UserId = string;\n```\n\n```ts\nexport const USER_ROLE_ADMIN = \"admin\";\nexport const USER_ROLE_USER = \"user\";\n```\n\n```ts\nexport function createUser() {}\nexport function deleteUser() {}\n```\n\nInvalid:\n\n```ts\nexport interface User {}\nexport const DEFAULT_USER = {};\n```\n\n```ts\nexport const USER_LIMIT = 5;\nexport function validateUser() {}\n```\n\n```ts\nexport class UserService {}\nexport function createUser() {}\n```\n\n## Development\n\n```sh\nnpm install\nnpm test\nnpm run typecheck\nnpm run build\n```\n","readmeFilename":"README.md"}