productive-eslint 3.3.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +35 -0
- package/README.md +94 -17
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +6 -0
- package/dist/index.config.d.ts +14 -20
- package/dist/index.config.js +1 -1
- package/dist/model-DGP73Lve.js +1 -0
- package/docs/README.md +45 -0
- package/docs/analyzer-runtime.md +105 -0
- package/docs/cli-diagnostics.md +106 -0
- package/docs/configuration.md +79 -0
- package/package.json +36 -31
- package/FIXES.md +0 -1039
package/AGENTS.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## Project overview
|
|
2
|
+
`productive-eslint` — opinionated ESLint flat config preset (npm package).
|
|
3
|
+
Inspired by eslint-config-hardcore. Targets ESLint 10+, TypeScript 5.9+, Node 24+.
|
|
4
|
+
|
|
5
|
+
## Tech stack
|
|
6
|
+
- Language: TypeScript (strict, `exactOptionalPropertyTypes`)
|
|
7
|
+
- Package manager: pnpm
|
|
8
|
+
- Bundler: tsdown
|
|
9
|
+
- Module system: ESM (`"type": "module"`)
|
|
10
|
+
- Entry point: `src/index.config.ts` → `dist/index.config.js`
|
|
11
|
+
- Supported consumer setup: TypeScript-only ESM codebases with `eslint.config.ts` or `eslint.config.mts`
|
|
12
|
+
|
|
13
|
+
## Project structure
|
|
14
|
+
- `src/index.config.ts` — main factory `createConfig(options)`, composes all rule configs
|
|
15
|
+
- `src/*.config.ts` — per-plugin rule configs (javascript, typescript, unicorn, vue, etc.)
|
|
16
|
+
- `src/utils/presets.ts` — `Preset` enum (`autoFixable`/`recommended`) and `mergePresetConfigs`
|
|
17
|
+
- `src/utils/globs.ts` — file glob constants
|
|
18
|
+
- `src/plugins/productive.plugin.ts` — custom ESLint plugin with custom rules
|
|
19
|
+
- `scripts/` — utility scripts (dump/compare ESLint rules), run via `jiti`
|
|
20
|
+
|
|
21
|
+
## Key patterns
|
|
22
|
+
- Each rule config file exports a `TPresetMap` — an object with `autoFixable` and `recommended` keys
|
|
23
|
+
- `mergePresetConfigs(map, preset)` merges `autoFixable` and, when requested, `recommended`
|
|
24
|
+
- Vue and RxJS support is auto-detected (via `local-pkg`) or explicitly enabled via options
|
|
25
|
+
- The config uses `FlatConfigComposer` from `eslint-flat-config-utils` for composability
|
|
26
|
+
|
|
27
|
+
## Scripts
|
|
28
|
+
- `pnpm lint` — typecheck (`tsc`) + lint with autofix (`eslint --fix`)
|
|
29
|
+
- `pnpm build` — build with tsdown (output to `dist/`)
|
|
30
|
+
- `pnpm release` — build + publish
|
|
31
|
+
- `pnpm inspect` — open ESLint config inspector
|
|
32
|
+
- `pnpm rules:dump` / `pnpm rules:compare` — utility scripts for rule management
|
|
33
|
+
|
|
34
|
+
## Linting
|
|
35
|
+
Repository-wide lint rules are intended to stay mechanical. Nuanced checks should move to focused on-demand diagnostics.
|
package/README.md
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
#
|
|
2
|
-
This
|
|
1
|
+
# An ESLint config for practical code analysis
|
|
2
|
+
This package provides an AI-friendly ESLint flat config for repository-wide mechanical checks.
|
|
3
3
|
It is heavily inspired by Evgeny Orekhov's [eslint-config-hardcore](https://github.com/EvgenyOrekhov/eslint-config-hardcore).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
The default preset is intentionally limited to rules that are safe as permanent lint noise: autofixable rules plus trivial
|
|
6
|
+
non-autofixable checks that do not require architecture, API, or product decisions.
|
|
7
|
+
|
|
8
|
+
`productive-eslint` targets modern TypeScript-only ESM codebases. Project configs must use `eslint.config.ts` or `eslint.config.mts`.
|
|
9
|
+
|
|
10
|
+
Documentation:
|
|
11
|
+
|
|
12
|
+
- [Configuration Model](./docs/configuration.md)
|
|
13
|
+
- [CLI Diagnostics](./docs/cli-diagnostics.md)
|
|
14
|
+
- [Analyzer Runtime](./docs/analyzer-runtime.md)
|
|
7
15
|
|
|
8
16
|
---
|
|
9
17
|
|
|
@@ -22,11 +30,11 @@ npm i -D productive-eslint eslint typescript prettier prettier-plugin-jsdoc
|
|
|
22
30
|
- TypeScript 5.9+ (required)
|
|
23
31
|
- Prettier 3.6+
|
|
24
32
|
|
|
25
|
-
2. Create eslint.config.ts in project root:
|
|
33
|
+
2. Create `eslint.config.ts` or `eslint.config.mts` in project root:
|
|
26
34
|
````typescript
|
|
27
|
-
import
|
|
35
|
+
import { createConfig } from 'productive-eslint'
|
|
28
36
|
|
|
29
|
-
export default
|
|
37
|
+
export default createConfig()
|
|
30
38
|
````
|
|
31
39
|
|
|
32
40
|
3. Add scripts to package.json:
|
|
@@ -41,17 +49,39 @@ npm i -D productive-eslint eslint typescript prettier prettier-plugin-jsdoc
|
|
|
41
49
|
|
|
42
50
|
### Options
|
|
43
51
|
|
|
44
|
-
`
|
|
52
|
+
`createConfig` accepts an options object:
|
|
45
53
|
|
|
46
54
|
| Option | Type | Default | Description |
|
|
47
55
|
|---|---|---|---|
|
|
48
|
-
| `
|
|
56
|
+
| `preset` | `Preset.AUTO_FIXABLE \| Preset.RECOMMENDED` | `Preset.RECOMMENDED` | Rule preset |
|
|
49
57
|
| `ignores` | `string[]` | `[]` | Additional glob patterns to ignore |
|
|
50
58
|
| `vue` | `boolean` | auto-detect | Enable Vue/Nuxt rules |
|
|
51
59
|
| `rxjs` | `boolean` | auto-detect | Enable RxJS rules |
|
|
52
60
|
|
|
53
61
|
By default, `vue` and `rxjs` are auto-detected based on installed packages.
|
|
54
62
|
|
|
63
|
+
### Presets
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { createConfig, Preset } from 'productive-eslint'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`Preset.AUTO_FIXABLE` enables only rules with reliable ESLint autofix support.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
export default createConfig({
|
|
73
|
+
preset: Preset.AUTO_FIXABLE,
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`Preset.RECOMMENDED` is the default permanent baseline. It includes `AUTO_FIXABLE` plus mechanical non-autofixable rules.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
export default createConfig({
|
|
81
|
+
preset: Preset.RECOMMENDED,
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
55
85
|
---
|
|
56
86
|
|
|
57
87
|
### Monorepo setup
|
|
@@ -59,9 +89,9 @@ By default, `vue` and `rxjs` are auto-detected based on installed packages.
|
|
|
59
89
|
When the ESLint config lives at the **workspace root**, auto-detection may not find packages installed only in sub-packages. In this case, enable framework configs explicitly:
|
|
60
90
|
|
|
61
91
|
```ts
|
|
62
|
-
import
|
|
92
|
+
import { createConfig } from 'productive-eslint'
|
|
63
93
|
|
|
64
|
-
export default
|
|
94
|
+
export default createConfig({
|
|
65
95
|
rxjs: true,
|
|
66
96
|
vue: true,
|
|
67
97
|
})
|
|
@@ -69,17 +99,64 @@ export default productiveEslint({
|
|
|
69
99
|
|
|
70
100
|
---
|
|
71
101
|
|
|
72
|
-
###
|
|
73
|
-
|
|
102
|
+
### On-Demand Code Review Diagnostics
|
|
103
|
+
|
|
104
|
+
Repository-wide ESLint is kept mechanical on purpose. Nuanced checks such as type-safety debt, architecture boundaries,
|
|
105
|
+
async correctness, migration tails, framework lifecycle risk, and complexity should be handled by focused diagnostics
|
|
106
|
+
instead of being enabled as permanent lint noise.
|
|
107
|
+
|
|
108
|
+
These diagnostics are intended for explicit review/audit requests, not as an always-on agent tool loop during ordinary
|
|
109
|
+
coding tasks.
|
|
110
|
+
|
|
111
|
+
Available CLI diagnostics:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
productive-eslint analyze types
|
|
115
|
+
productive-eslint analyze architecture
|
|
116
|
+
productive-eslint analyze complexity
|
|
117
|
+
productive-eslint analyze async
|
|
118
|
+
productive-eslint analyze suppressions
|
|
119
|
+
productive-eslint analyze dead-code
|
|
120
|
+
productive-eslint analyze imports
|
|
121
|
+
productive-eslint analyze api
|
|
122
|
+
productive-eslint analyze vue
|
|
123
|
+
productive-eslint analyze rxjs
|
|
124
|
+
productive-eslint analyze migrations
|
|
125
|
+
productive-eslint analyze risk
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Run one diagnostic at a time from an explicit project root:
|
|
74
129
|
|
|
75
|
-
|
|
130
|
+
```bash
|
|
131
|
+
productive-eslint analyze types --cwd /path/to/project
|
|
132
|
+
productive-eslint analyze complexity --cwd . --top 20
|
|
133
|
+
productive-eslint analyze suppressions --cwd . --include "src/**/*.ts" --exclude "**/*.test.ts"
|
|
134
|
+
```
|
|
76
135
|
|
|
136
|
+
Recommended first audit pass:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
productive-eslint analyze risk --cwd .
|
|
140
|
+
productive-eslint analyze types --cwd .
|
|
141
|
+
productive-eslint analyze suppressions --cwd .
|
|
142
|
+
productive-eslint analyze async --cwd .
|
|
143
|
+
productive-eslint analyze architecture --cwd .
|
|
144
|
+
productive-eslint analyze complexity --cwd . --top 20
|
|
77
145
|
```
|
|
78
|
-
|
|
79
|
-
|
|
146
|
+
|
|
147
|
+
Use framework-specific diagnostics only when the target project enables the
|
|
148
|
+
matching preset support:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
productive-eslint analyze vue --cwd .
|
|
152
|
+
productive-eslint analyze rxjs --cwd .
|
|
153
|
+
productive-eslint analyze migrations --cwd .
|
|
80
154
|
```
|
|
81
155
|
|
|
156
|
+
The CLI requires the target project to export a marked `productive-eslint`
|
|
157
|
+
composer from `eslint.config.ts` or `eslint.config.mts`.
|
|
158
|
+
|
|
82
159
|
---
|
|
83
160
|
|
|
84
161
|
📄 License: MIT © Bogdan Binitskiy
|
|
85
|
-
💻 Contributor: [Roman Nikitin](https://github.com/Stelsovich1)
|
|
162
|
+
💻 Contributor: [Roman Nikitin](https://github.com/Stelsovich1)
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{_ as e,h as t,i as n,n as r,r as i,t as a,u as o}from"./model-DGP73Lve.js";import s from"node:path";import c from"node:process";import{createJiti as l}from"jiti";import{access as u,readFile as d}from"node:fs/promises";import f from"@typescript-eslint/eslint-plugin";import p from"@typescript-eslint/parser";import m from"eslint-plugin-import";import{ESLint as h}from"eslint";import g from"typescript";import _ from"@smarttools/eslint-plugin-rxjs";const v=e=>{let t=[`# ${e.title}`,``,`## Summary`,``,`- Files scanned: \`${e.fileCount}\``,`- Findings: \`${e.findingsCount}\``,`- Hotspots shown: \`${e.summaries.length}\``];if(e.summaryLines&&e.summaryLines.length>0)for(let n of e.summaryLines)t.push(`- ${n}`);if(t.push(``),e.summaries.length>0){if(e.topGroups&&e.topGroups.length>0){t.push(`## Top Directions`,``);for(let n of e.topGroups)t.push(`- ${n.label}: \`${n.count}\``);t.push(``)}t.push(`## Highest Risk`,``);for(let n of e.summaries){if(t.push(`### \`${n.file}\``,``),t.push(`- Score: \`${n.score}\``),n.labels&&n.labels.length>0&&t.push(`- Labels: ${n.labels.map(e=>`\`${e}\``).join(`, `)}`),n.reasons&&n.reasons.length>0){t.push(`- Why this file is prioritized:`);for(let e of n.reasons)t.push(` - ${e}`)}let e=n.findings.slice(0,10),r=n.findings.length-e.length;for(let r of e){let e=r.symbol?`${n.file} :: ${r.symbol}`:n.file,i=r.line?`${e}:${r.line}${r.column?`:${r.column}`:``}`:e;t.push(`- ${r.ruleId??`unknown-rule`} (${r.severity}, score ${r.score}) at \`${i}\``),r.labels&&r.labels.length>0&&t.push(` - Labels: ${r.labels.map(e=>`\`${e}\``).join(`, `)}`);for(let e of r.reasons)t.push(` - ${e}`)}r>0&&t.push(`- ${r} more findings in this hotspot are omitted from the Markdown report.`),t.push(``)}}return e.suggestedOrder.length>0&&(t.push(`## Suggested Order`,``),e.suggestedOrder.forEach((e,n)=>{t.push(`${n+1}. \`${e}\``)}),t.push(``)),t.push(`## Next Step`,``,e.nextStep),`${t.join(`
|
|
3
|
+
`).trim()}\n`};var y=class extends Error{constructor(e){super(e),this.name=`CliError`}};const ee=[`eslint.config.ts`,`eslint.config.mts`],te=async e=>{for(let t of ee){let n=s.join(e,t);try{return await u(n),n}catch{continue}}return null},ne=t=>{if(!e(t))throw new y(`The selected project does not export a supported productive-eslint composer pipeline.`);return t},re=e=>!e||typeof e!=`object`||!(`default`in e)?e:e.default??e,ie=async e=>{let t=await te(e);if(!t)throw new y(`No supported eslint.config.ts or eslint.config.mts file was found in "${e}".`);let n=l(import.meta.url,{fsCache:!1,moduleCache:!1}),r=c.cwd();try{return c.chdir(e),{composer:ne(re(n(t))),configPath:t}}finally{c.chdir(r)}},b=async({config:e,context:t,ruleIds:n})=>{let r=new h({cwd:t.cwd,errorOnUnmatchedPattern:!1,ignore:!0,ignorePatterns:t.exclude,overrideConfig:e,overrideConfigFile:!0,passOnNoPatterns:!0,ruleFilter:({ruleId:e})=>n.includes(e)}),i=t.include.length>0?t.include:[`.`];return(await r.lintFiles(i)).map(e=>({...e,filePath:s.relative(t.cwd,e.filePath)||e.filePath}))},ae=e=>e.labels?.includes(`test-only`)??!1,oe=e=>{let t=e.reduce((e,t)=>e+t.score,0);return e.length>0&&e.every(ae)?Math.min(t,10):t},x=(e,t)=>{let n=new Map;for(let t of e){let e=n.get(t.file)??[];e.push(t),n.set(t.file,e)}return[...n.entries()].map(([e,t])=>({file:e,findings:t.sort((e,t)=>t.score-e.score),labels:[...new Set(t.flatMap(e=>e.labels??[]))],reasons:[...new Set(t.flatMap(e=>e.reasons.slice(0,1)))],score:oe(t)})).sort((e,t)=>t.score-e.score||e.file.localeCompare(t.file)).slice(0,t)},S={"@typescript-eslint/explicit-module-boundary-types":`error`,"@typescript-eslint/no-explicit-any":`error`,"@typescript-eslint/no-unsafe-return":`error`,"import/no-mutable-exports":`error`,"no-restricted-syntax":[`error`,{message:`Default exports make public API naming less explicit and harder to refactor.`,selector:`ExportDefaultDeclaration`}]},se=m,ce=f,C=Object.keys(S),le={"@typescript-eslint/explicit-module-boundary-types":{category:`public-signature`,labels:[`public-signature`,`missing-return-type`],reason:`An exported boundary should make its return contract explicit.`,score:5,severity:`medium`},"@typescript-eslint/no-explicit-any":{category:`public-type-safety`,labels:[`public-type-safety`,`explicit-any`],reason:`Explicit any in public-facing code weakens downstream contracts.`,score:6,severity:`high`},"@typescript-eslint/no-unsafe-return":{category:`public-type-safety`,labels:[`public-type-safety`,`unsafe-return`],reason:`Unsafe returns can leak unknown runtime shape through an API boundary.`,score:6,severity:`high`},"import/no-mutable-exports":{category:`public-mutability`,labels:[`public-mutability`,`mutable-export`],reason:`Mutable exports make public module state difficult to reason about.`,score:5,severity:`medium`},"no-restricted-syntax":{category:`export-policy`,labels:[`export-policy`,`default-export`],reason:`Default exports make public API naming less explicit and harder to refactor.`,score:2,severity:`low`}},ue=/(^|\/)(index|public-api)\.(c|m)?[jt]sx?$/u,de=/(^|\/)(api|public|shared|lib|components)\//u,fe=/\.vue$/u,pe=(e,t)=>{let n=[],r=[],i=t;return ue.test(e)&&(n.push(`public-entry`),r.push(`This file looks like a public entrypoint.`),i+=3),de.test(e)&&(n.push(`shared-surface`),r.push(`The file sits in a shared or API-facing surface.`),i+=2),fe.test(e)&&(n.push(`vue-component-contract`),r.push(`A Vue component file can expose props, emits, slots, or bindings.`),i+=2),{labels:n,reasons:r,score:i}},me=e=>e.clone().append({files:[t],languageOptions:{parserOptions:{extraFileExtensions:[`.vue`],parser:p,projectService:!0,sourceType:`module`}},name:`productive-eslint/analyze-api-vue-parser`}).append({files:[o,t],name:`productive-eslint/analyze-api`,plugins:{"@typescript-eslint":ce,import:se},rules:S,settings:{"import/resolver":{typescript:!0}}}),he=(e,t)=>{if(!t.ruleId||!C.includes(t.ruleId))return null;let n=le[t.ruleId];if(!n)return null;let r=pe(e,n.score);return{category:n.category,column:t.column,confidence:`high`,file:e,labels:[...n.labels,...r.labels],line:t.line,reasons:[n.reason,t.message,...r.reasons],ruleId:t.ruleId,score:r.score,severity:n.severity}},w=async(e,t)=>{if(t.top<1)throw new y(`--top must be at least 1.`);let n=await b({config:await me(e).toConfigs(),context:t,ruleIds:C}),r=n.flatMap(e=>e.messages.flatMap(t=>{let n=he(e.filePath,t);return n?[n]:[]})),i=x(r,t.top),a=i[0],o=r.filter(e=>e.category===`public-type-safety`).length,s=r.filter(e=>e.category===`public-signature`).length,c=r.filter(e=>e.category===`export-policy`).length;return{fileCount:n.length,findingsCount:r.length,nextStep:a?`Start with \`${a.file}\` and make exported contracts explicit before changing internal implementation details.`:`No public API contract findings were reported by the current analyzer ruleset.`,suggestedOrder:i.map(e=>e.file),summaries:i,summaryLines:[`Public type-safety findings: \`${o}\``,`Public signature findings: \`${s}\``,`Export policy findings: \`${c}\``],title:`API Analysis`}},T=[`boundaries/dependencies`],ge=/\b(?:import|export)\b[\s\S]*?\bfrom\s*['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)/u,_e=(e,n)=>e.clone().append({files:[o,t],name:`productive-eslint/analyze-architecture`,rules:{"boundaries/dependencies":a},settings:{"boundaries/root-path":n}}),ve=e=>e.some(e=>e.plugins?.boundaries?e.files?Array.isArray(e.files)&&e.files.includes(`**/*.vue`):!0:!1),E=e=>{let t=e.match(ge);return t?.[1]??t?.[2]??null},D=(e,t,n)=>{if(n.startsWith(`.`)){let r=s.resolve(s.dirname(s.join(e,t)),n);return s.relative(e,r)}return n},ye=async(e,t)=>{let r=n(t.file);if(!r)return{labels:[`unknown-source`],reason:`Could not map the source file to a known architecture element.`,scoreDelta:0};if(t.category===`private-entry`){let a=E((await d(s.join(e,t.file),`utf8`)).split(`
|
|
4
|
+
`)[Math.max(0,(t.line??1)-1)]??``);if(!a)return{category:`private-entry`,labels:[`private-entry`],reason:`A private entry import was reported, but the exact target import could not be recovered.`,scoreDelta:4};let o=n(D(e,t.file,a));return{category:`private-entry`,labels:[`private-entry`],reason:`This import bypasses the public entry point of the target architecture element.`,scoreDelta:4,...o?{directionLabel:i(r,o)}:{}}}let a=E((await d(s.join(e,t.file),`utf8`)).split(`
|
|
5
|
+
`)[Math.max(0,(t.line??1)-1)]??``);if(!a)return{category:`layer-direction`,labels:[`layer-direction`],reason:`An architecture direction violation was reported, but the target dependency could not be recovered.`,scoreDelta:3};let o=n(D(e,t.file,a));return o?{category:r.type===o.type?`slice-direction`:`layer-direction`,directionLabel:i(r,o),labels:[r.type===o.type?`slice-direction`:`layer-direction`],reason:r.type===o.type?`This import crosses into another slice of the same architecture layer.`:`This import violates the allowed dependency direction between architecture layers.`,scoreDelta:3}:{category:`layer-direction`,labels:[`layer-direction`],reason:`The target dependency could not be mapped to a known architecture element.`,scoreDelta:3}},be=(e,t)=>!t.ruleId||!T.includes(t.ruleId)?null:{category:t.message.includes(`private-entry`)?`private-entry`:`layer-direction`,column:t.column,confidence:`high`,file:e.filePath,labels:[],line:t.line,reasons:[t.message],ruleId:t.ruleId,score:t.message.includes(`private-entry`)?5:4,severity:`high`},xe=e=>{let t=new Map;for(let n of e){let e=n.category===`private-entry`?`private entry imports`:n.labels?.find(e=>e.includes(`->`));e&&t.set(e,(t.get(e)??0)+1)}return[...t.entries()].map(([e,t])=>({count:t,label:e})).sort((e,t)=>t.count-e.count||e.label.localeCompare(t.label)).slice(0,5)},O=async(e,n)=>{if(n.top<1)throw new y(`--top must be at least 1.`);let i=await _e(e,n.cwd).toConfigs();ve(i)||i.push({files:[t],name:`productive-eslint/analyze-architecture-vue-plugin`,plugins:r.plugins});let a=await b({config:i,context:n,ruleIds:[...T]}),o=[];for(let e of a)for(let t of e.messages){let r=be(e,t);if(!r)continue;let i=await ye(n.cwd,r);o.push({...r,...i.category?{category:i.category}:{},labels:[...new Set([...r.labels??[],...i.labels,...i.directionLabel?[i.directionLabel]:[]])],reasons:[...r.reasons,i.reason],score:r.score+i.scoreDelta})}let s=x(o,n.top),c=s[0],l=xe(o),u=o.filter(e=>e.category===`private-entry`).length,d=o.filter(e=>e.category===`layer-direction`).length,f=o.filter(e=>e.category===`slice-direction`).length;return{fileCount:a.length,findingsCount:o.length,nextStep:c?`Start with \`${c.file}\` and remove the repeated architecture violations there first.`:`No architecture boundary violations were reported by the current analyzer ruleset.`,suggestedOrder:s.map(e=>e.file),summaries:s,summaryLines:[`Layer-direction violations: \`${d}\``,`Slice-direction violations: \`${f}\``,`Private-entry violations: \`${u}\``],title:`Architecture Analysis`,topGroups:l}},k={"@typescript-eslint/await-thenable":`error`,"@typescript-eslint/no-floating-promises":`error`,"@typescript-eslint/no-misused-promises":`error`,"@typescript-eslint/require-await":`error`,"@typescript-eslint/return-await":[`error`,`error-handling-correctness-only`],"no-async-promise-executor":`error`,"promise/always-return":`error`,"promise/catch-or-return":`error`,"promise/no-multiple-resolved":`error`,"promise/no-return-in-finally":`error`,"promise/valid-params":`error`},Se=f,A=Object.keys(k),Ce={"@typescript-eslint/await-thenable":{category:`likely-bug`,labels:[`likely-bug`,`await-non-thenable`],reason:`Awaiting a non-thenable value usually means async intent drifted.`,score:5,severity:`high`},"@typescript-eslint/no-floating-promises":{category:`likely-bug`,labels:[`likely-bug`,`floating-promise`],reason:`A promise can reject without any handling path.`,score:6,severity:`high`},"@typescript-eslint/no-misused-promises":{category:`likely-bug`,labels:[`likely-bug`,`misused-promise`],reason:`Async behavior is flowing through an API shape that is usually not promise-safe.`,score:6,severity:`high`},"@typescript-eslint/require-await":{category:`suspicious-flow`,labels:[`suspicious-flow`,`unneeded-async`],reason:`An async function without await can make callers assume async work or error boundaries that are not present.`,score:3,severity:`medium`},"@typescript-eslint/return-await":{category:`suspicious-flow`,labels:[`suspicious-flow`,`return-await-error-boundary`],reason:`Returning a promise without await can bypass the local async error boundary.`,score:4,severity:`medium`},"no-async-promise-executor":{category:`likely-bug`,labels:[`likely-bug`,`async-executor`],reason:`Async promise executors often hide rejected work and double-resolution hazards.`,score:5,severity:`high`},"promise/always-return":{category:`suspicious-flow`,labels:[`suspicious-flow`,`incomplete-chain`],reason:`A promise callback does not consistently return its async result.`,score:3,severity:`medium`},"promise/catch-or-return":{category:`suspicious-flow`,labels:[`suspicious-flow`,`missing-catch`],reason:`The promise chain does not make error handling explicit.`,score:4,severity:`medium`},"promise/no-multiple-resolved":{category:`lifecycle-hazard`,labels:[`lifecycle-hazard`,`multiple-resolve`],reason:`The same promise can resolve or reject more than once.`,score:5,severity:`high`},"promise/no-return-in-finally":{category:`lifecycle-hazard`,labels:[`lifecycle-hazard`,`finally-control-flow`],reason:`Returning from finally can suppress or replace earlier async outcomes.`,score:4,severity:`high`},"promise/valid-params":{category:`likely-bug`,labels:[`likely-bug`,`invalid-promise-api`],reason:`Promise APIs are being called with invalid argument shapes.`,score:4,severity:`medium`}},we=/(^|\/)(test|tests|__tests__|__mocks__)\//u,Te=/\.(spec|test)\.(c|m)?[jt]sx?$/u,Ee=/(^|\/)(shared|lib|libs|utils|helpers|api)\//u,De=e=>we.test(e)||Te.test(e),Oe=e=>Ee.test(e),ke=e=>e.clone().append({files:[t],languageOptions:{parserOptions:{extraFileExtensions:[`.vue`],parser:p,projectService:!0,sourceType:`module`}},name:`productive-eslint/analyze-async-vue-parser`}).append({files:[o,t],name:`productive-eslint/analyze-async`,plugins:{"@typescript-eslint":Se},rules:k}),Ae=(e,t)=>{if(!t.ruleId||!A.includes(t.ruleId))return null;let n=Ce[t.ruleId];if(!n)return null;let r=De(e),i=!r&&Oe(e),a=Math.max(1,n.score+(i?2:0)-(r?2:0)),o=[...n.labels,...i?[`shared-surface`]:[],...r?[`test-only`]:[]],s=[n.reason,t.message,...i?[`The finding sits in a shared async surface that can affect multiple callers.`]:[],...r?[`The finding is test-only, so urgency is usually lower than production code.`]:[]];return{category:n.category,column:t.column,confidence:`high`,file:e,labels:o,line:t.line,reasons:s,ruleId:t.ruleId,score:a,severity:n.severity}},j=async(e,t)=>{if(t.top<1)throw new y(`--top must be at least 1.`);let n=await b({config:await ke(e).toConfigs(),context:t,ruleIds:A}),r=n.flatMap(e=>e.messages.flatMap(t=>{let n=Ae(e.filePath,t);return n?[n]:[]})),i=x(r,t.top),a=i[0],o=r.filter(e=>e.category===`likely-bug`).length,s=r.filter(e=>e.category===`suspicious-flow`).length,c=r.filter(e=>e.category===`lifecycle-hazard`).length;return{fileCount:n.length,findingsCount:r.length,nextStep:a?`Start with \`${a.file}\` and make the async control flow explicit there first.`:`No async reliability findings were reported by the current analyzer ruleset.`,suggestedOrder:i.map(e=>e.file),summaries:i,summaryLines:[`Likely bug findings: \`${o}\``,`Suspicious flow findings: \`${s}\``,`Lifecycle hazard findings: \`${c}\``],title:`Async Analysis`}},M={complexity:[`error`,10],"max-depth":[`error`,3],"no-nested-ternary":`error`,"productive/no-abusive-nested-if":[`error`,3],"sonarjs/cognitive-complexity":[`error`,15],"sonarjs/no-all-duplicated-branches":`error`,"sonarjs/no-duplicated-branches":`error`,"sonarjs/no-gratuitous-expressions":`error`,"sonarjs/no-identical-conditions":`error`,"sonarjs/no-identical-expressions":`error`,"sonarjs/no-invariant-returns":`error`,"sonarjs/no-nested-conditional":`error`},N=Object.keys(M),je={complexity:3,"max-depth":3,"no-nested-ternary":2,"productive/no-abusive-nested-if":2,"sonarjs/cognitive-complexity":4,"sonarjs/no-all-duplicated-branches":3,"sonarjs/no-duplicated-branches":3,"sonarjs/no-gratuitous-expressions":2,"sonarjs/no-identical-conditions":3,"sonarjs/no-identical-expressions":3,"sonarjs/no-invariant-returns":3,"sonarjs/no-nested-conditional":2},Me={complexity:`Cyclomatic complexity already crossed the analyzer threshold.`,"max-depth":`Branch nesting is above the safe editing threshold.`,"no-nested-ternary":`Nested ternaries make branch intent harder to preserve.`,"productive/no-abusive-nested-if":`Nested if chains are dense enough to deserve structural attention.`,"sonarjs/cognitive-complexity":`Cognitive complexity is already high before additional risk scoring.`,"sonarjs/no-all-duplicated-branches":`Every branch has the same implementation, so the control flow is likely misleading.`,"sonarjs/no-duplicated-branches":`Duplicated branch implementations make it harder to infer the intended behavior.`,"sonarjs/no-gratuitous-expressions":`A condition or expression is structurally unnecessary and can obscure intent.`,"sonarjs/no-identical-conditions":`Repeated branch conditions usually point to stale or unreachable logic.`,"sonarjs/no-identical-expressions":`Identical expressions in a comparison can turn branching into dead intent.`,"sonarjs/no-invariant-returns":`A function returns the same value through different branches, making the branching suspect.`,"sonarjs/no-nested-conditional":`Nested conditionals compress multiple branches into a hard-to-edit expression.`},Ne=e=>e.endsWith(`.tsx`)?g.ScriptKind.TSX:g.ScriptKind.TS,P=e=>g.isArrowFunction(e)||g.isConstructorDeclaration(e)||g.isFunctionDeclaration(e)||g.isFunctionExpression(e)||g.isGetAccessor(e)||g.isMethodDeclaration(e)||g.isSetAccessor(e),Pe=e=>{if(g.isConstructorDeclaration(e))return`constructor`;if(`name`in e&&e.name)return g.isIdentifier(e.name)||g.isStringLiteral(e.name)?e.name.text:e.name.getText();let t=e.parent;return t&&g.isVariableDeclaration(t)&&g.isIdentifier(t.name)||t&&g.isPropertyAssignment(t)&&(g.isIdentifier(t.name)||g.isStringLiteral(t.name)||g.isNumericLiteral(t.name))?t.name.text:t&&g.isBinaryExpression(t)&&t.operatorToken.kind===g.SyntaxKind.EqualsToken?t.left.getText():`<anonymous>`},Fe=e=>e?g.isObjectLiteralExpression(e)?`object`:g.isArrayLiteralExpression(e)?`array`:g.isStringLiteralLike(e)||g.isNumericLiteral(e)?`literal`:e.kind===g.SyntaxKind.TrueKeyword||e.kind===g.SyntaxKind.FalseKeyword?`boolean`:g.isIdentifier(e)?`identifier`:g.isCallExpression(e)?`call`:g.isAwaitExpression(e)?`await`:g.isConditionalExpression(e)?`conditional`:g.SyntaxKind[e.kind]??`other`:`void`,F=e=>{let t=0,n=e=>{g.isBinaryExpression(e)&&(e.operatorToken.kind===g.SyntaxKind.AmpersandAmpersandToken||e.operatorToken.kind===g.SyntaxKind.BarBarToken)&&(t+=1),e.forEachChild(n)};return n(e),t},I=e=>g.isIdentifier(e)?e.text:e.getText(),Ie=e=>e===g.SyntaxKind.EqualsToken||e===g.SyntaxKind.PlusEqualsToken||e===g.SyntaxKind.MinusEqualsToken||e===g.SyntaxKind.AsteriskEqualsToken||e===g.SyntaxKind.AsteriskAsteriskEqualsToken||e===g.SyntaxKind.SlashEqualsToken||e===g.SyntaxKind.PercentEqualsToken||e===g.SyntaxKind.AmpersandEqualsToken||e===g.SyntaxKind.BarEqualsToken||e===g.SyntaxKind.CaretEqualsToken||e===g.SyntaxKind.LessThanLessThanEqualsToken||e===g.SyntaxKind.GreaterThanGreaterThanEqualsToken||e===g.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken||e===g.SyntaxKind.AmpersandAmpersandEqualsToken||e===g.SyntaxKind.BarBarEqualsToken||e===g.SyntaxKind.QuestionQuestionEqualsToken,Le=(e,t)=>{let n=0,r=[],i=new Map,a=new Set,o=new Set,s=new Set,c=0,l=0,u=0,d=0,f=e=>{let t=n;n+=1,r.push(t),c=Math.max(c,r.length),h(e),r.pop()},p=e=>{let t=r.at(-1);if(t===void 0)return;let n=i.get(t)??new Set;n.add(e),i.set(t,n),a.add(t)},m=()=>{let e=r.at(-1);e!==void 0&&o.add(e)},h=t=>{if(!(t!==e&&P(t))){if(g.isIfStatement(t)){d+=F(t.expression),f(t.thenStatement),t.elseStatement&&f(t.elseStatement);return}if(g.isForStatement(t)||g.isForInStatement(t)||g.isForOfStatement(t)||g.isWhileStatement(t)||g.isDoStatement(t)){`expression`in t&&t.expression&&(d+=F(t.expression)),f(t.statement);return}if(g.isSwitchStatement(t)){for(let e of t.caseBlock.clauses)f(e);return}if(g.isTryStatement(t)&&r.length>0&&(u+=1),g.isAwaitExpression(t)&&r.length>0&&(l+=1),g.isBinaryExpression(t)&&Ie(t.operatorToken.kind)&&(g.isIdentifier(t.left)||g.isPropertyAccessExpression(t.left)||g.isElementAccessExpression(t.left)||g.isArrayLiteralExpression(t.left)||g.isObjectLiteralExpression(t.left))&&p(I(t.left)),(g.isPrefixUnaryExpression(t)||g.isPostfixUnaryExpression(t))&&(t.operator===g.SyntaxKind.PlusPlusToken||t.operator===g.SyntaxKind.MinusMinusToken)){let e=t.operand;(g.isIdentifier(e)||g.isPropertyAccessExpression(e)||g.isElementAccessExpression(e))&&p(I(e))}(g.isCallExpression(t)||g.isNewExpression(t))&&r.length>0&&m(),g.isReturnStatement(t)&&s.add(Fe(t.expression)),t.forEachChild(h)}};e.body&&h(e.body);let _=new Map;for(let e of i.values())for(let t of e)_.set(t,(_.get(t)??0)+1);let v=t.getLineAndCharacterOfPosition(e.getStart(t)).line+1,y=t.getLineAndCharacterOfPosition(e.end).line+1;return{asyncInsideBranches:l,booleanGuardComplexity:d,branchMutations:a.size,functionLines:y-v+1,maxDepth:c,mixedReturnShapes:s.size>1?s.size:0,sharedMutableAcrossBranches:[..._.values()].filter(e=>e>1).length,sideEffectBranches:o.size,tryCatchInsideBranches:u}},Re=(e,t)=>{let n=[],r=i=>{if(P(i)){let r=t.getLineAndCharacterOfPosition(i.getStart(t)).line+1,a=t.getLineAndCharacterOfPosition(i.end).line+1;n.push({displayName:Pe(i),endLine:a,file:e,metrics:Le(i,t),startLine:r})}i.forEachChild(r)};return r(t),n},L=(e,t)=>t===void 0?null:e.filter(e=>t>=e.startLine&&t<=e.endLine).sort((e,t)=>e.endLine-e.startLine-(t.endLine-t.startLine))[0]??null,ze=(e,t,n)=>{if(n&&n.displayName!==`<anonymous>`)return n;let r=e.filter(e=>e.displayName!==`<anonymous>`);for(let e of t){let t=L(r,e.line);if(t)return t}return n},Be=e=>Math.max(0,e.maxDepth-2)+e.branchMutations*2+e.asyncInsideBranches*2+e.tryCatchInsideBranches*2+(e.mixedReturnShapes>0?2:0)+e.sharedMutableAcrossBranches*3+Math.max(0,e.sideEffectBranches-1)*3+ +(e.functionLines>40)+e.booleanGuardComplexity,Ve=e=>{let t=[];return e.maxDepth>2&&t.push(`Nested branch depth reaches ${e.maxDepth}.`),e.branchMutations>0&&t.push(`Branches mutate state in ${e.branchMutations} places.`),e.sharedMutableAcrossBranches>0&&t.push(`Mutable state is shared across ${e.sharedMutableAcrossBranches} branch paths.`),e.asyncInsideBranches>0&&t.push(`Async work appears inside ${e.asyncInsideBranches} branch paths.`),e.sideEffectBranches>1&&t.push(`Side effects appear in ${e.sideEffectBranches} different branches.`),e.tryCatchInsideBranches>0&&t.push(`Try/catch handling is nested inside branching logic.`),e.mixedReturnShapes>0&&t.push(`The function returns more than one structural shape.`),e.functionLines>40&&t.push(`Function spans ${e.functionLines} lines.`),e.booleanGuardComplexity>0&&t.push(`Boolean guards contain multiple logical operators.`),t},He=e=>e>=12?`high`:e>=7?`medium`:`low`,Ue=e=>e.clone().append({files:[o],name:`productive-eslint/analyze-complexity`,rules:M}),We=(e,t)=>e.sort((e,t)=>t.score-e.score||e.file.localeCompare(t.file)).slice(0,t).map(e=>({file:e.file,findings:[e],...e.labels?{labels:e.labels}:{},reasons:e.reasons.slice(0,3),score:e.score})),R=e=>e.symbol?`${e.file} :: ${e.symbol}`:e.file,z=async(e,t)=>{if(t.top<1)throw new y(`--top must be at least 1.`);let n=await b({config:await Ue(e).toConfigs(),context:t,ruleIds:N}),r=[];for(let e of n){if(e.messages.length===0)continue;let n=s.join(t.cwd,e.filePath),i=await d(n,`utf8`),a=g.createSourceFile(n,i,g.ScriptTarget.Latest,!0,Ne(n)),o=Re(e.filePath,a),c=new Map;for(let t of e.messages){if(!t.ruleId||!N.includes(t.ruleId))continue;let n=L(o,t.line),r=n?`${e.filePath} :: ${n.displayName}:${n.startLine}`:`${e.filePath} :: <module>:0`,i=c.get(r)??{context:n,hits:[]};i.hits.push({line:t.line,message:t.message,ruleId:t.ruleId}),c.set(r,i)}for(let[,t]of c){let n=ze(o,t.hits,t.context),i=n?.metrics??{asyncInsideBranches:0,booleanGuardComplexity:0,branchMutations:0,functionLines:0,maxDepth:0,mixedReturnShapes:0,sharedMutableAcrossBranches:0,sideEffectBranches:0,tryCatchInsideBranches:0},a=t.hits.reduce((e,t)=>e+(je[t.ruleId]??1),0)+Be(i),s=He(a),c=n?.displayName??`<module>`,l=[...t.hits.map(e=>`${Me[e.ruleId]??e.message} (${e.ruleId} at line ${e.line})`),...Ve(i)];r.push({category:`semantic-change-risk`,confidence:`medium`,file:e.filePath,labels:[s,...n?[`function:${c}`]:[`module-scope`]],...n?{line:n.startLine}:{},reasons:l,ruleId:`complexity/hotspot`,score:a,severity:s,symbol:c})}}let i=We(r,t.top),a=r.filter(e=>e.severity===`high`).length,o=r.filter(e=>e.severity===`medium`).length,c=r.filter(e=>e.severity===`low`).length,l=i[0],u=l?.findings[0];return{fileCount:n.length,findingsCount:r.length,nextStep:l?`Start with \`${u?R(u):l.file}\` and reduce the branch and side-effect risk there first.`:`No complexity hotspots were reported by the current analyzer ruleset.`,suggestedOrder:i.flatMap(e=>e.findings.map(R)),summaries:i,summaryLines:[`High-risk hotspots: \`${a}\``,`Medium-risk hotspots: \`${o}\``,`Low-risk hotspots: \`${c}\``],title:`Complexity Analysis`}},Ge={"@typescript-eslint/no-unused-private-class-members":`error`,"@typescript-eslint/no-unused-vars":[`error`,{args:`after-used`,argsIgnorePattern:`^_`,caughtErrors:`all`,caughtErrorsIgnorePattern:`^_`,ignoreRestSiblings:!0,varsIgnorePattern:`^_`}],"@typescript-eslint/no-useless-constructor":`error`,"no-unreachable":`error`,"no-useless-catch":`error`,"unicorn/no-empty-file":`error`,"unicorn/no-static-only-class":`error`,"unicorn/no-useless-undefined":`error`,"unused-imports/no-unused-imports":`error`},Ke=f,B=Object.keys(Ge),qe={"@typescript-eslint/no-unused-private-class-members":{category:`safe-delete-candidate`,labels:[`safe-delete-candidate`,`private-member`],reason:`A private class member appears unused and is usually safe to delete.`,score:4,severity:`medium`},"@typescript-eslint/no-unused-vars":{category:`safe-delete-candidate`,labels:[`safe-delete-candidate`,`unused-symbol`],reason:`An unused local symbol adds maintenance noise.`,score:3,severity:`medium`},"@typescript-eslint/no-useless-constructor":{category:`safe-delete-candidate`,labels:[`safe-delete-candidate`,`useless-constructor`],reason:`A constructor does not add behavior beyond the inherited default.`,score:3,severity:`low`},"no-unreachable":{category:`likely-bug-or-delete`,labels:[`likely-bug-or-delete`,`unreachable`],reason:`Unreachable code is either dead or hides a control-flow mistake.`,score:6,severity:`high`},"no-useless-catch":{category:`safe-delete-candidate`,labels:[`safe-delete-candidate`,`useless-catch`],reason:`A catch block only rethrows and can usually be removed.`,score:3,severity:`low`},"unicorn/no-empty-file":{category:`safe-delete-candidate`,labels:[`safe-delete-candidate`,`empty-file`],reason:`An empty source file is usually leftover migration or scaffolding debt.`,score:4,severity:`medium`},"unicorn/no-static-only-class":{category:`requires-judgment`,labels:[`requires-judgment`,`static-only-class`],reason:`A static-only class may be a namespace-shaped leftover rather than useful API.`,score:2,severity:`low`},"unicorn/no-useless-undefined":{category:`safe-delete-candidate`,labels:[`safe-delete-candidate`,`redundant-undefined`],reason:`A redundant undefined usually has no semantic value.`,score:1,severity:`low`},"unused-imports/no-unused-imports":{category:`mechanical-delete`,labels:[`mechanical-delete`,`unused-import`],reason:`An unused import can be removed mechanically.`,score:2,severity:`low`}},Je=/(^|\/)(test|tests|__tests__|__mocks__)\//u,Ye=/\.(spec|test)\.(c|m)?[jt]sx?$/u,Xe=e=>Je.test(e)||Ye.test(e),Ze=e=>e.clone().append({files:[t],languageOptions:{parserOptions:{extraFileExtensions:[`.vue`],parser:p,projectService:!0,sourceType:`module`}},name:`productive-eslint/analyze-dead-code-vue-parser`}).append({files:[o,t],name:`productive-eslint/analyze-dead-code`,plugins:{"@typescript-eslint":Ke},rules:Ge}),Qe=(e,t)=>{if(!t.ruleId||!B.includes(t.ruleId))return null;let n=qe[t.ruleId];if(!n)return null;let r=Xe(e);return{category:r?`test-only`:n.category,column:t.column,confidence:n.category===`requires-judgment`?`medium`:`high`,file:e,labels:[...n.labels,...r?[`test-only`]:[]],line:t.line,reasons:[n.reason,t.message,...r?[`The finding is test-only, so deletion priority is usually lower.`]:[]],ruleId:t.ruleId,score:Math.max(1,n.score-(r?2:0)),severity:r?`low`:n.severity}},V=async(e,t)=>{if(t.top<1)throw new y(`--top must be at least 1.`);let n=await b({config:await Ze(e).toConfigs(),context:t,ruleIds:B}),r=n.flatMap(e=>e.messages.flatMap(t=>{let n=Qe(e.filePath,t);return n?[n]:[]})),i=x(r,t.top),a=i[0],o=r.filter(e=>e.labels?.includes(`mechanical-delete`)).length,s=r.filter(e=>e.labels?.includes(`safe-delete-candidate`)).length,c=r.filter(e=>e.category===`requires-judgment`).length;return{fileCount:n.length,findingsCount:r.length,nextStep:a?`Start with \`${a.file}\` and remove the mechanical dead-code findings before touching judgment-heavy cleanup.`:`No dead-code findings were reported by the current analyzer ruleset.`,suggestedOrder:i.map(e=>e.file),summaries:i,summaryLines:[`Mechanical deletion findings: \`${o}\``,`Safe-delete candidates: \`${s}\``,`Requires judgment findings: \`${c}\``],title:`Dead Code Analysis`}},H={"@typescript-eslint/consistent-type-imports":[`error`,{disallowTypeAnnotations:!1,fixStyle:`separate-type-imports`,prefer:`type-imports`}],"import/consistent-type-specifier-style":[`error`,`prefer-top-level`],"import/no-cycle":[`error`,{ignoreExternal:!0,maxDepth:10}],"import/no-duplicates":`error`,"import/no-mutable-exports":`error`,"import/no-namespace":`error`,"import/no-relative-packages":`error`,"import/no-self-import":`error`,"import/no-useless-path-segments":`error`},$e=m,et=f,U=Object.keys(H),tt={"@typescript-eslint/consistent-type-imports":{category:`type-value-boundary`,labels:[`type-value-boundary`,`mechanical-fix`],reason:`Mixed type and value imports make dependency intent harder to scan.`,score:2,severity:`low`},"import/consistent-type-specifier-style":{category:`type-value-boundary`,labels:[`type-value-boundary`,`mechanical-fix`],reason:`Inline type specifiers make import shape less consistent.`,score:1,severity:`low`},"import/no-cycle":{category:`dependency-graph`,labels:[`dependency-graph`,`cycle`],reason:`Import cycles increase coupling and can create initialization hazards.`,score:7,severity:`high`},"import/no-duplicates":{category:`local-import-hygiene`,labels:[`local-import-hygiene`,`mechanical-fix`],reason:`Duplicate imports are local dependency noise and are usually mechanical to merge.`,score:2,severity:`low`},"import/no-mutable-exports":{category:`public-contract-risk`,labels:[`public-contract-risk`,`mutable-export`],reason:`Mutable exports make module contracts harder to reason about.`,score:5,severity:`medium`},"import/no-namespace":{category:`dependency-shape`,labels:[`dependency-shape`,`namespace-import`],reason:`Namespace imports can hide which dependency surface is actually used.`,score:2,severity:`low`},"import/no-relative-packages":{category:`package-boundary`,labels:[`package-boundary`,`relative-package-import`],reason:`Relative package imports bypass package boundaries and published entrypoints.`,score:6,severity:`high`},"import/no-self-import":{category:`dependency-graph`,labels:[`dependency-graph`,`self-import`],reason:`A module importing itself indicates broken dependency shape.`,score:6,severity:`high`},"import/no-useless-path-segments":{category:`local-import-hygiene`,labels:[`local-import-hygiene`,`mechanical-fix`],reason:`Useless path segments make imports harder to compare and maintain.`,score:1,severity:`low`}},nt=e=>e.clone().append({files:[o,t],name:`productive-eslint/analyze-imports`,plugins:{"@typescript-eslint":et,import:$e},rules:H,settings:{"import/resolver":{typescript:!0}}}),rt=(e,t)=>{if(!t.ruleId||!U.includes(t.ruleId))return null;let n=tt[t.ruleId];return n?{category:n.category,column:t.column,confidence:`high`,file:e,labels:n.labels,line:t.line,reasons:[n.reason,t.message],ruleId:t.ruleId,score:n.score,severity:n.severity}:null},it=e=>[...e.reduce((e,t)=>{let n=t.category??`uncategorized`;return e.set(n,(e.get(n)??0)+1),e},new Map)].map(([e,t])=>({count:t,label:e})).sort((e,t)=>t.count-e.count||e.label.localeCompare(t.label)),W=async(e,t)=>{if(t.top<1)throw new y(`--top must be at least 1.`);let n=await b({config:await nt(e).toConfigs(),context:t,ruleIds:U}),r=n.flatMap(e=>e.messages.flatMap(t=>{let n=rt(e.filePath,t);return n?[n]:[]})),i=x(r,t.top),a=i[0],o=r.filter(e=>e.labels?.includes(`cycle`)).length,s=r.filter(e=>e.category===`package-boundary`).length,c=r.filter(e=>e.labels?.includes(`mechanical-fix`)).length;return{fileCount:n.length,findingsCount:r.length,nextStep:a?`Start with \`${a.file}\`; fix cycles and package-boundary findings before mechanical import cleanup.`:`No import-shape findings were reported by the current analyzer ruleset.`,suggestedOrder:i.map(e=>e.file),summaries:i,summaryLines:[`Import cycle findings: \`${o}\``,`Package-boundary findings: \`${s}\``,`Mechanical import fixes: \`${c}\``],title:`Imports Analysis`,topGroups:it(r)}},G={"@typescript-eslint/no-deprecated":`error`,"import/no-deprecated":`error`,"n/no-deprecated-api":`error`},K={"productive-rxjs/no-compat":`error`,"productive-rxjs/no-create":`error`,"productive-rxjs/no-topromise":`error`},q={"vue/no-deprecated-data-object-declaration":`error`,"vue/no-deprecated-delete-set":`error`,"vue/no-deprecated-destroyed-lifecycle":`error`,"vue/no-deprecated-dollar-listeners-api":`error`,"vue/no-deprecated-dollar-scopedslots-api":`error`,"vue/no-deprecated-events-api":`error`,"vue/no-deprecated-filter":`error`,"vue/no-deprecated-functional-template":`error`,"vue/no-deprecated-html-element-is":`error`,"vue/no-deprecated-inline-template":`error`,"vue/no-deprecated-model-definition":`error`,"vue/no-deprecated-props-default-this":`error`,"vue/no-deprecated-router-link-tag-prop":`error`,"vue/no-deprecated-scope-attribute":`error`,"vue/no-deprecated-slot-attribute":`error`,"vue/no-deprecated-slot-scope-attribute":`error`,"vue/no-deprecated-v-bind-sync":`error`,"vue/no-deprecated-v-is":`error`,"vue/no-deprecated-v-on-native-modifier":`error`,"vue/no-deprecated-v-on-number-modifiers":`error`,"vue/no-deprecated-vue-config-keycodes":`error`},J=[...Object.keys(G),...Object.keys(K),...Object.keys(q)],at=_,ot={"productive-rxjs/no-compat":`rxjs/no-compat`,"productive-rxjs/no-create":`rxjs/no-create`,"productive-rxjs/no-topromise":`rxjs/no-topromise`},st=e=>e.startsWith(`vue/no-deprecated-`)?{category:`vue-migration`,labels:[`vue-migration`,`deprecated-api`],reason:`Deprecated Vue API usage should be removed before framework upgrades.`,score:4,severity:`medium`}:e.startsWith(`@typescript-eslint/`)?{category:`typescript-migration`,labels:[`typescript-migration`,`deprecated-api`],reason:`Deprecated TypeScript symbols should be migrated before they become harder to trace.`,score:4,severity:`medium`}:e.startsWith(`import/`)?{category:`dependency-migration`,labels:[`dependency-migration`,`deprecated-api`],reason:`Deprecated imported APIs should be removed before dependency upgrades.`,score:4,severity:`medium`}:e.startsWith(`rxjs/`)?{category:`rxjs-migration`,labels:[`rxjs-migration`,`deprecated-api`],reason:`RxJS toPromise is deprecated and blocks clean reactive upgrades.`,score:4,severity:`medium`}:{category:`node-migration`,labels:[`node-migration`,`deprecated-api`],reason:`Deprecated Node APIs should be removed before runtime upgrades.`,score:4,severity:`medium`},ct=e=>e.clone().append({files:[o],name:`productive-eslint/analyze-migrations-source`,rules:G}).append({files:[o],name:`productive-eslint/analyze-migrations-rxjs`,plugins:{"productive-rxjs":at},rules:K}).append({files:[t],name:`productive-eslint/analyze-migrations-vue`,rules:q}),lt=(e,t)=>{if(!t.ruleId||!J.includes(t.ruleId))return null;let n=ot[t.ruleId]??t.ruleId,r=st(n);return{category:r.category,column:t.column,confidence:`high`,file:e,labels:r.labels,line:t.line,reasons:[r.reason,t.message],ruleId:n,score:r.score,severity:r.severity}},ut=async(e,t)=>{if(t.top<1)throw new y(`--top must be at least 1.`);let n=await b({config:await ct(e).toConfigs(),context:t,ruleIds:J}),r=n.flatMap(e=>e.messages.flatMap(t=>{let n=lt(e.filePath,t);return n?[n]:[]})),i=x(r,t.top),a=i[0],o=r.filter(e=>e.category===`vue-migration`).length,s=r.filter(e=>e.category===`rxjs-migration`).length,c=r.filter(e=>e.category===`node-migration`).length,l=r.filter(e=>e.category===`typescript-migration`).length,u=r.filter(e=>e.category===`dependency-migration`).length;return{fileCount:n.length,findingsCount:r.length,nextStep:a?`Start with \`${a.file}\` and remove deprecated APIs before planning larger framework upgrades.`:`No migration-tail findings were reported by the current analyzer ruleset.`,suggestedOrder:i.map(e=>e.file),summaries:i,summaryLines:[`Vue migration findings: \`${o}\``,`RxJS migration findings: \`${s}\``,`Node migration findings: \`${c}\``,`TypeScript migration findings: \`${l}\``,`Dependency migration findings: \`${u}\``],title:`Migrations Analysis`}},Y={"@typescript-eslint/ban-ts-comment":[`error`,{minimumDescriptionLength:10,"ts-check":!1,"ts-expect-error":`allow-with-description`,"ts-ignore":!0,"ts-nocheck":!0}],"eslint-comments/no-aggregating-enable":`error`,"eslint-comments/no-duplicate-disable":`error`,"eslint-comments/no-unlimited-disable":`error`,"eslint-comments/no-unused-enable":`error`},dt=f,X=Object.keys(Y),ft=/(^|\/)(test|tests|__tests__|__mocks__)\//u,pt=/\.(spec|test)\.(c|m)?[jt]sx?$/u,mt=/(^|\/)(shared|lib|libs|utils|helpers|api)\//u,ht=/eslint-disable|eslint-enable|@ts-(?:ignore|expect-error|nocheck|check)/gu,Z=/^Unused eslint-disable directive/u,gt={category:`unused-disable`,labels:[`unused-disable`,`stale-suppression`],reason:`The disable directive no longer suppresses anything and should be removed.`,score:4,severity:`medium`},_t={"@typescript-eslint/ban-ts-comment":{category:`ts-directive`,labels:[`ts-directive`,`compiler-suppression`],reason:`A TypeScript compiler directive is muting or weakening type feedback.`,score:4,severity:`medium`},"eslint-comments/no-aggregating-enable":{category:`aggregated-enable`,labels:[`aggregated-enable`,`scope-confusion`],reason:`One eslint-enable is reopening multiple disables, which makes suppression scope harder to reason about.`,score:3,severity:`medium`},"eslint-comments/no-duplicate-disable":{category:`duplicate-disable`,labels:[`duplicate-disable`,`redundant-suppression`],reason:`The same rule is being disabled more than once in the same area.`,score:3,severity:`medium`},"eslint-comments/no-unlimited-disable":{category:`broad-disable`,labels:[`broad-disable`,`wide-suppression`],reason:`An eslint-disable comment is muting every rule instead of naming the specific exception.`,score:6,severity:`high`},"eslint-comments/no-unused-enable":{category:`unused-enable`,labels:[`unused-enable`,`stale-suppression`],reason:`The eslint-enable directive is not paired with an active disable and should be cleaned up.`,score:2,severity:`low`}},vt=e=>ft.test(e)||pt.test(e),yt=e=>mt.test(e),bt=async(e,t)=>{let n=(await d(s.join(e,t),`utf8`)).match(ht)?.length??0;return{isSharedSurface:yt(t),isTest:vt(t),suppressionCount:n}},xt=e=>e.clone().append({linterOptions:{reportUnusedDisableDirectives:`error`},name:`productive-eslint/analyze-suppressions-options`}).append({files:[t],languageOptions:{parserOptions:{extraFileExtensions:[`.vue`],parser:p,projectService:!0,sourceType:`module`}},name:`productive-eslint/analyze-suppressions-vue-parser`}).append({files:[o,t],name:`productive-eslint/analyze-suppressions`,plugins:{"@typescript-eslint":dt},rules:Y}),St=e=>e.message.includes(`@ts-ignore`)?{category:`ts-ignore`,labels:[`ts-ignore`,`compiler-suppression`],reason:`ts-ignore suppresses the next compiler error even when the code stops failing later.`,score:6,severity:`high`}:e.message.includes(`@ts-nocheck`)?{category:`ts-nocheck`,labels:[`ts-nocheck`,`file-wide-suppression`],reason:`ts-nocheck suppresses type checking for the whole file and tends to hide deeper debt.`,score:7,severity:`high`}:e.message.includes(`@ts-expect-error`)?{category:`weak-ts-expect-error`,labels:[`ts-expect-error`,`weak-description`],reason:`ts-expect-error without a useful explanation makes the suppressed type debt harder to revisit safely.`,score:4,severity:`medium`}:_t[`@typescript-eslint/ban-ts-comment`]??{category:`ts-directive`,labels:[`ts-directive`,`compiler-suppression`],reason:`A TypeScript compiler directive is muting or weakening type feedback.`,score:4,severity:`medium`},Ct=(e,t,n)=>{let r=!t.ruleId&&Z.test(t.message)?gt:t.ruleId===`@typescript-eslint/ban-ts-comment`?St(t):t.ruleId?_t[t.ruleId]:null;if(!r)return null;let i=[...r.labels,...n.isSharedSurface?[`shared-surface`]:[],...n.isTest?[`test-only`]:[],...n.suppressionCount>=3?[`dense-suppressions`]:[]],a=[r.reason,t.message,...n.isSharedSurface?[`The suppression sits in a shared surface, so it can hide issues for multiple callers.`]:[],...n.isTest?[`The suppression is test-only, so cleanup urgency is usually lower than production code.`]:[],...n.suppressionCount>=3?[`This file already contains ${n.suppressionCount} suppression directives, which usually points to accumulated local debt.`]:[]],o=Math.max(1,r.score+(n.isSharedSurface?2:0)+(n.isTest?-2:0)+ +(n.suppressionCount>=3));return{category:r.category,column:t.column,confidence:t.ruleId?`high`:`medium`,file:e,labels:i,line:t.line,reasons:a,ruleId:t.ruleId??`eslint/unused-disable-directive`,score:o,severity:r.severity}},wt=async(e,t)=>{if(t.top<1)throw new y(`--top must be at least 1.`);let n=await b({config:await xt(e).toConfigs(),context:t,ruleIds:X}),r=n.filter(e=>e.messages.some(e=>!e.ruleId&&Z.test(e.message)||(e.ruleId?X.includes(e.ruleId):!1))),i=new Map;await Promise.all(r.map(async e=>{i.set(e.filePath,await bt(t.cwd,e.filePath))}));let a=r.flatMap(e=>e.messages.flatMap(t=>{let n=i.get(e.filePath);if(!n)return[];let r=Ct(e.filePath,t,n);return r?[r]:[]})),o=x(a,t.top),s=o[0],c=a.filter(e=>e.category===`unused-disable`||e.category===`unused-enable`).length,l=a.filter(e=>e.category===`broad-disable`).length,u=a.filter(e=>e.labels?.includes(`compiler-suppression`)).length,d=o.filter(e=>e.labels?.includes(`dense-suppressions`)).length;return{fileCount:n.length,findingsCount:a.length,nextStep:s?`Start with \`${s.file}\` and remove stale or broad suppressions there before touching lower-risk files.`:`No suppression findings were reported by the current analyzer ruleset.`,suggestedOrder:o.map(e=>e.file),summaries:o,summaryLines:[`Unused directive findings: \`${c}\``,`Broad disable findings: \`${l}\``,`TypeScript directive findings: \`${u}\``,`Dense suppression files: \`${d}\``],title:`Suppressions Analysis`}},Tt={"@typescript-eslint/ban-ts-comment":`error`,"@typescript-eslint/no-base-to-string":`error`,"@typescript-eslint/no-explicit-any":`error`,"@typescript-eslint/no-unnecessary-condition":`error`,"@typescript-eslint/no-unsafe-argument":`error`,"@typescript-eslint/no-unsafe-assignment":`error`,"@typescript-eslint/no-unsafe-call":`error`,"@typescript-eslint/no-unsafe-enum-comparison":`error`,"@typescript-eslint/no-unsafe-member-access":`error`,"@typescript-eslint/no-unsafe-return":`error`,"@typescript-eslint/no-unsafe-unary-minus":`error`},Et=f,Dt=Object.keys(Tt),Ot={"@typescript-eslint/ban-ts-comment":`Type suppression hides an actual type-system signal.`,"@typescript-eslint/no-base-to-string":`Implicit object stringification often hides an unexpected runtime representation.`,"@typescript-eslint/no-explicit-any":`Explicit any weakens the local type boundary.`,"@typescript-eslint/no-unnecessary-condition":`A condition is unnecessary according to TypeScript and can mislead control-flow analysis.`,"@typescript-eslint/no-unsafe-argument":`Unsafe argument passes an untyped value across a typed boundary.`,"@typescript-eslint/no-unsafe-assignment":`Unsafe assignment can spread an untyped value further.`,"@typescript-eslint/no-unsafe-call":`Unsafe call means runtime behavior depends on an untyped value.`,"@typescript-eslint/no-unsafe-enum-comparison":`Unsafe enum comparison can make a branch look valid while comparing incompatible domains.`,"@typescript-eslint/no-unsafe-member-access":`Unsafe member access depends on unchecked runtime shape.`,"@typescript-eslint/no-unsafe-return":`Unsafe return leaks a weakly typed value through an API boundary.`,"@typescript-eslint/no-unsafe-unary-minus":`Unsafe unary minus depends on unchecked numeric coercion.`},kt={"@typescript-eslint/ban-ts-comment":5,"@typescript-eslint/no-base-to-string":2,"@typescript-eslint/no-explicit-any":4,"@typescript-eslint/no-unnecessary-condition":2,"@typescript-eslint/no-unsafe-argument":4,"@typescript-eslint/no-unsafe-assignment":3,"@typescript-eslint/no-unsafe-call":3,"@typescript-eslint/no-unsafe-enum-comparison":3,"@typescript-eslint/no-unsafe-member-access":3,"@typescript-eslint/no-unsafe-return":4,"@typescript-eslint/no-unsafe-unary-minus":3},At={"@typescript-eslint/ban-ts-comment":`high`,"@typescript-eslint/no-base-to-string":`low`,"@typescript-eslint/no-explicit-any":`high`,"@typescript-eslint/no-unnecessary-condition":`low`,"@typescript-eslint/no-unsafe-argument":`high`,"@typescript-eslint/no-unsafe-assignment":`medium`,"@typescript-eslint/no-unsafe-call":`medium`,"@typescript-eslint/no-unsafe-enum-comparison":`medium`,"@typescript-eslint/no-unsafe-member-access":`medium`,"@typescript-eslint/no-unsafe-return":`high`,"@typescript-eslint/no-unsafe-unary-minus":`medium`},jt=/(^|\/)(test|tests|__tests__|__mocks__)\//u,Mt=/\.(spec|test)\.(mts|ts|tsx)$/u,Nt=/(^|\/)(shared|lib|libs|utils|helpers)\//u,Pt=/\.vue$/u,Ft=/(^|\/)(components|ui)\//u,It=[/\bdefineProps\s*</u,/\bdefineProps\s*\(/u,/\bdefineEmits\s*</u,/\bdefineEmits\s*\(/u,/\bdefineSlots\s*</u,/\bdefineSlots\s*\(/u,/\bdefineExpose\s*\(/u,/\bdefineModel\s*</u,/\bdefineModel\s*\(/u,/\bdefineComponent\s*\(/u],Lt=new Set([`@typescript-eslint/no-explicit-any`,`@typescript-eslint/no-unsafe-return`]),Rt=new Set([`@typescript-eslint/no-unnecessary-condition`]),zt=e=>e.endsWith(`.tsx`)?g.ScriptKind.TSX:g.ScriptKind.TS,Bt=e=>(g.getCombinedModifierFlags(e)&g.ModifierFlags.Export)!==0,Vt=e=>e.statements.flatMap(t=>{if(g.isExportAssignment(t)||g.isExportDeclaration(t)||Bt(t)){let n=e.getLineAndCharacterOfPosition(t.getStart(e)).line+1;return[{endLine:e.getLineAndCharacterOfPosition(t.getEnd()).line+1,startLine:n}]}return[]}),Ht=e=>e.split(`
|
|
6
|
+
`).flatMap((e,t)=>It.some(t=>t.test(e))?[t+1]:[]),Ut=async(e,t)=>{let n=s.join(e,t),r=await d(n,`utf8`);if(Pt.test(t)){let e=Ht(r);return{exportedRanges:[],hasExports:e.length>0,isSharedUtility:Nt.test(t)||Ft.test(t),isTest:jt.test(t)||Mt.test(t),isVueComponent:!0,vueContractLines:e}}let i=Vt(g.createSourceFile(n,r,g.ScriptTarget.Latest,!0,zt(n)));return{exportedRanges:i,hasExports:i.length>0,isSharedUtility:Nt.test(t),isTest:jt.test(t)||Mt.test(t),isVueComponent:!1,vueContractLines:[]}},Wt=(e,t)=>e!==void 0&&t.some(t=>e>=t.startLine&&e<=t.endLine),Gt=(e,t)=>e!==void 0&&t.some(t=>Math.abs(t-e)<=2),Kt=(e,t)=>Rt.has(t.ruleId??``)?{category:`control-flow-signal`,labels:[`control-flow-signal`,`unnecessary-condition`],reason:`The condition is impossible or redundant according to the current TypeScript model.`,scoreDelta:0}:e.isTest?{category:`test-only`,labels:[`test-only`],reason:`Test-only type holes usually have lower migration urgency.`,scoreDelta:-2}:Lt.has(t.ruleId??``)&&Wt(t.line,e.exportedRanges)?{category:`exported-api`,labels:[`exported-api`],reason:`The finding sits inside an exported declaration and can leak outward.`,scoreDelta:4}:e.isVueComponent&&Lt.has(t.ruleId??``)&&Gt(t.line,e.vueContractLines)?{category:`vue-component-contract`,labels:[`vue-component-contract`],reason:`The finding appears near a Vue component contract and can leak through props, emits, or exposed bindings.`,scoreDelta:4}:e.isVueComponent&&e.isSharedUtility?{category:`shared-vue-component`,labels:[`shared-vue-component`],reason:`Shared Vue components can spread weak typing through reusable props and emits contracts.`,scoreDelta:2}:e.hasExports&&s.basename(t.file)===`index.ts`?{category:`public-entry`,labels:[`public-entry`],reason:`The file looks like a public entrypoint, so weak typing spreads quickly.`,scoreDelta:3}:e.isSharedUtility?{category:`shared-utility`,labels:[`shared-utility`],reason:`Shared utilities tend to amplify weak typing across multiple callers.`,scoreDelta:2}:{category:`internal`,labels:[`internal`],reason:`The finding appears to be internal implementation detail.`,scoreDelta:0},qt=e=>e.clone().append({files:[`**/*.vue`],languageOptions:{parserOptions:{extraFileExtensions:[`.vue`],parser:p,projectService:!0,sourceType:`module`}},name:`productive-eslint/analyze-types-vue-parser`}).append({files:[`**/*.ts`,`**/*.tsx`,`**/*.vue`],name:`productive-eslint/analyze-types`,plugins:{"@typescript-eslint":Et},rules:Tt}),Jt=(e,t)=>!t.ruleId||!Dt.includes(t.ruleId)?null:{category:`internal`,column:t.column,confidence:`high`,file:e,labels:[],line:t.line,reasons:[Ot[t.ruleId]??t.message,t.message],ruleId:t.ruleId,score:kt[t.ruleId]??1,severity:At[t.ruleId]??`low`},Yt=async(e,t)=>{let n=await b({config:await qt(e).toConfigs(),context:t,ruleIds:Dt}),r=new Map;await Promise.all(n.map(async e=>{r.set(e.filePath,await Ut(t.cwd,e.filePath))}));let i=n.flatMap(e=>{let t=r.get(e.filePath);return e.messages.flatMap(n=>{let r=Jt(e.filePath,n);if(!r||!t)return r?[r]:[];let i=Kt(t,r);return[{...r,category:i.category,labels:[...new Set([...r.labels??[],...i.labels])],reasons:[...r.reasons,i.reason],score:Math.max(1,r.score+i.scoreDelta)}]})});if(t.top<1)throw new y(`--top must be at least 1.`);let a=x(i,t.top),o=a[0],s=o?.findings.every(e=>e.category===`control-flow-signal`)??!1,c=i.filter(e=>e.category===`exported-api`||e.category===`public-entry`).length,l=i.filter(e=>e.category===`vue-component-contract`||e.category===`shared-vue-component`).length,u=i.filter(e=>e.category===`shared-utility`).length,d=i.filter(e=>e.category===`control-flow-signal`).length,f=i.filter(e=>e.category===`test-only`).length;return{fileCount:n.length,findingsCount:i.length,nextStep:o?s?`Start with \`${o.file}\` and verify whether the redundant conditions are stale defensive code or outdated types.`:`Start with \`${o.file}\` and remove the highest-scoring type holes first.`:`No type analyzer findings were reported by the current analyzer ruleset.`,suggestedOrder:a.map(e=>e.file),summaries:a,summaryLines:[`Exported or public-surface findings: \`${c}\``,`Vue component contract findings: \`${l}\``,`Shared-utility findings: \`${u}\``,`Control-flow signal findings: \`${d}\``,`Test-only findings: \`${f}\``],title:`Types Analysis`}},Xt=[{analyze:Yt,label:`Types`,topic:`types`},{analyze:O,label:`Architecture`,topic:`architecture`},{analyze:z,label:`Complexity`,topic:`complexity`},{analyze:j,label:`Async`,topic:`async`},{analyze:wt,label:`Suppressions`,topic:`suppressions`},{analyze:V,label:`Dead Code`,topic:`dead-code`},{analyze:W,label:`Imports`,topic:`imports`},{analyze:w,label:`API`,topic:`api`}],Zt=e=>e>=10?`high`:e>=4?`medium`:`low`,Qt=(e,t)=>e.summaries.map(e=>({category:t.topic,confidence:`medium`,file:e.file,labels:[`risk`,`risk:${t.topic}`,...e.labels??[]],reasons:[`${t.label} analyzer reported score ${e.score} for this file.`,...(e.reasons??[]).slice(0,2)],ruleId:`productive/risk-${t.topic}`,score:e.score,severity:Zt(e.score)})),$t=async(e,t)=>{if(t.top<1)throw new y(`--top must be at least 1.`);let n=[];for(let r of Xt)n.push({analyzer:r,report:await r.analyze(e,t)});let r=x(n.flatMap(({analyzer:e,report:t})=>Qt(t,e)),t.top),i=r[0];return{fileCount:Math.max(0,...n.map(({report:e})=>e.fileCount)),findingsCount:n.reduce((e,{report:t})=>e+t.findingsCount,0),nextStep:i?`Start with \`${i.file}\`; it is the highest combined-risk file across the universal analyzer set.`:`No risk findings were reported by the universal analyzer set.`,suggestedOrder:r.map(e=>e.file),summaries:r,summaryLines:n.map(({analyzer:e,report:t})=>`${e.label} findings: \`${t.findingsCount}\``),title:`Risk Analysis`,topGroups:n.map(({analyzer:e,report:t})=>({count:t.findingsCount,label:e.label})).filter(e=>e.count>0)}},en={"rxjs/no-async-subscribe":`error`,"rxjs/no-exposed-subjects":`error`,"rxjs/no-ignored-error":`error`,"rxjs/no-ignored-observable":`error`,"rxjs/no-ignored-replay-buffer":`error`,"rxjs/no-ignored-subscribe":`error`,"rxjs/no-ignored-subscription":`error`,"rxjs/no-nested-subscribe":`error`,"rxjs/no-subject-unsubscribe":`error`,"rxjs/no-subject-value":`error`,"rxjs/no-topromise":`error`,"rxjs/no-unbound-methods":`error`,"rxjs/no-unsafe-switchmap":`error`,"rxjs/no-unsafe-takeuntil":`error`,"rxjs/throw-error":`error`},tn=_,Q=Object.keys(en),nn={"rxjs/no-async-subscribe":{category:`subscription-lifecycle`,labels:[`subscription-lifecycle`,`async-subscribe`],reason:`Async subscribe callbacks hide promise handling inside subscription lifecycle code.`,score:5,severity:`high`},"rxjs/no-exposed-subjects":{category:`state-exposure`,labels:[`state-exposure`,`subject-exposure`],reason:`Exposed subjects let callers push into internal reactive state.`,score:6,severity:`high`},"rxjs/no-ignored-error":{category:`error-handling`,labels:[`error-handling`,`missing-error-handler`],reason:`The subscription does not make the observable error path explicit.`,score:5,severity:`high`},"rxjs/no-ignored-observable":{category:`ignored-flow`,labels:[`ignored-flow`,`ignored-observable`],reason:`An observable is created but not returned, subscribed, or otherwise used.`,score:5,severity:`medium`},"rxjs/no-ignored-replay-buffer":{category:`state-exposure`,labels:[`state-exposure`,`unbounded-replay-buffer`],reason:`ReplaySubject without an explicit buffer size can retain more state than intended.`,score:4,severity:`medium`},"rxjs/no-ignored-subscribe":{category:`subscription-lifecycle`,labels:[`subscription-lifecycle`,`ignored-subscribe`],reason:`A subscription is started without making lifecycle ownership explicit.`,score:6,severity:`high`},"rxjs/no-ignored-subscription":{category:`subscription-lifecycle`,labels:[`subscription-lifecycle`,`ignored-subscription`],reason:`A subscription handle is ignored, making cleanup hard to verify.`,score:6,severity:`high`},"rxjs/no-nested-subscribe":{category:`flow-composition`,labels:[`flow-composition`,`nested-subscribe`],reason:`Nested subscriptions usually hide ordering and cleanup semantics.`,score:5,severity:`medium`},"rxjs/no-subject-unsubscribe":{category:`subscription-lifecycle`,labels:[`subscription-lifecycle`,`subject-unsubscribe`],reason:`Unsubscribing a Subject directly can break downstream reactive state unexpectedly.`,score:5,severity:`high`},"rxjs/no-subject-value":{category:`state-access`,labels:[`state-access`,`subject-value`],reason:`Reading Subject value directly bypasses normal observable flow.`,score:4,severity:`medium`},"rxjs/no-topromise":{category:`migration`,labels:[`migration`,`deprecated-api`],reason:`toPromise is deprecated and should be migrated to firstValueFrom or lastValueFrom.`,score:4,severity:`medium`},"rxjs/no-unbound-methods":{category:`flow-composition`,labels:[`flow-composition`,`unbound-method`],reason:`Passing unbound methods into reactive callbacks can lose the expected receiver.`,score:4,severity:`medium`},"rxjs/no-unsafe-switchmap":{category:`flow-cancellation`,labels:[`flow-cancellation`,`unsafe-switchmap`],reason:`Unsafe switchMap usage can cancel work that should be preserved.`,score:6,severity:`high`},"rxjs/no-unsafe-takeuntil":{category:`subscription-lifecycle`,labels:[`subscription-lifecycle`,`unsafe-takeuntil`],reason:`Operators after takeUntil can keep work alive past the intended teardown point.`,score:6,severity:`high`},"rxjs/throw-error":{category:`error-handling`,labels:[`error-handling`,`throw-error-factory`],reason:`RxJS throwError should preserve lazy error creation for reliable stacks and timing.`,score:4,severity:`medium`}},rn=e=>e.clone().append({files:[o],name:`productive-eslint/analyze-rxjs`,plugins:{rxjs:tn},rules:en}),an=(e,t)=>{if(!t.ruleId||!Q.includes(t.ruleId))return null;let n=nn[t.ruleId];return n?{category:n.category,column:t.column,confidence:`high`,file:e,labels:n.labels,line:t.line,reasons:[n.reason,t.message],ruleId:t.ruleId,score:n.score,severity:n.severity}:null},on=async(e,t)=>{if(t.top<1)throw new y(`--top must be at least 1.`);let n=await b({config:await rn(e).toConfigs(),context:t,ruleIds:Q}),r=n.flatMap(e=>e.messages.flatMap(t=>{let n=an(e.filePath,t);return n?[n]:[]})),i=x(r,t.top),a=i[0],o=r.filter(e=>e.category===`subscription-lifecycle`).length,s=r.filter(e=>[`flow-cancellation`,`flow-composition`,`ignored-flow`].includes(e.category??``)).length,c=r.filter(e=>[`state-access`,`state-exposure`].includes(e.category??``)).length,l=r.filter(e=>e.category===`error-handling`).length;return{fileCount:n.length,findingsCount:r.length,nextStep:a?`Start with \`${a.file}\` and clarify subscription ownership or reactive state boundaries first.`:`No RxJS flow findings were reported by the current analyzer ruleset.`,suggestedOrder:i.map(e=>e.file),summaries:i,summaryLines:[`Subscription lifecycle findings: \`${o}\``,`Reactive flow findings: \`${s}\``,`State boundary findings: \`${c}\``,`Error handling findings: \`${l}\``],title:`RxJS Analysis`}},sn={"vue/no-arrow-functions-in-watch":`error`,"vue/no-async-in-computed-properties":`error`,"vue/no-expose-after-await":`error`,"vue/no-lifecycle-after-await":`error`,"vue/no-mutating-props":`error`,"vue/no-ref-object-reactivity-loss":`error`,"vue/no-required-prop-with-default":`error`,"vue/no-side-effects-in-computed-properties":`error`,"vue/no-use-v-if-with-v-for":`error`,"vue/no-v-html":`error`,"vue/no-watch-after-await":`error`,"vue/require-explicit-emits":`error`,"vue/require-typed-object-prop":`error`,"vue/require-typed-ref":`error`,"vue/require-v-for-key":`error`,"vue/valid-v-html":`error`},cn=Object.keys(sn),ln={"vue/no-arrow-functions-in-watch":{category:`reactivity-hazard`,labels:[`reactivity-hazard`,`watch-this-binding`],reason:`Arrow functions in watchers can lose the component instance binding expected by Options API code.`,score:4,severity:`medium`},"vue/no-async-in-computed-properties":{category:`reactivity-hazard`,labels:[`reactivity-hazard`,`async-computed`],reason:`Async work inside computed properties makes reactivity timing unreliable.`,score:6,severity:`high`},"vue/no-expose-after-await":{category:`lifecycle-hazard`,labels:[`lifecycle-hazard`,`expose-after-await`],reason:`Bindings exposed after await can miss the synchronous setup contract.`,score:5,severity:`medium`},"vue/no-lifecycle-after-await":{category:`lifecycle-hazard`,labels:[`lifecycle-hazard`,`lifecycle-after-await`],reason:`Lifecycle hooks registered after await can miss component setup timing.`,score:5,severity:`medium`},"vue/no-mutating-props":{category:`component-contract`,labels:[`component-contract`,`prop-mutation`],reason:`Prop mutation breaks parent-owned component contracts.`,score:6,severity:`high`},"vue/no-ref-object-reactivity-loss":{category:`reactivity-hazard`,labels:[`reactivity-hazard`,`ref-reactivity-loss`],reason:`Destructuring or extracting ref object values can disconnect code from reactivity updates.`,score:5,severity:`medium`},"vue/no-required-prop-with-default":{category:`component-contract`,labels:[`component-contract`,`required-prop-default`],reason:`A prop cannot be both required from the parent and owned by a local default contract.`,score:4,severity:`medium`},"vue/no-side-effects-in-computed-properties":{category:`reactivity-hazard`,labels:[`reactivity-hazard`,`computed-side-effect`],reason:`Computed properties should stay pure so dependency tracking remains predictable.`,score:6,severity:`high`},"vue/no-use-v-if-with-v-for":{category:`template-correctness`,labels:[`template-correctness`,`v-if-with-v-for`],reason:`Combining v-if and v-for on one element can produce surprising render behavior.`,score:4,severity:`medium`},"vue/no-v-html":{category:`security`,labels:[`security`,`unsafe-html`],reason:`Raw HTML rendering can expose XSS risk when input is not fully trusted.`,score:7,severity:`high`},"vue/no-watch-after-await":{category:`lifecycle-hazard`,labels:[`lifecycle-hazard`,`watch-after-await`],reason:`Watchers registered after await can miss component lifecycle cleanup expectations.`,score:5,severity:`medium`},"vue/require-explicit-emits":{category:`component-contract`,labels:[`component-contract`,`emits-contract`],reason:`Component events should be declared as part of the public contract.`,score:4,severity:`medium`},"vue/require-typed-object-prop":{category:`component-contract`,labels:[`component-contract`,`object-prop-type`],reason:`Object props should declare their runtime shape precisely enough for safe component use.`,score:4,severity:`medium`},"vue/require-typed-ref":{category:`component-contract`,labels:[`component-contract`,`ref-type`],reason:`Untyped refs weaken component state contracts and make later reactive reads ambiguous.`,score:3,severity:`medium`},"vue/require-v-for-key":{category:`template-correctness`,labels:[`template-correctness`,`missing-key`],reason:`Missing keys make list updates harder for Vue to reconcile safely.`,score:4,severity:`medium`},"vue/valid-v-html":{category:`template-correctness`,labels:[`template-correctness`,`invalid-v-html`],reason:`Invalid v-html usage points to a broken template contract.`,score:4,severity:`medium`}},un=e=>e.clone().append({files:[t],name:`productive-eslint/analyze-vue`,rules:sn}),dn=(e,t)=>{if(!t.ruleId||!cn.includes(t.ruleId))return null;let n=ln[t.ruleId];return n?{category:n.category,column:t.column,confidence:`high`,file:e,labels:n.labels,line:t.line,reasons:[n.reason,t.message],ruleId:t.ruleId,score:n.score,severity:n.severity}:null},$={api:w,architecture:O,async:j,complexity:z,"dead-code":V,imports:W,migrations:ut,risk:$t,rxjs:on,suppressions:wt,types:Yt,vue:async(e,t)=>{if(t.top<1)throw new y(`--top must be at least 1.`);let n=await b({config:await un(e).toConfigs(),context:t,ruleIds:cn}),r=n.flatMap(e=>e.messages.flatMap(t=>{let n=dn(e.filePath,t);return n?[n]:[]})),i=x(r,t.top),a=i[0],o=r.filter(e=>e.category===`component-contract`).length,s=r.filter(e=>e.category===`reactivity-hazard`).length,c=r.filter(e=>e.category===`security`).length,l=r.filter(e=>e.category===`lifecycle-hazard`).length;return{fileCount:n.length,findingsCount:r.length,nextStep:a?`Start with \`${a.file}\` and fix component contract or reactivity hazards before template style cleanup.`:`No Vue semantic findings were reported by the current analyzer ruleset.`,suggestedOrder:i.map(e=>e.file),summaries:i,summaryLines:[`Component contract findings: \`${o}\``,`Reactivity hazard findings: \`${s}\``,`Lifecycle hazard findings: \`${l}\``,`Security findings: \`${c}\``],title:`Vue Analysis`}}},fn=e=>e in $,pn=async e=>{if(!fn(e.topic))throw new y(`Unknown analyzer topic "${e.topic}".`);let{composer:t}=await ie(e.cwd),n=$[e.topic];return v(await n(t,e))},mn=e=>{let[t,...n]=e;if(!t)throw new y(`Missing analyzer topic. Example: productive-eslint analyze types`);let r={cwd:c.cwd(),exclude:[],include:[],top:10,topic:t};for(let e=0;e<n.length;e+=1){let t=n[e],i=n[e+1];if(t===`--cwd`){if(!i)throw new y(`Missing value for --cwd.`);r.cwd=s.resolve(i),e+=1;continue}if(t===`--include`){if(!i)throw new y(`Missing value for --include.`);r.include.push(i),e+=1;continue}if(t===`--exclude`){if(!i)throw new y(`Missing value for --exclude.`);r.exclude.push(i),e+=1;continue}if(t===`--top`){if(!i)throw new y(`Missing value for --top.`);r.top=Number.parseInt(i,10),e+=1;continue}throw new y(`Unknown option "${t}".`)}if(!Number.isInteger(r.top)||r.top<1)throw new y(`--top must be a positive integer.`);return r};(async()=>{let[,,e,...t]=c.argv;if(e!==`analyze`)throw new y(`Unknown command. Supported command: productive-eslint analyze <topic>`);let n=await pn(mn(t));c.stdout.write(n)})().catch(e=>{let t=e instanceof Error?e.message:String(e);c.stderr.write(`${t}\n`),c.exitCode=1});export{};
|
package/dist/index.config.d.ts
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
import { FlatConfigComposer } from "eslint-flat-config-utils";
|
|
2
1
|
import { Linter } from "eslint";
|
|
2
|
+
import { FlatConfigComposer } from "eslint-flat-config-utils";
|
|
3
3
|
|
|
4
|
-
//#region src/utils/
|
|
4
|
+
//#region src/utils/presets.d.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Presets for ESLint config.
|
|
7
7
|
*
|
|
8
|
-
* - AutoFixable: only rules with ESLint autofix support
|
|
9
|
-
* -
|
|
10
|
-
* - Medium: easy + remaining rules from current config
|
|
11
|
-
* - Hard: easy + medium + extra rules (user-defined)
|
|
8
|
+
* - AutoFixable: only rules with ESLint autofix support.
|
|
9
|
+
* - Recommended: autofixable rules plus mechanical non-autofixable rules.
|
|
12
10
|
*/
|
|
13
|
-
|
|
14
|
-
declare enum StrictnessPreset {
|
|
11
|
+
declare enum Preset {
|
|
15
12
|
AUTO_FIXABLE = "autoFixable",
|
|
16
|
-
|
|
17
|
-
HARD = "hard",
|
|
18
|
-
MEDIUM = "medium"
|
|
13
|
+
RECOMMENDED = "recommended"
|
|
19
14
|
}
|
|
20
15
|
//#endregion
|
|
21
16
|
//#region src/index.config.d.ts
|
|
@@ -23,10 +18,10 @@ declare enum StrictnessPreset {
|
|
|
23
18
|
interface IOptions {
|
|
24
19
|
/** Files to ignore. Defaults to empty array. */
|
|
25
20
|
ignores?: string[];
|
|
21
|
+
/** Preset: autoFixable or recommended. Defaults to recommended. */
|
|
22
|
+
preset?: Preset;
|
|
26
23
|
/** Enable RxJS rules. Auto-detected from installed packages when not set. */
|
|
27
24
|
rxjs?: boolean;
|
|
28
|
-
/** Preset: autoFixable, easy, medium, or hard. Defaults to hard. */
|
|
29
|
-
strictness?: StrictnessPreset;
|
|
30
25
|
/** Enable Vue rules. Auto-detected from installed packages when not set. */
|
|
31
26
|
vue?: boolean;
|
|
32
27
|
}
|
|
@@ -36,17 +31,16 @@ interface IOptions {
|
|
|
36
31
|
* @param options The options for generating the ESLint configuration.
|
|
37
32
|
* @param options.ignores Additional glob patterns to ignore.
|
|
38
33
|
* @param options.rxjs Enable RxJS rules. Auto-detected when not set.
|
|
39
|
-
* @param options.
|
|
40
|
-
*
|
|
41
|
-
* medium + user rules). Defaults to hard.
|
|
34
|
+
* @param options.preset Preset: autoFixable (only auto-fixable rules) or
|
|
35
|
+
* recommended (permanent mechanical baseline). Defaults to recommended.
|
|
42
36
|
* @param options.vue Enable Vue rules. Auto-detected when not set.
|
|
43
37
|
* @returns The generated ESLint configuration.
|
|
44
38
|
*/
|
|
45
39
|
declare const createConfig: ({
|
|
46
40
|
ignores,
|
|
41
|
+
preset,
|
|
47
42
|
rxjs,
|
|
48
|
-
strictness,
|
|
49
43
|
vue
|
|
50
|
-
}
|
|
44
|
+
}?: IOptions) => FlatConfigComposer<Linter.Config>;
|
|
51
45
|
//#endregion
|
|
52
|
-
export { IOptions,
|
|
46
|
+
export { IOptions, Preset, createConfig };
|