projscan 0.11.0 → 0.14.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 (278) hide show
  1. package/README.md +57 -33
  2. package/dist/analyzers/crossPackageImportCheck.d.ts +13 -0
  3. package/dist/analyzers/crossPackageImportCheck.js +136 -0
  4. package/dist/analyzers/crossPackageImportCheck.js.map +1 -0
  5. package/dist/analyzers/cycleCheck.d.ts +12 -0
  6. package/dist/analyzers/cycleCheck.js +65 -0
  7. package/dist/analyzers/cycleCheck.js.map +1 -0
  8. package/dist/analyzers/unusedDependencyCheck.js +69 -17
  9. package/dist/analyzers/unusedDependencyCheck.js.map +1 -1
  10. package/dist/cli/_shared.d.ts +16 -0
  11. package/dist/cli/_shared.js +210 -0
  12. package/dist/cli/_shared.js.map +1 -0
  13. package/dist/cli/commands/analyze.d.ts +1 -0
  14. package/dist/cli/commands/analyze.js +87 -0
  15. package/dist/cli/commands/analyze.js.map +1 -0
  16. package/dist/cli/commands/audit.d.ts +1 -0
  17. package/dist/cli/commands/audit.js +53 -0
  18. package/dist/cli/commands/audit.js.map +1 -0
  19. package/dist/cli/commands/badge.d.ts +1 -0
  20. package/dist/cli/commands/badge.js +45 -0
  21. package/dist/cli/commands/badge.js.map +1 -0
  22. package/dist/cli/commands/ci.d.ts +1 -0
  23. package/dist/cli/commands/ci.js +57 -0
  24. package/dist/cli/commands/ci.js.map +1 -0
  25. package/dist/cli/commands/coupling.d.ts +1 -0
  26. package/dist/cli/commands/coupling.js +83 -0
  27. package/dist/cli/commands/coupling.js.map +1 -0
  28. package/dist/cli/commands/coverage.d.ts +1 -0
  29. package/dist/cli/commands/coverage.js +63 -0
  30. package/dist/cli/commands/coverage.js.map +1 -0
  31. package/dist/cli/commands/dependencies.d.ts +1 -0
  32. package/dist/cli/commands/dependencies.js +46 -0
  33. package/dist/cli/commands/dependencies.js.map +1 -0
  34. package/dist/cli/commands/diagram.d.ts +1 -0
  35. package/dist/cli/commands/diagram.js +45 -0
  36. package/dist/cli/commands/diagram.js.map +1 -0
  37. package/dist/cli/commands/diff.d.ts +1 -0
  38. package/dist/cli/commands/diff.js +70 -0
  39. package/dist/cli/commands/diff.js.map +1 -0
  40. package/dist/cli/commands/doctor.d.ts +1 -0
  41. package/dist/cli/commands/doctor.js +62 -0
  42. package/dist/cli/commands/doctor.js.map +1 -0
  43. package/dist/cli/commands/explain.d.ts +1 -0
  44. package/dist/cli/commands/explain.js +42 -0
  45. package/dist/cli/commands/explain.js.map +1 -0
  46. package/dist/cli/commands/explainIssue.d.ts +1 -0
  47. package/dist/cli/commands/explainIssue.js +49 -0
  48. package/dist/cli/commands/explainIssue.js.map +1 -0
  49. package/dist/cli/commands/file.d.ts +1 -0
  50. package/dist/cli/commands/file.js +45 -0
  51. package/dist/cli/commands/file.js.map +1 -0
  52. package/dist/cli/commands/fix.d.ts +1 -0
  53. package/dist/cli/commands/fix.js +70 -0
  54. package/dist/cli/commands/fix.js.map +1 -0
  55. package/dist/cli/commands/fixSuggest.d.ts +1 -0
  56. package/dist/cli/commands/fixSuggest.js +71 -0
  57. package/dist/cli/commands/fixSuggest.js.map +1 -0
  58. package/dist/cli/commands/help.d.ts +1 -0
  59. package/dist/cli/commands/help.js +11 -0
  60. package/dist/cli/commands/help.js.map +1 -0
  61. package/dist/cli/commands/hotspots.d.ts +1 -0
  62. package/dist/cli/commands/hotspots.js +74 -0
  63. package/dist/cli/commands/hotspots.js.map +1 -0
  64. package/dist/cli/commands/mcp.d.ts +1 -0
  65. package/dist/cli/commands/mcp.js +21 -0
  66. package/dist/cli/commands/mcp.js.map +1 -0
  67. package/dist/cli/commands/outdated.d.ts +1 -0
  68. package/dist/cli/commands/outdated.js +51 -0
  69. package/dist/cli/commands/outdated.js.map +1 -0
  70. package/dist/cli/commands/prDiff.d.ts +1 -0
  71. package/dist/cli/commands/prDiff.js +59 -0
  72. package/dist/cli/commands/prDiff.js.map +1 -0
  73. package/dist/cli/commands/review.d.ts +1 -0
  74. package/dist/cli/commands/review.js +66 -0
  75. package/dist/cli/commands/review.js.map +1 -0
  76. package/dist/cli/commands/search.d.ts +1 -0
  77. package/dist/cli/commands/search.js +233 -0
  78. package/dist/cli/commands/search.js.map +1 -0
  79. package/dist/cli/commands/structure.d.ts +1 -0
  80. package/dist/cli/commands/structure.js +58 -0
  81. package/dist/cli/commands/structure.js.map +1 -0
  82. package/dist/cli/commands/upgrade.d.ts +1 -0
  83. package/dist/cli/commands/upgrade.js +44 -0
  84. package/dist/cli/commands/upgrade.js.map +1 -0
  85. package/dist/cli/commands/workspaces.d.ts +1 -0
  86. package/dist/cli/commands/workspaces.js +35 -0
  87. package/dist/cli/commands/workspaces.js.map +1 -0
  88. package/dist/cli/index.js +51 -1416
  89. package/dist/cli/index.js.map +1 -1
  90. package/dist/core/ast.d.ts +20 -0
  91. package/dist/core/ast.js +190 -0
  92. package/dist/core/ast.js.map +1 -1
  93. package/dist/core/auditRunner.d.ts +8 -0
  94. package/dist/core/auditRunner.js +50 -1
  95. package/dist/core/auditRunner.js.map +1 -1
  96. package/dist/core/codeGraph.d.ts +7 -1
  97. package/dist/core/codeGraph.js +2 -0
  98. package/dist/core/codeGraph.js.map +1 -1
  99. package/dist/core/couplingAnalyzer.d.ts +1 -1
  100. package/dist/core/couplingAnalyzer.js +3 -3
  101. package/dist/core/dependencyAnalyzer.d.ts +15 -1
  102. package/dist/core/dependencyAnalyzer.js +115 -18
  103. package/dist/core/dependencyAnalyzer.js.map +1 -1
  104. package/dist/core/explainIssue.d.ts +9 -0
  105. package/dist/core/explainIssue.js +106 -0
  106. package/dist/core/explainIssue.js.map +1 -0
  107. package/dist/core/fileInspector.js +12 -0
  108. package/dist/core/fileInspector.js.map +1 -1
  109. package/dist/core/fixSuggest.d.ts +41 -0
  110. package/dist/core/fixSuggest.js +327 -0
  111. package/dist/core/fixSuggest.js.map +1 -0
  112. package/dist/core/hotspotAnalyzer.js +2 -2
  113. package/dist/core/indexCache.js +5 -1
  114. package/dist/core/indexCache.js.map +1 -1
  115. package/dist/core/issueEngine.js +18 -0
  116. package/dist/core/issueEngine.js.map +1 -1
  117. package/dist/core/languages/LanguageAdapter.d.ts +1 -1
  118. package/dist/core/languages/goAdapter.js +12 -5
  119. package/dist/core/languages/goAdapter.js.map +1 -1
  120. package/dist/core/languages/goCallSites.d.ts +20 -0
  121. package/dist/core/languages/goCallSites.js +42 -0
  122. package/dist/core/languages/goCallSites.js.map +1 -0
  123. package/dist/core/languages/goCyclomatic.d.ts +1 -1
  124. package/dist/core/languages/goCyclomatic.js +2 -2
  125. package/dist/core/languages/goExports.d.ts +1 -1
  126. package/dist/core/languages/goExports.js +1 -1
  127. package/dist/core/languages/goFunctions.d.ts +24 -0
  128. package/dist/core/languages/goFunctions.js +99 -0
  129. package/dist/core/languages/goFunctions.js.map +1 -0
  130. package/dist/core/languages/goManifests.d.ts +1 -1
  131. package/dist/core/languages/goManifests.js +2 -2
  132. package/dist/core/languages/javaAdapter.d.ts +2 -0
  133. package/dist/core/languages/javaAdapter.js +153 -0
  134. package/dist/core/languages/javaAdapter.js.map +1 -0
  135. package/dist/core/languages/javaCallSites.d.ts +16 -0
  136. package/dist/core/languages/javaCallSites.js +45 -0
  137. package/dist/core/languages/javaCallSites.js.map +1 -0
  138. package/dist/core/languages/javaCyclomatic.d.ts +21 -0
  139. package/dist/core/languages/javaCyclomatic.js +49 -0
  140. package/dist/core/languages/javaCyclomatic.js.map +1 -0
  141. package/dist/core/languages/javaExports.d.ts +25 -0
  142. package/dist/core/languages/javaExports.js +80 -0
  143. package/dist/core/languages/javaExports.js.map +1 -0
  144. package/dist/core/languages/javaFunctions.d.ts +22 -0
  145. package/dist/core/languages/javaFunctions.js +87 -0
  146. package/dist/core/languages/javaFunctions.js.map +1 -0
  147. package/dist/core/languages/javaImports.d.ts +25 -0
  148. package/dist/core/languages/javaImports.js +49 -0
  149. package/dist/core/languages/javaImports.js.map +1 -0
  150. package/dist/core/languages/javaManifests.d.ts +25 -0
  151. package/dist/core/languages/javaManifests.js +86 -0
  152. package/dist/core/languages/javaManifests.js.map +1 -0
  153. package/dist/core/languages/pythonAdapter.js +8 -1
  154. package/dist/core/languages/pythonAdapter.js.map +1 -1
  155. package/dist/core/languages/pythonCallSites.d.ts +19 -0
  156. package/dist/core/languages/pythonCallSites.js +40 -0
  157. package/dist/core/languages/pythonCallSites.js.map +1 -0
  158. package/dist/core/languages/pythonFunctions.d.ts +23 -0
  159. package/dist/core/languages/pythonFunctions.js +87 -0
  160. package/dist/core/languages/pythonFunctions.js.map +1 -0
  161. package/dist/core/languages/registry.js +3 -1
  162. package/dist/core/languages/registry.js.map +1 -1
  163. package/dist/core/languages/rubyAdapter.d.ts +2 -0
  164. package/dist/core/languages/rubyAdapter.js +136 -0
  165. package/dist/core/languages/rubyAdapter.js.map +1 -0
  166. package/dist/core/languages/rubyCallSites.d.ts +16 -0
  167. package/dist/core/languages/rubyCallSites.js +34 -0
  168. package/dist/core/languages/rubyCallSites.js.map +1 -0
  169. package/dist/core/languages/rubyCyclomatic.d.ts +19 -0
  170. package/dist/core/languages/rubyCyclomatic.js +47 -0
  171. package/dist/core/languages/rubyCyclomatic.js.map +1 -0
  172. package/dist/core/languages/rubyExports.d.ts +24 -0
  173. package/dist/core/languages/rubyExports.js +53 -0
  174. package/dist/core/languages/rubyExports.js.map +1 -0
  175. package/dist/core/languages/rubyFunctions.d.ts +22 -0
  176. package/dist/core/languages/rubyFunctions.js +91 -0
  177. package/dist/core/languages/rubyFunctions.js.map +1 -0
  178. package/dist/core/languages/rubyImports.d.ts +12 -0
  179. package/dist/core/languages/rubyImports.js +75 -0
  180. package/dist/core/languages/rubyImports.js.map +1 -0
  181. package/dist/core/languages/rubyManifests.d.ts +20 -0
  182. package/dist/core/languages/rubyManifests.js +55 -0
  183. package/dist/core/languages/rubyManifests.js.map +1 -0
  184. package/dist/core/languages/treeSitterLoader.js +3 -1
  185. package/dist/core/languages/treeSitterLoader.js.map +1 -1
  186. package/dist/core/monorepo.js +5 -5
  187. package/dist/core/outdatedDetector.d.ts +13 -2
  188. package/dist/core/outdatedDetector.js +86 -16
  189. package/dist/core/outdatedDetector.js.map +1 -1
  190. package/dist/core/prDiff.d.ts +1 -1
  191. package/dist/core/prDiff.js +2 -2
  192. package/dist/core/review.d.ts +21 -0
  193. package/dist/core/review.js +457 -0
  194. package/dist/core/review.js.map +1 -0
  195. package/dist/grammars/tree-sitter-java.wasm +0 -0
  196. package/dist/grammars/tree-sitter-ruby.wasm +0 -0
  197. package/dist/index.d.ts +5 -1
  198. package/dist/index.js +3 -0
  199. package/dist/index.js.map +1 -1
  200. package/dist/mcp/server.js +0 -22
  201. package/dist/mcp/server.js.map +1 -1
  202. package/dist/mcp/tools/_shared.d.ts +24 -0
  203. package/dist/mcp/tools/_shared.js +82 -0
  204. package/dist/mcp/tools/_shared.js.map +1 -0
  205. package/dist/mcp/tools/analyze.d.ts +2 -0
  206. package/dist/mcp/tools/analyze.js +55 -0
  207. package/dist/mcp/tools/analyze.js.map +1 -0
  208. package/dist/mcp/tools/audit.d.ts +2 -0
  209. package/dist/mcp/tools/audit.js +37 -0
  210. package/dist/mcp/tools/audit.js.map +1 -0
  211. package/dist/mcp/tools/coupling.d.ts +2 -0
  212. package/dist/mcp/tools/coupling.js +67 -0
  213. package/dist/mcp/tools/coupling.js.map +1 -0
  214. package/dist/mcp/tools/coverage.d.ts +2 -0
  215. package/dist/mcp/tools/coverage.js +53 -0
  216. package/dist/mcp/tools/coverage.js.map +1 -0
  217. package/dist/mcp/tools/dependencies.d.ts +2 -0
  218. package/dist/mcp/tools/dependencies.js +22 -0
  219. package/dist/mcp/tools/dependencies.js.map +1 -0
  220. package/dist/mcp/tools/doctor.d.ts +2 -0
  221. package/dist/mcp/tools/doctor.js +30 -0
  222. package/dist/mcp/tools/doctor.js.map +1 -0
  223. package/dist/mcp/tools/explain.d.ts +2 -0
  224. package/dist/mcp/tools/explain.js +30 -0
  225. package/dist/mcp/tools/explain.js.map +1 -0
  226. package/dist/mcp/tools/explainIssue.d.ts +2 -0
  227. package/dist/mcp/tools/explainIssue.js +30 -0
  228. package/dist/mcp/tools/explainIssue.js.map +1 -0
  229. package/dist/mcp/tools/file.d.ts +2 -0
  230. package/dist/mcp/tools/file.js +22 -0
  231. package/dist/mcp/tools/file.js.map +1 -0
  232. package/dist/mcp/tools/fixSuggest.d.ts +2 -0
  233. package/dist/mcp/tools/fixSuggest.js +57 -0
  234. package/dist/mcp/tools/fixSuggest.js.map +1 -0
  235. package/dist/mcp/tools/graph.d.ts +2 -0
  236. package/dist/mcp/tools/graph.js +69 -0
  237. package/dist/mcp/tools/graph.js.map +1 -0
  238. package/dist/mcp/tools/hotspots.d.ts +2 -0
  239. package/dist/mcp/tools/hotspots.js +103 -0
  240. package/dist/mcp/tools/hotspots.js.map +1 -0
  241. package/dist/mcp/tools/outdated.d.ts +2 -0
  242. package/dist/mcp/tools/outdated.js +36 -0
  243. package/dist/mcp/tools/outdated.js.map +1 -0
  244. package/dist/mcp/tools/prDiff.d.ts +2 -0
  245. package/dist/mcp/tools/prDiff.js +38 -0
  246. package/dist/mcp/tools/prDiff.js.map +1 -0
  247. package/dist/mcp/tools/review.d.ts +2 -0
  248. package/dist/mcp/tools/review.js +54 -0
  249. package/dist/mcp/tools/review.js.map +1 -0
  250. package/dist/mcp/tools/search.d.ts +2 -0
  251. package/dist/mcp/tools/search.js +167 -0
  252. package/dist/mcp/tools/search.js.map +1 -0
  253. package/dist/mcp/tools/structure.d.ts +2 -0
  254. package/dist/mcp/tools/structure.js +34 -0
  255. package/dist/mcp/tools/structure.js.map +1 -0
  256. package/dist/mcp/tools/upgrade.d.ts +2 -0
  257. package/dist/mcp/tools/upgrade.js +38 -0
  258. package/dist/mcp/tools/upgrade.js.map +1 -0
  259. package/dist/mcp/tools/workspaces.d.ts +2 -0
  260. package/dist/mcp/tools/workspaces.js +13 -0
  261. package/dist/mcp/tools/workspaces.js.map +1 -0
  262. package/dist/mcp/tools.d.ts +12 -6
  263. package/dist/mcp/tools.js +46 -854
  264. package/dist/mcp/tools.js.map +1 -1
  265. package/dist/reporters/consoleReporter.d.ts +9 -1
  266. package/dist/reporters/consoleReporter.js +177 -0
  267. package/dist/reporters/consoleReporter.js.map +1 -1
  268. package/dist/reporters/jsonReporter.d.ts +9 -1
  269. package/dist/reporters/jsonReporter.js +9 -0
  270. package/dist/reporters/jsonReporter.js.map +1 -1
  271. package/dist/reporters/markdownReporter.d.ts +9 -1
  272. package/dist/reporters/markdownReporter.js +141 -0
  273. package/dist/reporters/markdownReporter.js.map +1 -1
  274. package/dist/tool-manifest.json +446 -0
  275. package/dist/types.d.ts +235 -5
  276. package/dist/utils/config.js +26 -9
  277. package/dist/utils/config.js.map +1 -1
  278. package/package.json +8 -3
