projscan 0.3.1 → 0.5.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.
Files changed (60) hide show
  1. package/README.md +62 -1
  2. package/dist/analyzers/deadCodeCheck.d.ts +18 -0
  3. package/dist/analyzers/deadCodeCheck.js +168 -0
  4. package/dist/analyzers/deadCodeCheck.js.map +1 -0
  5. package/dist/analyzers/dependencyRiskCheck.js +29 -10
  6. package/dist/analyzers/dependencyRiskCheck.js.map +1 -1
  7. package/dist/analyzers/unusedDependencyCheck.d.ts +2 -0
  8. package/dist/analyzers/unusedDependencyCheck.js +144 -0
  9. package/dist/analyzers/unusedDependencyCheck.js.map +1 -0
  10. package/dist/cli/index.js +160 -4
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/core/auditRunner.d.ts +16 -0
  13. package/dist/core/auditRunner.js +222 -0
  14. package/dist/core/auditRunner.js.map +1 -0
  15. package/dist/core/coverageJoin.d.ts +6 -0
  16. package/dist/core/coverageJoin.js +50 -0
  17. package/dist/core/coverageJoin.js.map +1 -0
  18. package/dist/core/coverageParser.d.ts +15 -0
  19. package/dist/core/coverageParser.js +187 -0
  20. package/dist/core/coverageParser.js.map +1 -0
  21. package/dist/core/fileInspector.js +20 -13
  22. package/dist/core/fileInspector.js.map +1 -1
  23. package/dist/core/hotspotAnalyzer.d.ts +2 -0
  24. package/dist/core/hotspotAnalyzer.js +20 -1
  25. package/dist/core/hotspotAnalyzer.js.map +1 -1
  26. package/dist/core/importGraph.d.ts +31 -0
  27. package/dist/core/importGraph.js +139 -0
  28. package/dist/core/importGraph.js.map +1 -0
  29. package/dist/core/issueEngine.js +4 -0
  30. package/dist/core/issueEngine.js.map +1 -1
  31. package/dist/core/outdatedDetector.d.ts +9 -0
  32. package/dist/core/outdatedDetector.js +87 -0
  33. package/dist/core/outdatedDetector.js.map +1 -0
  34. package/dist/core/upgradePreview.d.ts +2 -0
  35. package/dist/core/upgradePreview.js +167 -0
  36. package/dist/core/upgradePreview.js.map +1 -0
  37. package/dist/index.d.ts +9 -1
  38. package/dist/index.js +8 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/mcp/tools.js +73 -0
  41. package/dist/mcp/tools.js.map +1 -1
  42. package/dist/reporters/consoleReporter.d.ts +5 -1
  43. package/dist/reporters/consoleReporter.js +170 -0
  44. package/dist/reporters/consoleReporter.js.map +1 -1
  45. package/dist/reporters/jsonReporter.d.ts +5 -1
  46. package/dist/reporters/jsonReporter.js +12 -0
  47. package/dist/reporters/jsonReporter.js.map +1 -1
  48. package/dist/reporters/markdownReporter.d.ts +5 -1
  49. package/dist/reporters/markdownReporter.js +111 -0
  50. package/dist/reporters/markdownReporter.js.map +1 -1
  51. package/dist/types.d.ts +77 -0
  52. package/dist/utils/banner.js +11 -7
  53. package/dist/utils/banner.js.map +1 -1
  54. package/dist/utils/packageJsonLocator.d.ts +11 -0
  55. package/dist/utils/packageJsonLocator.js +58 -0
  56. package/dist/utils/packageJsonLocator.js.map +1 -0
  57. package/dist/utils/semver.d.ts +13 -0
  58. package/dist/utils/semver.js +45 -0
  59. package/dist/utils/semver.js.map +1 -0
  60. package/package.json +2 -2
package/README.md CHANGED
@@ -68,6 +68,10 @@ projscan fix # Auto-fix detected issues
68
68
  projscan ci # CI health gate (exits 1 on low score)
69
69
  projscan ci --changed-only # Gate only on this PR's diff
70
70
  projscan ci --format sarif # SARIF 2.1.0 for GitHub Code Scanning
71
+ projscan outdated # Declared-vs-installed drift (offline)
72
+ projscan audit # npm audit, normalized + SARIF-ready
73
+ projscan upgrade <pkg> # Preview upgrade impact (local CHANGELOG + importers)
74
+ projscan coverage # Coverage × hotspots — scariest untested files
71
75
  projscan diff # Compare health + hotspot trends against a baseline
72
76
  projscan diagram # Architecture visualization
73
77
  projscan structure # Directory tree
@@ -93,6 +97,10 @@ For a comprehensive walkthrough, see the **[Full Guide](docs/GUIDE.md)**.
93
97
  | `projscan diagram` | ASCII architecture diagram of your project |
94
98
  | `projscan structure` | Directory tree with file counts |
95
99
  | `projscan dependencies` | Dependency analysis — counts, risks, recommendations |
100
+ | `projscan outdated` | Declared-vs-installed drift check (offline) |
101
+ | `projscan audit` | `npm audit`-powered vulnerability report — SARIF-ready for Code Scanning |
102
+ | `projscan upgrade <pkg>` | Preview upgrade impact — local CHANGELOG + importer list, offline |
103
+ | `projscan coverage` | **Coverage × hotspots — rank the scariest untested files** |
96
104
  | `projscan badge` | Generate a health score badge for your README |
97
105
  | `projscan mcp` | Run as an MCP server for AI coding agents (Claude Code, Cursor, …) |
98
106
 
@@ -329,11 +337,60 @@ projscan diff # Shows which hotspots rose / fell
329
337
 
330
338
  The baseline file now captures top hotspots too, so `diff` surfaces files that are **getting worse** (not just new issues).
331
339
 
340
+ ## Dependency Health
341
+
342
+ projscan ships three focused commands for keeping your dependency graph healthy — all **offline** by default, no registry calls.
343
+
344
+ ```bash
345
+ projscan outdated # Which declared deps drift from what's installed?
346
+ projscan outdated --format json # Machine-readable drift report
347
+ projscan audit # Wrap npm audit; normalized, SARIF-ready
348
+ projscan audit --format sarif > a.sarif # Upload to GitHub Code Scanning
349
+ projscan upgrade chalk # What breaks if I bump chalk? Who imports it?
350
+ projscan upgrade chalk --format markdown # Paste-ready review comment
351
+ ```
352
+
353
+ ### What each one tells you
354
+
355
+ - **`outdated`** — reads `package.json` and `node_modules/<pkg>/package.json` to classify drift (`major` / `minor` / `patch` / `same` / `unknown`). No network.
356
+ - **`audit`** — wraps `npm audit --json`, normalizes the output, and emits SARIF with per-finding rules anchored to `package.json`. Graceful fallback message for yarn/pnpm projects.
357
+ - **`upgrade <pkg>`** — reads `node_modules/<pkg>/CHANGELOG.md`, slices the section between your installed version and the previous one, flags `BREAKING CHANGE` / `deprecated` / `removed support` markers, and lists every file in your repo that imports the package. All offline.
358
+
359
+ ### Unused dependencies (automatic in `doctor`)
360
+
361
+ `projscan doctor` now flags declared dependencies that are never imported from source. Each finding is anchored to the **exact line in `package.json`** so GitHub Code Scanning PR annotations land in the right place.
362
+
363
+ Implicit-use packages (typescript, eslint/prettier plugins, `@types/*`, and anything invoked from a `package.json` script) are allowlisted. Override via `.projscanrc` → `disableRules` if projscan flags something that is used but not imported.
364
+
365
+ ## Coverage × Hotspots — Scariest Untested Files
366
+
367
+ `projscan coverage` joins your test coverage with the hotspot ranking. A file with high churn and low coverage is where a bug is most likely to bite you — so that's where you want tests first.
368
+
369
+ ```bash
370
+ projscan coverage # Top 30 scariest untested files
371
+ projscan coverage --format markdown # Paste into a tech-debt ticket
372
+ projscan coverage --format json # Machine-readable for dashboards
373
+ ```
374
+
375
+ **How it decides "scariest":** `priority = riskScore × (0.3 + 0.7 × uncoveredFraction)` — so a file with 50 risk and 10% coverage outranks a file with 50 risk and 95% coverage.
376
+
377
+ **Which coverage files are supported:**
378
+
379
+ - `coverage/lcov.info` (lcov — Vitest, Jest, c8)
380
+ - `coverage/coverage-final.json` (Istanbul per-file detail)
381
+ - `coverage/coverage-summary.json` (Istanbul summary)
382
+
383
+ Coverage is also automatically joined into `projscan hotspots` when one of those files exists — no flag needed. Uncovered churning files get a score bump and a `low coverage (X%)` reason tag.
384
+
385
+ ### Dead-code detection (automatic in `doctor`)
386
+
387
+ `projscan doctor` now flags source files whose exports nothing imports — dead code left over from refactors or utilities that were never wired up. Respects `package.json` public entry points (`main`, `exports`, `bin`, `types`), skips test files and barrel (`index`) files.
388
+
332
389
  ## AI Agent Integration (MCP)