package/README.md CHANGED
@@ -20,7 +20,7 @@
20
20
 
21
21
  AI coding agents are becoming the primary interface to code. Today, when you ask your agent *"which files implement auth?"* or *"what breaks if I bump React from 18 to 19?"* - it either guesses from names, or it shells out to grep and reads raw output not built for it.
22
22
 
23
- **projscan is the first code-intelligence tool built for agents, not for humans.** Your agent gets a fast, AST-accurate, context-budget-aware view of your codebase through 17 structured MCP tools. It can query the import graph, find symbol definitions, preview upgrades, rank hotspots, diff structural changes between refs, and surface coupling/cycle hotspots - without loading the file tree into its context.
23
+ **projscan is the first code-intelligence tool built for agents, not for humans.** Your agent gets a fast, AST-accurate, context-budget-aware view of your codebase through 19 structured MCP tools. It can query the import graph, find symbol definitions, preview upgrades, rank hotspots, diff structural changes between refs, surface coupling/cycle hotspots, get a one-call PR review, and request structured fix-action prompts for any open issue - without loading the file tree into its context.
24
24
 
25
25
  Humans get the same thing through the CLI.
26
26
 
@@ -190,7 +190,7 @@ This outputs a [shields.io](https://shields.io) badge URL and markdown snippet y
190
190
 
191
191
  ## What It Detects
192
192
 
193
- **Languages**: TypeScript, JavaScript, Python, and Go (full AST analysis for all four), plus file-level detection for Rust, Java, Ruby, C/C++, PHP, Swift, Kotlin, and 20+ more.
193
+ **Languages**: TypeScript, JavaScript, Python, Go, Java, and Ruby (full AST analysis for all six), plus file-level detection for Rust, C/C++, PHP, Swift, Kotlin, and 20+ more.
194
194
 
195
195
  **Frameworks**: React, Next.js, Vue, Nuxt, Svelte, Angular, Express, Fastify, NestJS, Vite, Tailwind CSS, Prisma, and more
196
196
 
@@ -206,35 +206,57 @@ Python repos now get the same treatment JS/TS has had since 0.6:
206
206
 
207
207
  `projscan_upgrade` remains Node-only for now - a Python equivalent (reading pip / poetry metadata) is on the roadmap.
208
208
 
209
- ### Bundle (0.11)
209
+ ### Go (0.11)
210
210
 
211
- 0.11.0 is a multi-theme bundle. Five releases of work shipped under one version:
211
+ Go flows through the same pipeline as JS/TS and Python:
212
212
 
213
- - **Signal Quality (was 0.11)** - the hotspot risk score now uses AST-derived **cyclomatic complexity** instead of a line-count proxy. Per-file CC is exposed via `projscan_file` and the new `projscan_coupling` tool. Coupling metrics (fan-in / fan-out / instability) and **circular-import detection** (Tarjan SCC) ship as a first-class `projscan coupling` command and MCP tool.
214
- - **PR Native (was 0.12)** - new `projscan_pr_diff` tool returns the **structural** diff between two refs: exports added/removed, imports added/removed, call sites added/removed, ΔCC, Δfan-in. Stands up a temporary git worktree at the base ref to get a clean second graph. What an agent reviewing a PR actually wants to know.
215
- - **Monorepo (was 0.13)** - workspace detection for npm/yarn workspaces, pnpm-workspace.yaml, and Nx/Turbo/Lerna fallback. New `projscan workspaces` command lists every package; `--package <name>` (and the `package` MCP arg) scope `hotspots` and `coupling` to a single workspace.
216
- - **Observability (was 0.14)** - **opt-in**, privacy-preserving telemetry. Records only tool name, duration, success, version, timestamp; never source content, paths, or arguments. Off by default; enable via `.projscanrc` `telemetry.enabled` or `PROJSCAN_TELEMETRY=1`. Sink is a local JSONL file you control. New `projscan_telemetry` tool surfaces effective state.
217
- - **Second Language (was 0.15)** - **Go** via tree-sitter-go. Go files now flow through the same graph / hotspot / coupling / pr-diff pipeline as JS/TS and Python. `go.mod` parsed for module-path resolution; capitalization rule applied for export visibility.
213
+ - **AST-accurate import graph** via tree-sitter-go. Single-line and parenthesized import blocks, aliased imports, dot-imports.
214
+ - **Capitalization-rule export visibility** - uppercase identifiers are public, lowercase are private. Captures `func`, method, `var`, `const`, `type` (struct/interface).
215
+ - **`go.mod` module-path resolution** - imports prefixed with the module path resolve into the repo; stdlib and third-party are external.
216
+ - **Cyclomatic complexity** counted from `if`, `for`, `switch` cases, select communication cases, `&&`/`||`. Default cases and `defer`/`go` don't count.
218
217
 
219
- Cache version bumped 2 → 3 (CC stored per file). Existing v2 caches are discarded on first 0.11 run and rebuilt automatically.
218
+ ### Coupling and cycles (0.11)
220
219
 
221
- **Issues**:
222
- - Missing linting (ESLint) and formatting (Prettier) configuration
223
- - Missing test framework
224
- - Missing `.editorconfig`
225
- - Large utility directories (architecture smell)
226
- - Excessive, deprecated, or wildcard-versioned dependencies
227
- - Missing lockfile
228
- - Committed `.env` files and private keys (security)
229
- - Hardcoded secrets - AWS keys, GitHub tokens, Slack tokens, generic passwords (security)
230
- - Missing `.env` in `.gitignore` (security)
220
+ `projscan coupling` (CLI + MCP tool) reports per-file fan-in / fan-out / instability (Bob Martin's I = Ce / (Ca + Ce)) and detects circular imports via Tarjan SCC. Cross-package edges are flagged when running on a monorepo.
221
+
222
+ ### PR-aware structural diff (0.11)
223
+
224
+ `projscan pr-diff` returns the structural diff between two refs: exports added/removed/renamed, imports added/removed, call sites added/removed, ΔCC, Δfan-in. Spins up a temporary git worktree at the base ref to build a clean second graph. Renames are detected via similarity scoring (max of normalized Levenshtein and shared-affix fraction, threshold 0.5).
225
+
226
+ ### Monorepo support (0.11)
227
+
228
+ Detects npm/yarn workspaces, `pnpm-workspace.yaml`, Lerna, modern Nx (`nx.json#workspaceLayout` + `project.json` scan), legacy Nx (`workspace.json#projects`), and a `packages/*` + `apps/*` + `libs/*` fallback. `projscan workspaces` lists every package; `--package <name>` (or the `package` MCP arg) scopes most commands to a single workspace.
229
+
230
+ Cache version bumped 2 → 3 in 0.11 (CC stored per file). Existing v2 caches are discarded on first run and rebuilt automatically.
231
231
 
232
232
  ## Performance
233
233
 
234
- - **5,000 files** analyzed in under 1.5 seconds
235
- - **20,000 files** analyzed in under 3 seconds
234
+ Reference numbers from `npm run bench` (projscan repo + synthetic) on an Apple M3 Pro running Node 25:
235
+
236
+ | Repo | Files | analyze | doctor | hotspots | coupling | search |
237
+ |------|-------|---------|--------|----------|----------|--------|
238
+ | projscan itself | ~120 | 415–578 ms | 404–498 ms | 462–637 ms | 164–326 ms | 234–393 ms |
239
+ | Synthetic medium | 500 | 244–273 ms | 248–262 ms | 264–293 ms | 165–208 ms | 182–225 ms |
240
+
241
+ For real-world numbers against larger codebases, `npm run bench:references` shallow-clones TypeScript, Django, and kubernetes/client-go into `.bench-cache/` (gitignored) and runs the same suite. First run is network-bound; later runs reuse the cache. Restrict to one target with `-- --only ts|django|k8s-client-go`.
242
+
243
+ Cold-cache and warm-cache times shown side by side. Run `npm run bench` against your own machine to recalibrate.
244
+
236
245
  - **Zero network requests** - everything runs locally
237
- - **9 runtime dependencies** - still minimal (the three tree-sitter packages bring ~850 KB of vendored wasm: web-tree-sitter ~190 KB, tree-sitter-python ~450 KB, tree-sitter-go ~210 KB)
246
+ - **11 runtime dependencies** - still minimal (the five tree-sitter grammars bring ~3.3 MB of vendored wasm: web-tree-sitter ~190 KB, tree-sitter-python ~450 KB, tree-sitter-go ~210 KB, tree-sitter-java ~405 KB, tree-sitter-ruby ~2.0 MB)
247
+
248
+ ## Optional features
249
+
250
+ projscan keeps the install slim by default. One feature is gated behind an optional peer dependency:
251
+
252
+ - **Semantic search** uses local embeddings via `@xenova/transformers` (~25 MB quantized model, downloads on first use, then cached). Without it, `projscan search` falls back to BM25 lexical search and prints a one-line tip pointing here. Install when you want it:
253
+
254
+ ```bash
255
+ npm install @xenova/transformers
256
+ projscan search "cache invalidation" --semantic
257
+ ```
258
+
259
+ See [AI Agent Integration → Semantic search](#semantic-search-090-opt-in) for details.
238
260
 
239
261
  ## CI/CD Integration
240
262
 
@@ -441,32 +463,34 @@ claude mcp add projscan -- npx projscan mcp
441
463
  - *"What breaks if I bump chalk to 6?"* → `projscan_upgrade { package: "chalk" }`
442
464
  - *"Where should I refactor first?"* → `projscan_hotspots`
443
465
 
444
- ### The 17 MCP tools
466
+ ### The 19 MCP tools
445
467
 
446
- **Structural (0.6.0 / 0.11 agent-native):**
468
+ **Structural (0.6.0 / 0.11 / 0.13 / 0.14 - agent-native):**
447
469
  - **`projscan_graph`** - query the AST-based code graph. Directions: `imports`, `exports`, `importers`, `symbol_defs`, `package_importers`. Millisecond responses on a warm cache.
448
470
  - **`projscan_search`** - fast search across `symbols` (exported names), `files` (path substring), or `content` (source substring with line + excerpt). Replaces the temptation to shell out to grep.
449
471
  - **`projscan_coupling`** *(0.11)* - per-file fan-in / fan-out / instability + circular-import cycles (Tarjan SCC). Filter by `direction: cycles_only | high_fan_in | high_fan_out`.
450
472
  - **`projscan_pr_diff`** *(0.11)* - structural diff between two git refs. Returns added/removed/modified files with explicit lists of exports, imports, and call sites that changed, plus ΔCC and Δfan-in.
473
+ - **`projscan_review`** *(0.13)* - one-call PR review. Composes `pr_diff` + per-changed-file risk + new/expanded import cycles + risky function additions + dependency changes + a verdict (`ok` / `review` / `block`).
474
+ - **`projscan_fix_suggest`** *(0.14)* - structured action prompt for any open issue: headline, why it matters, where, one-paragraph instruction, optional suggested test. Closes the diagnose → fix loop.
475
+ - **`projscan_explain_issue`** *(0.14)* - deep dive on one issue: code excerpt, related issues in the same file, similar past commits via `git log --grep`, plus the structured FixSuggestion.
451
476
 
452
477
  **Analysis:**
453
478
  - `projscan_analyze` - full project report
454
- - `projscan_doctor` - health score + issues
455
- - `projscan_hotspots` - risk-ranked files (churn × **AST cyclomatic complexity** × issues × ownership × coverage; falls back to LOC for non-AST languages)
456
- - `projscan_file` - per-file risk + ownership + related issues + CC + fan-in/fan-out
479
+ - `projscan_doctor` - health score + issues (now includes `cycle-detected-N` for circular imports as of 0.13)
480
+ - `projscan_hotspots` - risk-ranked files (churn × **AST cyclomatic complexity** × issues × ownership × coverage; falls back to LOC for non-AST languages). Pass `view: "functions"` *(0.13)* for top-N risky individual functions.
481
+ - `projscan_file` - per-file risk + ownership + related issues + CC + fan-in/fan-out + per-function CC table *(0.13)*
457
482
  - `projscan_explain` - per-file purpose, imports, exports, smells
458
483
  - `projscan_structure` - directory tree
459
484
  - `projscan_coverage` - scariest untested files (coverage × hotspots)
460
485
 
461
486
  **Dependencies:**
462
- - `projscan_dependencies` - declared deps, risks
463
- - `projscan_outdated` - declared-vs-installed drift (offline)
464
- - `projscan_audit` - normalized `npm audit`
487
+ - `projscan_dependencies` - declared deps, risks. In a monorepo: aggregated totals + `byWorkspace` breakdown; `package` arg scopes to one *(0.13)*.
488
+ - `projscan_outdated` - declared-vs-installed drift (offline). Per-package `byWorkspace`; `package` arg.
489
+ - `projscan_audit` - normalized `npm audit`. `package` arg scopes findings to one workspace's direct deps *(0.13)*.
465
490
  - `projscan_upgrade` - upgrade preview (CHANGELOG + importers, offline)
466
491
 
467
- **Workspace + observability (0.11):**
492
+ **Workspace (0.11):**
468
493
  - `projscan_workspaces` - list monorepo packages (npm/yarn/pnpm/Nx/Turbo/Lerna). Use the `name` as the `package` arg on `projscan_hotspots` / `projscan_coupling` to scope.
469
- - `projscan_telemetry` - inspect opt-in telemetry state (enabled?, sink path, env override).
470
494
 
471
495
  ### Context-window budgeting
472
496
 
@@ -0,0 +1,13 @@
1
+ import type { FileEntry, Issue } from '../types.js';
2
+ /**
3
+ * Cross-package import policy (0.14.0). Walks the cross-package edges
4
+ * detected by `computeCoupling` and checks each against the user-configured
5
+ * `monorepo.importPolicy` block in `.projscanrc`. Emits one
6
+ * `cross-package-violation-N` issue per violating edge (capped at 50 to
7
+ * keep the doctor output bounded on large monorepos).
8
+ *
9
+ * Off by default: the analyzer is a no-op when no `monorepo.importPolicy`
10
+ * entries are configured or when the repo isn't a monorepo. Adding a
11
+ * single rule turns it on for the matching `from` package.
12
+ */
13
+ export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;
@@ -0,0 +1,136 @@
1
+ import path from 'node:path';
2
+ import { buildCodeGraph } from '../core/codeGraph.js';
3
+ import { loadCachedGraph, saveCachedGraph } from '../core/indexCache.js';
4
+ import { computeCoupling } from '../core/couplingAnalyzer.js';
5
+ import { detectWorkspaces } from '../core/monorepo.js';
6
+ import { loadConfig } from '../utils/config.js';
7
+ const MAX_VIOLATIONS_REPORTED = 50;
8
+ /**
9
+ * Cross-package import policy (0.14.0). Walks the cross-package edges
10
+ * detected by `computeCoupling` and checks each against the user-configured
11
+ * `monorepo.importPolicy` block in `.projscanrc`. Emits one
12
+ * `cross-package-violation-N` issue per violating edge (capped at 50 to
13
+ * keep the doctor output bounded on large monorepos).
14
+ *
15
+ * Off by default: the analyzer is a no-op when no `monorepo.importPolicy`
16
+ * entries are configured or when the repo isn't a monorepo. Adding a
17
+ * single rule turns it on for the matching `from` package.
18
+ */
19
+ export async function check(rootPath, files) {
20
+ const { config } = await loadConfig(rootPath);
21
+ const rules = config.monorepo?.importPolicy ?? [];
22
+ if (rules.length === 0)
23
+ return [];
24
+ const ws = await detectWorkspaces(rootPath);
25
+ const realWorkspaces = ws.packages.filter((p) => !p.isRoot);
26
+ if (ws.kind === 'none' || realWorkspaces.length < 2)
27
+ return [];
28
+ // Index rules by `from` for O(1) lookup.
29
+ const rulesByPackage = new Map();
30
+ for (const r of rules)
31
+ rulesByPackage.set(r.from, r);
32
+ const cached = await loadCachedGraph(rootPath);
33
+ let graph;
34
+ try {
35
+ graph = await buildCodeGraph(rootPath, files, cached);
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ await saveCachedGraph(rootPath, graph).catch(() => undefined);
41
+ const coupling = computeCoupling(graph, ws);
42
+ if (coupling.crossPackageEdges.length === 0)
43
+ return [];
44
+ const issues = [];
45
+ let counter = 0;
46
+ for (const edge of coupling.crossPackageEdges) {
47
+ if (issues.length >= MAX_VIOLATIONS_REPORTED)
48
+ break;
49
+ const rule = rulesByPackage.get(edge.from.package);
50
+ if (!rule)
51
+ continue;
52
+ const verdict = evaluateEdge(rule, edge.to.package);
53
+ if (verdict !== 'deny')
54
+ continue;
55
+ counter++;
56
+ // Try to find the actual import line. The graph file's imports include
57
+ // resolved files; we can match by source string suffix to recover the line.
58
+ const importingFile = graph.files.get(edge.from.file);
59
+ const lineHint = importingFile?.imports.find((i) =>
60
+ // The resolved target's basename usually appears in the source spec;
61
+ // this is best-effort. If we can't pin a line, fall back to file only.
62
+ edge.to.file.includes(path.basename(i.source).replace(/\.[a-z]+$/, '')));
63
+ const location = lineHint?.line
64
+ ? { file: edge.from.file, line: lineHint.line }
65
+ : { file: edge.from.file };
66
+ issues.push({
67
+ id: `cross-package-violation-${counter}`,
68
+ title: `Disallowed import from "${edge.from.package}" to "${edge.to.package}"`,
69
+ description: `${edge.from.file} imports from package "${edge.to.package}" but the .projscanrc importPolicy rule for "${edge.from.package}" forbids it. ` +
70
+ 'Replace with the package\'s public entry or update the importPolicy.',
71
+ severity: 'warning',
72
+ category: 'architecture',
73
+ fixAvailable: false,
74
+ locations: [location],
75
+ });
76
+ }
77
+ if (coupling.crossPackageEdges.length > MAX_VIOLATIONS_REPORTED && issues.length === MAX_VIOLATIONS_REPORTED) {
78
+ issues.push({
79
+ id: 'cross-package-violation-overflow',
80
+ title: 'Additional cross-package policy violations not reported',
81
+ description: `Doctor caps violation reporting at ${MAX_VIOLATIONS_REPORTED}. Run \`projscan coupling\` for the full edge list.`,
82
+ severity: 'info',
83
+ category: 'architecture',
84
+ fixAvailable: false,
85
+ });
86
+ }
87
+ return issues;
88
+ }
89
+ /**
90
+ * Decide whether an edge from a rule's `from` package to `targetPackage`
91
+ * is allowed or denied. Logic:
92
+ * - If `allow` is set and matches → 'allow' (short-circuits).
93
+ * - If `deny` is set and matches → 'deny'.
94
+ * - If `allow` is set and doesn't match → 'deny' (allow-list semantics).
95
+ * - Otherwise → 'allow' (no policy hit).
96
+ */
97
+ function evaluateEdge(rule, targetPackage) {
98
+ if (rule.allow && rule.allow.length > 0) {
99
+ if (matchesAny(targetPackage, rule.allow))
100
+ return 'allow';
101
+ // allow-list miss is a deny.
102
+ return 'deny';
103
+ }
104
+ if (rule.deny && rule.deny.length > 0) {
105
+ if (matchesAny(targetPackage, rule.deny))
106
+ return 'deny';
107
+ }
108
+ return 'allow';
109
+ }
110
+ function matchesAny(name, patterns) {
111
+ for (const p of patterns) {
112
+ if (matchesGlob(name, p))
113
+ return true;
114
+ }
115
+ return false;
116
+ }
117
+ function matchesGlob(name, pattern) {
118
+ if (pattern === '*')
119
+ return true;
120
+ if (pattern === name)
121
+ return true;
122
+ // Simple suffix glob: `pkg/*`
123
+ if (pattern.endsWith('/*')) {
124
+ const prefix = pattern.slice(0, -2);
125
+ if (name.startsWith(prefix + '/'))
126
+ return true;
127
+ }
128
+ // Simple prefix glob: `*/sub`
129
+ if (pattern.startsWith('*/')) {
130
+ const suffix = pattern.slice(2);
131
+ if (name.endsWith('/' + suffix))
132
+ return true;
133
+ }
134
+ return false;
135
+ }
136
+ //# sourceMappingURL=crossPackageImportCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crossPackageImportCheck.js","sourceRoot":"","sources":["../../src/analyzers/crossPackageImportCheck.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { FileEntry, Issue } from '../types.js';
2
+ /**
3
+ * Lift Tarjan-detected import cycles from the coupling analyzer into the
4
+ * doctor issue list. Each cycle yields ONE warning issue listing every file
5
+ * in the cycle as a location, so an agent calling `projscan_doctor` (which
6
+ * doesn't fetch coupling data on its own) still sees circular dependencies.
7
+ *
8
+ * Capped at MAX_CYCLES_REPORTED to keep the doctor output bounded on
9
+ * pathological codebases. Each issue's `locations` is capped at
10
+ * MAX_FILES_PER_CYCLE_ISSUE; the rest are listed in the description.
11
+ */
12
+ export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;
@@ -0,0 +1,65 @@
1
+ import { buildCodeGraph } from '../core/codeGraph.js';
2
+ import { loadCachedGraph, saveCachedGraph } from '../core/indexCache.js';
3
+ import { computeCoupling } from '../core/couplingAnalyzer.js';
4
+ const MAX_FILES_PER_CYCLE_ISSUE = 8;
5
+ const MAX_CYCLES_REPORTED = 20;
6
+ /**
7
+ * Lift Tarjan-detected import cycles from the coupling analyzer into the
8
+ * doctor issue list. Each cycle yields ONE warning issue listing every file
9
+ * in the cycle as a location, so an agent calling `projscan_doctor` (which
10
+ * doesn't fetch coupling data on its own) still sees circular dependencies.
11
+ *
12
+ * Capped at MAX_CYCLES_REPORTED to keep the doctor output bounded on
13
+ * pathological codebases. Each issue's `locations` is capped at
14
+ * MAX_FILES_PER_CYCLE_ISSUE; the rest are listed in the description.
15
+ */
16
+ export async function check(rootPath, files) {
17
+ // Build the graph (cache-first). saveCachedGraph is best-effort and won't
18
+ // throw on permissions issues.
19
+ const cached = await loadCachedGraph(rootPath);
20
+ let graph;
21
+ try {
22
+ graph = await buildCodeGraph(rootPath, files, cached);
23
+ }
24
+ catch {
25
+ // If graph building fails entirely (e.g. all parsers throw), skip silently.
26
+ return [];
27
+ }
28
+ await saveCachedGraph(rootPath, graph).catch(() => undefined);
29
+ const coupling = computeCoupling(graph);
30
+ if (coupling.cycles.length === 0)
31
+ return [];
32
+ const issues = [];
33
+ const cyclesToReport = coupling.cycles.slice(0, MAX_CYCLES_REPORTED);
34
+ for (let i = 0; i < cyclesToReport.length; i++) {
35
+ const cycle = cyclesToReport[i];
36
+ const id = `cycle-detected-${i + 1}`;
37
+ const locFiles = cycle.files.slice(0, MAX_FILES_PER_CYCLE_ISSUE);
38
+ const overflowCount = cycle.files.length - locFiles.length;
39
+ const filesPretty = cycle.files.join(', ');
40
+ const description = overflowCount > 0
41
+ ? `Circular import among ${cycle.size} files: ${filesPretty}. Resolve by introducing an interface boundary or moving shared types to a leaf module.`
42
+ : `Circular import among ${cycle.size} files: ${filesPretty}. Resolve by introducing an interface boundary or moving shared types to a leaf module.`;
43
+ issues.push({
44
+ id,
45
+ title: `Circular imports detected (${cycle.size} files)`,
46
+ description,
47
+ severity: 'warning',
48
+ category: 'architecture',
49
+ fixAvailable: false,
50
+ locations: locFiles.map((file) => ({ file })),
51
+ });
52
+ }
53
+ if (coupling.cycles.length > MAX_CYCLES_REPORTED) {
54
+ issues.push({
55
+ id: 'cycle-detected-overflow',
56
+ title: `${coupling.cycles.length - MAX_CYCLES_REPORTED} additional import cycles not reported`,
57
+ description: `Doctor caps cycle reporting at ${MAX_CYCLES_REPORTED} cycles. Run \`projscan coupling --cycles-only\` for the full list.`,
58
+ severity: 'info',
59
+ category: 'architecture',
60
+ fixAvailable: false,
61
+ });
62
+ }
63
+ return issues;
64
+ }
65
+ //# sourceMappingURL=cycleCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cycleCheck.js","sourceRoot":"","sources":["../../src/analyzers/cycleCheck.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,MAAM,yBAAyB,GAAG,CAAC,CAAC;AACpC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,QAAgB,EAAE,KAAkB;IAC9D,0EAA0E;IAC1E,+BAA+B;IAC/B,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,KAAK,CAAC;IACV,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;QAC5E,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAE9D,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAE5C,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;IAErE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,yBAAyB,CAAC,CAAC;QACjE,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;QAE3D,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,WAAW,GACf,aAAa,GAAG,CAAC;YACf,CAAC,CAAC,yBAAyB,KAAK,CAAC,IAAI,WAAW,WAAW,yFAAyF;YACpJ,CAAC,CAAC,yBAAyB,KAAK,CAAC,IAAI,WAAW,WAAW,yFAAyF,CAAC;QAEzJ,MAAM,CAAC,IAAI,CAAC;YACV,EAAE;YACF,KAAK,EAAE,8BAA8B,KAAK,CAAC,IAAI,SAAS;YACxD,WAAW;YACX,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,cAAc;YACxB,YAAY,EAAE,KAAK;YACnB,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;SAC9C,CAAC,CAAC;IACL,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,mBAAmB,EAAE,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,yBAAyB;YAC7B,KAAK,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,mBAAmB,wCAAwC;YAC9F,WAAW,EAAE,kCAAkC,mBAAmB,qEAAqE;YACvI,QAAQ,EAAE,MAAM;YAChB,QAAQ,EAAE,cAAc;YACxB,YAAY,EAAE,KAAK;SACpB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { buildImportGraph } from '../core/importGraph.js';
3
+ import { buildImportGraph, toPackageName } from '../core/importGraph.js';
4
+ import { detectWorkspaces } from '../core/monorepo.js';
4
5
  import { findDependencyLines } from '../utils/packageJsonLocator.js';
5
6
  /**
6
7
  * Patterns for packages that are typically not imported directly from source
@@ -68,7 +69,52 @@ function isImplicitlyUsed(pkg) {
68
69
  return IMPLICIT_USE_PREFIXES.some((prefix) => pkg.startsWith(prefix));
69
70
  }
70
71
  export async function check(rootPath, files) {
71
- const pkgPath = path.join(rootPath, 'package.json');
72
+ // Build the import graph once across the whole repo. We slice it per-package
73
+ // below for workspace-aware mode.
74
+ const fullGraph = await buildImportGraph(rootPath, files);
75
+ // Workspace detection. When the repo is a single package (kind 'none') or a
76
+ // root-only manifest with no real workspaces, we degrade to the original
77
+ // single-manifest path for full backward compatibility.
78
+ const ws = await detectWorkspaces(rootPath);
79
+ const realWorkspaces = ws.packages.filter((p) => !p.isRoot);
80
+ const isMonorepo = ws.kind !== 'none' && realWorkspaces.length > 0;
81
+ if (!isMonorepo) {
82
+ return await checkOnePackage(rootPath, '', fullGraph, files);
83
+ }
84
+ // Workspace-aware: check the root manifest (if any) AND each workspace package.
85
+ // Each gets its own deps-vs-imports comparison, scoped to the files that
86
+ // belong to that package by longest-prefix path matching.
87
+ const issues = [];
88
+ const rootPkg = ws.packages.find((p) => p.isRoot);
89
+ if (rootPkg) {
90
+ // Root manifest: only consider files NOT under any workspace package dir.
91
+ const claimedPrefixes = realWorkspaces
92
+ .map((p) => p.relativePath)
93
+ .filter((p) => p.length > 0);
94
+ const rootFiles = files.filter((f) => !claimedPrefixes.some((prefix) => f.relativePath.startsWith(prefix + '/')));
95
+ issues.push(...(await checkOnePackage(rootPath, '', fullGraph, rootFiles)));
96
+ }
97
+ for (const wp of realWorkspaces) {
98
+ const pkgDir = path.join(rootPath, wp.relativePath);
99
+ const prefix = wp.relativePath + '/';
100
+ const wpFiles = files.filter((f) => f.relativePath === wp.relativePath || f.relativePath.startsWith(prefix));
101
+ issues.push(...(await checkOnePackage(pkgDir, wp.relativePath, fullGraph, wpFiles)));
102
+ }
103
+ return issues;
104
+ }
105
+ /**
106
+ * Run the unused-dependency check against one package.json.
107
+ *
108
+ * @param packageDir absolute path to the directory containing package.json.
109
+ * @param locationPrefix path prefix (relative to repo root) used in issue
110
+ * `locations.file`. Empty string for the repo root; e.g. `packages/foo` for
111
+ * a workspace package - produces locations like `packages/foo/package.json`.
112
+ * @param fullGraph repo-wide import graph (built once and reused).
113
+ * @param scopedFiles file list this manifest is responsible for. We slice the
114
+ * global graph down to imports from these files only.
115
+ */
116
+ async function checkOnePackage(packageDir, locationPrefix, fullGraph, scopedFiles) {
117
+ const pkgPath = path.join(packageDir, 'package.json');
72
118
  let raw;
73
119
  try {
74
120
  raw = await fs.readFile(pkgPath, 'utf-8');
@@ -88,36 +134,42 @@ export async function check(rootPath, files) {
88
134
  const allDeclared = new Set([...Object.keys(dependencies), ...Object.keys(devDependencies)]);
89
135
  if (allDeclared.size === 0)
90
136
  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.
137
+ // Project per-package usage: walk only the files this manifest covers and
138
+ // collect their external package names from the full graph's byFile map.
139
+ const usedPackages = new Set();
140
+ for (const f of scopedFiles) {
141
+ const specifiers = fullGraph.byFile.get(f.relativePath);
142
+ if (!specifiers)
143
+ continue;
144
+ for (const spec of specifiers) {
145
+ const name = toPackageName(spec);
146
+ if (name)
147
+ usedPackages.add(name);
148
+ }
149
+ }
94
150
  const scriptUsedBinaries = extractScriptBinaries(pkg);
95
- const locations = await findDependencyLines(rootPath);
151
+ const locations = await findDependencyLines(packageDir);
152
+ const locationFile = locationPrefix ? `${locationPrefix}/package.json` : 'package.json';
96
153
  const unused = [];
97
154
  for (const name of allDeclared) {
98
- if (graph.externalPackages.has(name))
155
+ if (usedPackages.has(name))
99
156
  continue;
100
157
  if (isImplicitlyUsed(name))
101
158
  continue;
102
159
  if (scriptUsedBinaries.has(name))
103
160
  continue;
104
- // skip scoped bin lookups (e.g., "npx some-tool") - covered by scriptUsedBinaries
105
161
  const isDev = name in devDependencies;
106
162
  const line = locations?.lineOfDependency.get(name);
163
+ const inWorkspace = locationPrefix ? ` (workspace: ${locationPrefix})` : '';
107
164
  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.`,
165
+ id: locationPrefix ? `unused-dependency-${locationPrefix}-${name}` : `unused-dependency-${name}`,
166
+ title: `Unused ${isDev ? 'dev' : ''} dependency: ${name}${inWorkspace}`.replace(' ', ' ').trim(),
167
+ description: `The package "${name}" is declared in ${locationFile} but never imported from source files under that package. If it's used only in package.json scripts or as a plugin, add it to the projscan allowlist via .projscanrc → disableRules.`,
111
168
  severity: isDev ? 'info' : 'warning',
112
169
  category: 'dependencies',
113
170
  fixAvailable: false,
114
171
  locations: locations
115
- ? [
116
- {
117
- file: 'package.json',
118
- line: line ?? 1,
119
- },
120
- ]
172
+ ? [{ file: locationFile, line: line ?? 1 }]
121
173
  : undefined,
122
174
  });
123
175
  }
@@ -1 +1 @@
1
- {"version":3,"file":"unusedDependencyCheck.js","sourceRoot":"","sources":["../../src/analyzers/unusedDependencyCheck.ts"],"names":[],"mappings":"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"}
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,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,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,6EAA6E;IAC7E,kCAAkC;IAClC,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAE1D,4EAA4E;IAC5E,yEAAyE;IACzE,wDAAwD;IACxD,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC5C,MAAM,cAAc,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC5D,MAAM,UAAU,GAAG,EAAE,CAAC,IAAI,KAAK,MAAM,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;IAEnE,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,MAAM,eAAe,CAAC,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAC/D,CAAC;IAED,gFAAgF;IAChF,yEAAyE;IACzE,0DAA0D;IAC1D,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,OAAO,EAAE,CAAC;QACZ,0EAA0E;QAC1E,MAAM,eAAe,GAAG,cAAc;aACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;aAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC/B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAC5B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAClF,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,eAAe,CAAC,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,KAAK,MAAM,EAAE,IAAI,cAAc,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,GAAG,GAAG,CAAC;QACrC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,EAAE,CAAC,YAAY,IAAI,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7G,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACvF,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;GAUG;AACH,KAAK,UAAU,eAAe,CAC5B,UAAkB,EAClB,cAAsB,EACtB,SAAuD,EACvD,WAAwB;IAExB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IACtD,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;IACD,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,0EAA0E;IAC1E,yEAAyE;IACzE,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACxD,IAAI,CAAC,UAAU;YAAE,SAAS;QAC1B,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;YACjC,IAAI,IAAI;gBAAE,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAC;IACxD,MAAM,YAAY,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,eAAe,CAAC,CAAC,CAAC,cAAc,CAAC;IACxF,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACrC,IAAI,gBAAgB,CAAC,IAAI,CAAC;YAAE,SAAS;QACrC,IAAI,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAE3C,MAAM,KAAK,GAAG,IAAI,IAAI,eAAe,CAAC;QACtC,MAAM,IAAI,GAAG,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,WAAW,GAAG,cAAc,CAAC,CAAC,CAAC,gBAAgB,cAAc,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAE5E,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC,qBAAqB,cAAc,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,qBAAqB,IAAI,EAAE;YAChG,KAAK,EAAE,UAAU,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,gBAAgB,IAAI,GAAG,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE;YACjG,WAAW,EAAE,gBAAgB,IAAI,oBAAoB,YAAY,sLAAsL;YACvP,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,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;gBAC3C,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"}
@@ -0,0 +1,16 @@
1
+ import { Command } from 'commander';
2
+ import type { ArchitectureLayer, FileExplanation, Issue, ProjscanConfig, ReportFormat, FileEntry, DirectoryNode } from '../types.js';
3
+ export declare const pkg: any;
4
+ export declare const program: Command;
5
+ export declare function getFormat(): ReportFormat;
6
+ export declare function getRootPath(): string;
7
+ export declare function loadProjectConfig(): Promise<ProjscanConfig>;
8
+ export declare function filterIssuesByChangedFiles(issues: Issue[], rootPath: string, baseRef?: string): Promise<Issue[]>;
9
+ export declare function setupLogLevel(): void;
10
+ export declare function maybeBanner(): void;
11
+ export declare function maybeCompactBanner(): void;
12
+ /** Walk a DirectoryNode to find the node whose `path` matches targetPath. */
13
+ export declare function sliceCliTree(node: DirectoryNode, targetPath: string): DirectoryNode | null;
14
+ export declare function analyzeFile(filePath: string, content: string): FileExplanation;
15
+ export declare function buildArchitectureLayers(files: FileEntry[], frameworkNames: string[]): ArchitectureLayer[];
16
+ export declare function promptYesNo(question: string): Promise<boolean>;