333
390
 
334
391
  **`projscan mcp`** starts an [MCP](https://modelcontextprotocol.io) server over stdio so AI coding agents can query projscan during a session.
335
392
 
336
- **Tools** (7):
393
+ **Tools** (11):
337
394
  - `projscan_analyze` — full project report
338
395
  - `projscan_doctor` — health score + issues
339
396
  - `projscan_hotspots` — risk-ranked files (with `limit`, `since` args)
@@ -341,6 +398,10 @@ The baseline file now captures top hotspots too, so `diff` surfaces files that a
341
398
  - `projscan_explain` — per-file purpose, imports, exports, smells
342
399
  - `projscan_structure` — directory tree
343
400
  - `projscan_dependencies` — package audit
401
+ - `projscan_outdated` — declared-vs-installed drift
402
+ - `projscan_audit` — npm audit, normalized
403
+ - `projscan_upgrade` — offline upgrade preview with CHANGELOG + importers
404
+ - `projscan_coverage` — coverage × hotspots, "scariest untested files"
344
405
 
345
406
  **Prompts** (2, parameterized with live project data):
346
407
  - `prioritize_refactoring` — ranked plan grounded in current hotspots
@@ -0,0 +1,18 @@
1
+ import type { FileEntry, Issue } from '../types.js';
2
+ /**
3
+ * Flag exports that are never imported anywhere in the project. This catches:
4
+ * - dead named exports left over from refactors
5
+ * - utilities that are implemented but never hooked up
6
+ *
7
+ * Does NOT flag:
8
+ * - files listed as the package's public entry points (main, exports, types, bin)
9
+ * - default exports (too many false positives — framework conventions)
10
+ * - test files (they're not supposed to export)
11
+ * - index files (barrels re-export for public use)
12
+ *
13
+ * False-positive guard: if a file is the target of at least one import, we treat
14
+ * all its exports as "possibly used" — the regex-based graph can't tell which
15
+ * named export is imported via `import { ... } from './barrel'`. This keeps
16
+ * noise low at the cost of missing some dead exports that live in used files.
17
+ */
18
+ export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;
@@ -0,0 +1,168 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { buildImportGraph } from '../core/importGraph.js';
4
+ import { extractExports } from '../core/fileInspector.js';
5
+ const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts']);
6
+ // Never flag these — they're public API by definition
7
+ const PUBLIC_PATH_PREFIXES = ['src/index', 'index.'];
8
+ /**
9
+ * Flag exports that are never imported anywhere in the project. This catches:
10
+ * - dead named exports left over from refactors
11
+ * - utilities that are implemented but never hooked up
12
+ *
13
+ * Does NOT flag:
14
+ * - files listed as the package's public entry points (main, exports, types, bin)
15
+ * - default exports (too many false positives — framework conventions)
16
+ * - test files (they're not supposed to export)
17
+ * - index files (barrels re-export for public use)
18
+ *
19
+ * False-positive guard: if a file is the target of at least one import, we treat
20
+ * all its exports as "possibly used" — the regex-based graph can't tell which
21
+ * named export is imported via `import { ... } from './barrel'`. This keeps
22
+ * noise low at the cost of missing some dead exports that live in used files.
23
+ */
24
+ export async function check(rootPath, files) {
25
+ const sourceFiles = files.filter((f) => SOURCE_EXTENSIONS.has(f.extension));
26
+ if (sourceFiles.length === 0)
27
+ return [];
28
+ const publicEntries = await loadPublicEntries(rootPath);
29
+ const graph = await buildImportGraph(rootPath, sourceFiles);
30
+ // Build a set of files that are the target of at least one relative import.
31
+ // A relative import specifier is resolved against its importing file's dir,
32
+ // so we convert each relative specifier into a candidate target path.
33
+ const importedTargets = new Set();
34
+ for (const [importingFile, specifiers] of graph.byFile) {
35
+ const importingDir = path.posix.dirname(importingFile);
36
+ for (const spec of specifiers) {
37
+ if (!spec.startsWith('.'))
38
+ continue;
39
+ const resolved = path.posix.normalize(path.posix.join(importingDir, spec));
40
+ for (const candidate of resolutionCandidates(resolved)) {
41
+ importedTargets.add(candidate);
42
+ }
43
+ }
44
+ }
45
+ const issues = [];
46
+ for (const file of sourceFiles) {
47
+ if (isTestFile(file.relativePath))
48
+ continue;
49
+ if (isBarrelFile(file.relativePath))
50
+ continue;
51
+ if (isPublicEntry(file.relativePath, publicEntries))
52
+ continue;
53
+ if (importedTargets.has(file.relativePath))
54
+ continue;
55
+ if (importedTargets.has(stripExtension(file.relativePath)))
56
+ continue;
57
+ let content;
58
+ try {
59
+ content = await fs.readFile(file.absolutePath, 'utf-8');
60
+ }
61
+ catch {
62
+ continue;
63
+ }
64
+ const exports = extractExports(content).filter((e) => e.type !== 'default' && e.name !== 'default');
65
+ if (exports.length === 0)
66
+ continue;
67
+ issues.push({
68
+ id: `unused-exports-${file.relativePath}`,
69
+ title: `Unused exports in ${file.relativePath}`,
70
+ description: `${exports.length} named export${exports.length === 1 ? '' : 's'} (${exports
71
+ .slice(0, 5)
72
+ .map((e) => e.name)
73
+ .join(', ')}${exports.length > 5 ? `, … +${exports.length - 5}` : ''}) but nothing in the project imports this file. Dead code or awaiting wiring?`,
74
+ severity: 'info',
75
+ category: 'architecture',
76
+ fixAvailable: false,
77
+ locations: [{ file: file.relativePath, line: 1 }],
78
+ });
79
+ }
80
+ return issues;
81
+ }
82
+ function isTestFile(relativePath) {
83
+ return (relativePath.includes('.test.') ||
84
+ relativePath.includes('.spec.') ||
85
+ relativePath.includes('__tests__') ||
86
+ relativePath.startsWith('tests/'));
87
+ }
88
+ function isBarrelFile(relativePath) {
89
+ const base = path.basename(relativePath, path.extname(relativePath));
90
+ return base === 'index';
91
+ }
92
+ function isPublicEntry(relativePath, publicEntries) {
93
+ if (publicEntries.has(relativePath))
94
+ return true;
95
+ if (publicEntries.has(stripExtension(relativePath)))
96
+ return true;
97
+ for (const prefix of PUBLIC_PATH_PREFIXES) {
98
+ if (relativePath === prefix || relativePath.startsWith(prefix))
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+ function stripExtension(p) {
104
+ const ext = path.extname(p);
105
+ return ext ? p.slice(0, -ext.length) : p;
106
+ }
107
+ /**
108
+ * Return the set of possible resolution targets for a relative import
109
+ * specifier (already joined+normalized). For './foo' we yield:
110
+ * foo, foo.ts, foo.tsx, foo.js, foo.jsx, foo.mjs, foo.cjs,
111
+ * foo/index.ts, foo/index.tsx, foo/index.js, ...
112
+ */
113
+ function resolutionCandidates(base) {
114
+ const exts = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'];
115
+ const out = [base];
116
+ for (const ext of exts) {
117
+ out.push(base + ext);
118
+ out.push(base + '/index' + ext);
119
+ }
120
+ // Handle imports written with an explicit ".js" that actually resolve to a .ts file (ESM+NodeNext)
121
+ if (base.endsWith('.js')) {
122
+ const noJs = base.slice(0, -3);
123
+ out.push(noJs + '.ts', noJs + '.tsx');
124
+ }
125
+ return out;
126
+ }
127
+ async function loadPublicEntries(rootPath) {
128
+ const entries = new Set();
129
+ const pkgPath = path.join(rootPath, 'package.json');
130
+ try {
131
+ const raw = await fs.readFile(pkgPath, 'utf-8');
132
+ const pkg = JSON.parse(raw);
133
+ for (const value of [pkg.main, pkg.types, pkg.typings]) {
134
+ if (typeof value === 'string')
135
+ addNormalized(entries, value);
136
+ }
137
+ if (typeof pkg.bin === 'string')
138
+ addNormalized(entries, pkg.bin);
139
+ else if (pkg.bin && typeof pkg.bin === 'object') {
140
+ for (const value of Object.values(pkg.bin))
141
+ addNormalized(entries, value);
142
+ }
143
+ collectExports(pkg.exports, entries);
144
+ }
145
+ catch {
146
+ // package.json missing/unreadable — don't guard, every file is a candidate
147
+ }
148
+ return entries;
149
+ }
150
+ function addNormalized(set, value) {
151
+ const cleaned = value.replace(/^\.\//, '').replace(/^\//, '');
152
+ set.add(cleaned);
153
+ set.add(stripExtension(cleaned));
154
+ }
155
+ function collectExports(exportsField, out) {
156
+ if (!exportsField)
157
+ return;
158
+ if (typeof exportsField === 'string') {
159
+ addNormalized(out, exportsField);
160
+ return;
161
+ }
162
+ if (typeof exportsField !== 'object')
163
+ return;
164
+ for (const value of Object.values(exportsField)) {
165
+ collectExports(value, out);
166
+ }
167
+ }
168
+ //# sourceMappingURL=deadCodeCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deadCodeCheck.js","sourceRoot":"","sources":["../../src/analyzers/deadCodeCheck.ts"],"names":[],"mappings":"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"}
@@ -1,17 +1,36 @@
1
1
  import { analyzeDependencies } from '../core/dependencyAnalyzer.js';
2
+ import { findDependencyLines } from '../utils/packageJsonLocator.js';
3
+ const PROJECT_LEVEL_RISKS = new Set([
4
+ 'excessive-dependencies',
5
+ 'many-dependencies',
6
+ 'no-lockfile',
7
+ ]);
2
8
  export async function check(rootPath, _files) {
3
9
  const report = await analyzeDependencies(rootPath);
4
10
  if (!report)
5
11
  return [];
6
- return report.risks.map((risk) => ({
7
- id: `dep-risk-${risk.name}`,
8
- title: risk.name === 'excessive-dependencies' || risk.name === 'many-dependencies' || risk.name === 'no-lockfile'
9
- ? risk.reason
10
- : `Dependency risk: ${risk.name}`,
11
- description: risk.reason,
12
- severity: risk.severity === 'high' ? 'error' : risk.severity === 'medium' ? 'warning' : 'info',
13
- category: 'dependencies',
14
- fixAvailable: false,
15
- }));
12
+ const locations = await findDependencyLines(rootPath);
13
+ return report.risks.map((risk) => {
14
+ const isProjectLevel = PROJECT_LEVEL_RISKS.has(risk.name);
15
+ const line = !isProjectLevel ? locations?.lineOfDependency.get(risk.name) : undefined;
16
+ const issueLocations = isProjectLevel
17
+ ? [{ file: 'package.json' }]
18
+ : line
19
+ ? [{ file: 'package.json', line }]
20
+ : [{ file: 'package.json' }];
21
+ return {
22
+ id: `dep-risk-${risk.name}`,
23
+ title: isProjectLevel ? risk.reason : `Dependency risk: ${risk.name}`,
24
+ description: risk.reason,
25
+ severity: risk.severity === 'high'
26
+ ? 'error'
27
+ : risk.severity === 'medium'
28
+ ? 'warning'
29
+ : 'info',
30
+ category: 'dependencies',
31
+ fixAvailable: false,
32
+ locations: issueLocations,
33
+ };
34
+ });
16
35
  }
17
36
  //# sourceMappingURL=dependencyRiskCheck.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dependencyRiskCheck.js","sourceRoot":"","sources":["../../src/analyzers/dependencyRiskCheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAGpE,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,QAAgB,EAAE,MAAmB;IAC/D,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEvB,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjC,EAAE,EAAE,YAAY,IAAI,CAAC,IAAI,EAAE;QAC3B,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK,wBAAwB,IAAI,IAAI,CAAC,IAAI,KAAK,mBAAmB,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa;YAC/G,CAAC,CAAC,IAAI,CAAC,MAAM;YACb,CAAC,CAAC,oBAAoB,IAAI,CAAC,IAAI,EAAE;QACnC,WAAW,EAAE,IAAI,CAAC,MAAM;QACxB,QAAQ,EAAE,IAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,OAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAkB,CAAC,CAAC,CAAC,MAAe;QACzH,QAAQ,EAAE,cAAc;QACxB,YAAY,EAAE,KAAK;KACpB,CAAC,CAAC,CAAC;AACN,CAAC"}
1
+ {"version":3,"file":"dependencyRiskCheck.js","sourceRoot":"","sources":["../../src/analyzers/dependencyRiskCheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAGrE,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,wBAAwB;IACxB,mBAAmB;IACnB,aAAa;CACd,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,QAAgB,EAAE,MAAmB;IAC/D,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEvB,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAEtD,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAS,EAAE;QACtC,MAAM,cAAc,GAAG,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtF,MAAM,cAAc,GAAgC,cAAc;YAChE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;YAC5B,CAAC,CAAC,IAAI;gBACJ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;gBAClC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;QAEjC,OAAO;YACL,EAAE,EAAE,YAAY,IAAI,CAAC,IAAI,EAAE;YAC3B,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,oBAAoB,IAAI,CAAC,IAAI,EAAE;YACrE,WAAW,EAAE,IAAI,CAAC,MAAM;YACxB,QAAQ,EACN,IAAI,CAAC,QAAQ,KAAK,MAAM;gBACtB,CAAC,CAAE,OAAiB;gBACpB,CAAC,CAAC,IAAI,CAAC,QAAQ,KAAK,QAAQ;oBAC1B,CAAC,CAAE,SAAmB;oBACtB,CAAC,CAAE,MAAgB;YACzB,QAAQ,EAAE,cAAc;YACxB,YAAY,EAAE,KAAK;YACnB,SAAS,EAAE,cAAc;SAC1B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { FileEntry, Issue } from '../types.js';
2
+ export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;
@@ -0,0 +1,144 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { buildImportGraph } from '../core/importGraph.js';
4
+ import { findDependencyLines } from '../utils/packageJsonLocator.js';
5
+ /**
6
+ * Patterns for packages that are typically not imported directly from source
7
+ * but are still legitimately used (plugins, configs, types, peer tooling).
8
+ * Without this allowlist we'd flag everything in devDependencies.
9
+ */
10
+ const IMPLICIT_USE_PREFIXES = [
11
+ '@types/',
12
+ 'eslint-plugin-',
13
+ 'eslint-config-',
14
+ 'prettier-plugin-',
15
+ 'postcss-plugin-',
16
+ 'rollup-plugin-',
17
+ 'vite-plugin-',
18
+ 'babel-plugin-',
19
+ 'babel-preset-',
20
+ 'stylelint-plugin-',
21
+ 'stylelint-config-',
22
+ ];
23
+ const IMPLICIT_USE_EXACT = new Set([
24
+ 'typescript',
25
+ 'ts-node',
26
+ 'tsx',
27
+ 'tsup',
28
+ 'esbuild',
29
+ 'vite',
30
+ 'webpack',
31
+ 'rollup',
32
+ 'parcel',
33
+ 'eslint',
34
+ 'prettier',
35
+ 'stylelint',
36
+ 'husky',
37
+ 'lint-staged',
38
+ 'commitlint',
39
+ '@commitlint/config-conventional',
40
+ 'semantic-release',
41
+ 'nx',
42
+ 'lerna',
43
+ 'rimraf',
44
+ 'cross-env',
45
+ 'concurrently',
46
+ 'nodemon',
47
+ 'npm-run-all',
48
+ 'only-allow',
49
+ 'zx',
50
+ 'chokidar',
51
+ 'react-scripts',
52
+ 'next',
53
+ 'nuxt',
54
+ 'vitest',
55
+ 'jest',
56
+ 'mocha',
57
+ 'ava',
58
+ 'tap',
59
+ 'jasmine',
60
+ '@playwright/test',
61
+ 'cypress',
62
+ 'storybook',
63
+ '@storybook/react',
64
+ ]);
65
+ function isImplicitlyUsed(pkg) {
66
+ if (IMPLICIT_USE_EXACT.has(pkg))
67
+ return true;
68
+ return IMPLICIT_USE_PREFIXES.some((prefix) => pkg.startsWith(prefix));
69
+ }
70
+ export async function check(rootPath, files) {
71
+ const pkgPath = path.join(rootPath, 'package.json');
72
+ let raw;
73
+ try {
74
+ raw = await fs.readFile(pkgPath, 'utf-8');
75
+ }
76
+ catch {
77
+ return [];
78
+ }
79
+ let pkg;
80
+ try {
81
+ pkg = JSON.parse(raw);
82
+ }
83
+ catch {
84
+ return [];
85
+ }
86
+ const dependencies = (pkg.dependencies ?? {});
87
+ const devDependencies = (pkg.devDependencies ?? {});
88
+ const allDeclared = new Set([...Object.keys(dependencies), ...Object.keys(devDependencies)]);
89
+ if (allDeclared.size === 0)
90
+ return [];
91
+ const graph = await buildImportGraph(rootPath, files);
92
+ // Also treat packages invoked from package.json scripts as used.
93
+ // e.g., "build": "tsc" means we should not flag typescript.
94
+ const scriptUsedBinaries = extractScriptBinaries(pkg);
95
+ const locations = await findDependencyLines(rootPath);
96
+ const unused = [];
97
+ for (const name of allDeclared) {
98
+ if (graph.externalPackages.has(name))
99
+ continue;
100
+ if (isImplicitlyUsed(name))
101
+ continue;
102
+ if (scriptUsedBinaries.has(name))
103
+ continue;
104
+ // skip scoped bin lookups (e.g., "npx some-tool") — covered by scriptUsedBinaries
105
+ const isDev = name in devDependencies;
106
+ const line = locations?.lineOfDependency.get(name);
107
+ unused.push({
108
+ id: `unused-dependency-${name}`,
109
+ title: `Unused ${isDev ? 'dev' : ''} dependency: ${name}`.replace(' ', ' ').trim(),
110
+ description: `The package "${name}" is declared in package.json but never imported from source. If it's used only in package.json scripts or as a plugin, add it to the projscan allowlist via .projscanrc → disableRules.`,
111
+ severity: isDev ? 'info' : 'warning',
112
+ category: 'dependencies',
113
+ fixAvailable: false,
114
+ locations: locations
115
+ ? [
116
+ {
117
+ file: 'package.json',
118
+ line: line ?? 1,
119
+ },
120
+ ]
121
+ : undefined,
122
+ });
123
+ }
124
+ return unused;
125
+ }
126
+ function extractScriptBinaries(pkg) {
127
+ const scripts = (pkg.scripts ?? {});
128
+ const bins = new Set();
129
+ for (const value of Object.values(scripts)) {
130
+ for (const token of value.split(/[\s&|;]+/)) {
131
+ if (!token)
132
+ continue;
133
+ if (token.startsWith('-'))
134
+ continue;
135
+ if (token.includes('/') || token.includes('\\'))
136
+ continue;
137
+ if (!/^[@\w][\w@\-/]*$/.test(token))
138
+ continue;
139
+ bins.add(token);
140
+ }
141
+ }
142
+ return bins;
143
+ }
144
+ //# sourceMappingURL=unusedDependencyCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unusedDependencyCheck.js","sourceRoot":"","sources":["../../src/analyzers/unusedDependencyCheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAErE;;;;GAIG;AACH,MAAM,qBAAqB,GAAG;IAC5B,SAAS;IACT,gBAAgB;IAChB,gBAAgB;IAChB,kBAAkB;IAClB,iBAAiB;IACjB,gBAAgB;IAChB,cAAc;IACd,eAAe;IACf,eAAe;IACf,mBAAmB;IACnB,mBAAmB;CACpB,CAAC;AAEF,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,YAAY;IACZ,SAAS;IACT,KAAK;IACL,MAAM;IACN,SAAS;IACT,MAAM;IACN,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,WAAW;IACX,OAAO;IACP,aAAa;IACb,YAAY;IACZ,iCAAiC;IACjC,kBAAkB;IAClB,IAAI;IACJ,OAAO;IACP,QAAQ;IACR,WAAW;IACX,cAAc;IACd,SAAS;IACT,aAAa;IACb,YAAY;IACZ,IAAI;IACJ,UAAU;IACV,eAAe;IACf,MAAM;IACN,MAAM;IACN,QAAQ;IACR,MAAM;IACN,OAAO;IACP,KAAK;IACL,KAAK;IACL,SAAS;IACT,kBAAkB;IAClB,SAAS;IACT,WAAW;IACX,kBAAkB;CACnB,CAAC,CAAC;AAEH,SAAS,gBAAgB,CAAC,GAAW;IACnC,IAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO,qBAAqB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,QAAgB,EAAE,KAAkB;IAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IACpD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,GAA4B,CAAC;IACjC,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAA2B,CAAC;IACxE,MAAM,eAAe,GAAG,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAA2B,CAAC;IAC9E,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC7F,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtC,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAEtD,iEAAiE;IACjE,4DAA4D;IAC5D,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAEtD,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC/C,IAAI,gBAAgB,CAAC,IAAI,CAAC;YAAE,SAAS;QACrC,IAAI,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC3C,kFAAkF;QAElF,MAAM,KAAK,GAAG,IAAI,IAAI,eAAe,CAAC;QACtC,MAAM,IAAI,GAAG,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEnD,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,qBAAqB,IAAI,EAAE;YAC/B,KAAK,EAAE,UAAU,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,gBAAgB,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE;YACnF,WAAW,EAAE,gBAAgB,IAAI,0LAA0L;YAC3N,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;YACpC,QAAQ,EAAE,cAAc;YACxB,YAAY,EAAE,KAAK;YACnB,SAAS,EAAE,SAAS;gBAClB,CAAC,CAAC;oBACE;wBACE,IAAI,EAAE,cAAc;wBACpB,IAAI,EAAE,IAAI,IAAI,CAAC;qBAChB;iBACF;gBACH,CAAC,CAAC,SAAS;SACd,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,qBAAqB,CAAC,GAA4B;IACzD,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAA2B,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5C,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YACpC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC1D,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC;gBAAE,SAAS;YAC9C,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}