code-auditor-mcp 3.10.0 → 4.0.1

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 (358) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +197 -0
  3. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts.map +1 -1
  4. package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.d.ts +4 -4
  5. package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.d.ts.map +1 -1
  6. package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
  7. package/dist/analyzers/analyzerUtils.js +2 -2
  8. package/dist/analyzers/analyzerUtils.js.map +1 -1
  9. package/dist/analyzers/applicability.d.ts.map +1 -1
  10. package/dist/analyzers/applicability.js +1 -0
  11. package/dist/analyzers/applicability.js.map +1 -1
  12. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
  13. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +1 -1
  14. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  15. package/dist/analyzers/cross-language/DependencyGraphBuilder.js +3 -3
  16. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  17. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  18. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  19. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +2 -2
  20. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
  21. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +62 -17
  22. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
  23. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  24. package/dist/analyzers/documentationAnalyzer.js +14 -16
  25. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  26. package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
  27. package/dist/analyzers/invariantsAnalyzer.js +30 -25
  28. package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
  29. package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -1
  30. package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
  31. package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -1
  32. package/dist/analyzers/provenance.d.ts +10 -1
  33. package/dist/analyzers/provenance.d.ts.map +1 -1
  34. package/dist/analyzers/provenance.js +138 -1
  35. package/dist/analyzers/provenance.js.map +1 -1
  36. package/dist/analyzers/reactAnalyzer.d.ts +1 -1
  37. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  38. package/dist/analyzers/reactAnalyzer.js +34 -116
  39. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  40. package/dist/analyzers/ruleRegistry.d.ts +5 -3
  41. package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
  42. package/dist/analyzers/ruleRegistry.js +199 -55
  43. package/dist/analyzers/ruleRegistry.js.map +1 -1
  44. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +2 -2
  45. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
  46. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +10 -17
  47. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
  48. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +6 -6
  49. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  50. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +13 -13
  51. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  52. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  53. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +266 -101
  54. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  55. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +3 -2
  56. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  57. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +14 -13
  58. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  59. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  60. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +11 -11
  61. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  62. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +1 -1
  63. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  64. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +19 -7
  65. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  66. package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts.map +1 -1
  67. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +2 -2
  68. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  69. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +93 -46
  70. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  71. package/dist/analyzers/universal/schema/codeAnalysis.d.ts +58 -34
  72. package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
  73. package/dist/analyzers/universal/schema/codeAnalysis.js +239 -97
  74. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
  75. package/dist/analyzers/universal/schema/config.d.ts +6 -6
  76. package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
  77. package/dist/analyzers/universal/schema/jsonSchema.js +1 -1
  78. package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
  79. package/dist/analyzers/universal/schema/types.d.ts +10 -0
  80. package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
  81. package/dist/analyzers/universal/schema/violations.d.ts +1 -1
  82. package/dist/analyzers/universal/schema/violations.d.ts.map +1 -1
  83. package/dist/auditRouter.js +7 -7
  84. package/dist/auditRouter.js.map +1 -1
  85. package/dist/auditRunner.d.ts +1 -1
  86. package/dist/auditRunner.d.ts.map +1 -1
  87. package/dist/auditRunner.js +53 -8
  88. package/dist/auditRunner.js.map +1 -1
  89. package/dist/churn/churnExtractor.js.map +1 -1
  90. package/dist/cli.js +52 -24
  91. package/dist/cli.js.map +1 -1
  92. package/dist/codeIndexDB.d.ts +1 -1
  93. package/dist/codeIndexDB.d.ts.map +1 -1
  94. package/dist/codeIndexDB.js +164 -18
  95. package/dist/codeIndexDB.js.map +1 -1
  96. package/dist/codeIndexService.d.ts.map +1 -1
  97. package/dist/codeIndexService.js.map +1 -1
  98. package/dist/componentScanner.d.ts.map +1 -1
  99. package/dist/componentScanner.js +89 -28
  100. package/dist/componentScanner.js.map +1 -1
  101. package/dist/config/configLoader.js +1 -1
  102. package/dist/config/configLoader.js.map +1 -1
  103. package/dist/config/defaults.d.ts +2 -2
  104. package/dist/config/defaults.d.ts.map +1 -1
  105. package/dist/config/defaults.js +10 -3
  106. package/dist/config/defaults.js.map +1 -1
  107. package/dist/constants.d.ts +1 -1
  108. package/dist/constants.d.ts.map +1 -1
  109. package/dist/conventions/conventionMiner.d.ts.map +1 -1
  110. package/dist/conventions/conventionMiner.js +3 -10
  111. package/dist/conventions/conventionMiner.js.map +1 -1
  112. package/dist/coverage/lcovParser.js.map +1 -1
  113. package/dist/daemon/args.js.map +1 -1
  114. package/dist/daemon/core.d.ts.map +1 -1
  115. package/dist/daemon/core.js +50 -46
  116. package/dist/daemon/core.js.map +1 -1
  117. package/dist/daemon/lspServer.d.ts.map +1 -1
  118. package/dist/daemon/lspServer.js +1 -1
  119. package/dist/daemon/lspServer.js.map +1 -1
  120. package/dist/daemon/main.d.ts.map +1 -1
  121. package/dist/daemon/main.js +14 -4
  122. package/dist/daemon/main.js.map +1 -1
  123. package/dist/daemon/resolve.js.map +1 -1
  124. package/dist/daemon/socketServer.d.ts.map +1 -1
  125. package/dist/enforcement/gate.js +2 -2
  126. package/dist/functionScanner.d.ts.map +1 -1
  127. package/dist/functionScanner.js +47 -42
  128. package/dist/functionScanner.js.map +1 -1
  129. package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
  130. package/dist/generators/AiderConfigGenerator.d.ts.map +1 -1
  131. package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
  132. package/dist/generators/BaseConfigGenerator.js.map +1 -1
  133. package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
  134. package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
  135. package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
  136. package/dist/generators/CodexConfigGenerator.d.ts.map +1 -1
  137. package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
  138. package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
  139. package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
  140. package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
  141. package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
  142. package/dist/generators/GeminiConfigGenerator.d.ts.map +1 -1
  143. package/dist/generators/JetBrainsConfigGenerator.d.ts.map +1 -1
  144. package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
  145. package/dist/generators/ZCodeConfigGenerator.d.ts.map +1 -1
  146. package/dist/graph/importClassification.d.ts +114 -0
  147. package/dist/graph/importClassification.d.ts.map +1 -0
  148. package/dist/graph/importClassification.js +393 -0
  149. package/dist/graph/importClassification.js.map +1 -0
  150. package/dist/graph/importGraph.d.ts +1 -0
  151. package/dist/graph/importGraph.d.ts.map +1 -1
  152. package/dist/graph/importGraph.js +1 -1
  153. package/dist/graph/importGraph.js.map +1 -1
  154. package/dist/graph/outputFormatter.js.map +1 -1
  155. package/dist/hooks/codex.d.ts.map +1 -1
  156. package/dist/hooks/codex.js +1 -1
  157. package/dist/hooks/codex.js.map +1 -1
  158. package/dist/hooks/core.d.ts +1 -1
  159. package/dist/hooks/core.d.ts.map +1 -1
  160. package/dist/hooks/core.js +4 -4
  161. package/dist/hooks/core.js.map +1 -1
  162. package/dist/hooks/cursor.d.ts.map +1 -1
  163. package/dist/hooks/cursor.js +2 -2
  164. package/dist/hooks/cursor.js.map +1 -1
  165. package/dist/index.d.ts +1 -1
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js.map +1 -1
  168. package/dist/installer.d.ts +13 -7
  169. package/dist/installer.d.ts.map +1 -1
  170. package/dist/installer.js +25 -15
  171. package/dist/installer.js.map +1 -1
  172. package/dist/invariants/invariant-rules.schema.json +1 -1
  173. package/dist/invariants/ruleEngine.js.map +1 -1
  174. package/dist/invariants/ruleValidator.js +1 -1
  175. package/dist/invariants/ruleValidator.js.map +1 -1
  176. package/dist/invariants/types.d.ts +1 -1
  177. package/dist/invariants/types.d.ts.map +1 -1
  178. package/dist/languages/LanguageOrchestrator.d.ts +3 -2
  179. package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
  180. package/dist/languages/LanguageOrchestrator.js +1 -1
  181. package/dist/languages/LanguageOrchestrator.js.map +1 -1
  182. package/dist/languages/LanguageRegistry.d.ts.map +1 -1
  183. package/dist/languages/RuntimeManager.d.ts +20 -11
  184. package/dist/languages/RuntimeManager.d.ts.map +1 -1
  185. package/dist/languages/RuntimeManager.js +151 -51
  186. package/dist/languages/RuntimeManager.js.map +1 -1
  187. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  188. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  189. package/dist/languages/adapterBridge.d.ts +10 -0
  190. package/dist/languages/adapterBridge.d.ts.map +1 -1
  191. package/dist/languages/adapterBridge.js +30 -7
  192. package/dist/languages/adapterBridge.js.map +1 -1
  193. package/dist/languages/go/GoAdapter.d.ts +1 -19
  194. package/dist/languages/go/GoAdapter.d.ts.map +1 -1
  195. package/dist/languages/go/GoAdapter.js +33 -116
  196. package/dist/languages/go/GoAdapter.js.map +1 -1
  197. package/dist/languages/go/analyzer +0 -0
  198. package/dist/languages/go/analyzer-src/analyzer.go +8 -8
  199. package/dist/languages/go/analyzer-src/solid.go +5 -5
  200. package/dist/languages/goBinary.d.ts +46 -0
  201. package/dist/languages/goBinary.d.ts.map +1 -0
  202. package/dist/languages/goBinary.js +169 -0
  203. package/dist/languages/goBinary.js.map +1 -0
  204. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +1 -28
  205. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -1
  206. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +3 -42
  207. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
  208. package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
  209. package/dist/languages/tree-sitter/converter.js +2 -1
  210. package/dist/languages/tree-sitter/converter.js.map +1 -1
  211. package/dist/languages/tree-sitter/parser.js.map +1 -1
  212. package/dist/languages/tree-sitter/rawNode.d.ts +31 -0
  213. package/dist/languages/tree-sitter/rawNode.d.ts.map +1 -0
  214. package/dist/languages/tree-sitter/rawNode.js +38 -0
  215. package/dist/languages/tree-sitter/rawNode.js.map +1 -0
  216. package/dist/languages/types.d.ts +15 -21
  217. package/dist/languages/types.d.ts.map +1 -1
  218. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +12 -32
  219. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  220. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +103 -203
  221. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  222. package/dist/ledger.d.ts +3 -0
  223. package/dist/ledger.d.ts.map +1 -1
  224. package/dist/ledger.js +45 -9
  225. package/dist/ledger.js.map +1 -1
  226. package/dist/mcp-tools/projectTasks.d.ts +37 -37
  227. package/dist/mcp-tools/projectTasks.js +2 -2
  228. package/dist/mcp-tools/projectTasks.js.map +1 -1
  229. package/dist/mcp-tools/whitelistTools.d.ts +17 -17
  230. package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
  231. package/dist/mcp-tools/workflowGuide.js +3 -3
  232. package/dist/mcp-tools/workflowGuide.js.map +1 -1
  233. package/dist/mcp-tools-shared.d.ts.map +1 -1
  234. package/dist/mcp-tools-shared.js +13 -13
  235. package/dist/mcp-tools-shared.js.map +1 -1
  236. package/dist/mcp-ui-simple.js +7 -7
  237. package/dist/mcp-ui-simple.js.map +1 -1
  238. package/dist/mcp.js +14 -14
  239. package/dist/mcp.js.map +1 -1
  240. package/dist/mcpAuditJobs.js +7 -7
  241. package/dist/mcpAuditJobs.js.map +1 -1
  242. package/dist/mcpAutoIndex.js +1 -1
  243. package/dist/mcpAutoIndex.js.map +1 -1
  244. package/dist/mcpToolErrors.d.ts.map +1 -1
  245. package/dist/mcpToolErrors.js.map +1 -1
  246. package/dist/native-bootstrap.js.map +1 -1
  247. package/dist/nextFile.d.ts +2 -2
  248. package/dist/nextFile.d.ts.map +1 -1
  249. package/dist/nextFile.js +19 -5
  250. package/dist/nextFile.js.map +1 -1
  251. package/dist/nextFileIncremental.d.ts +10 -2
  252. package/dist/nextFileIncremental.d.ts.map +1 -1
  253. package/dist/nextFileIncremental.js +45 -16
  254. package/dist/nextFileIncremental.js.map +1 -1
  255. package/dist/pipeline.d.ts +2 -2
  256. package/dist/pipeline.d.ts.map +1 -1
  257. package/dist/pipeline.js +35 -0
  258. package/dist/pipeline.js.map +1 -1
  259. package/dist/pipelineAdapters.d.ts +17 -0
  260. package/dist/pipelineAdapters.d.ts.map +1 -1
  261. package/dist/pipelineAdapters.js +356 -197
  262. package/dist/pipelineAdapters.js.map +1 -1
  263. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  264. package/dist/reporting/csvReportGenerator.js +6 -6
  265. package/dist/reporting/csvReportGenerator.js.map +1 -1
  266. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  267. package/dist/reporting/htmlReportGenerator.js +11 -11
  268. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  269. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  270. package/dist/reporting/jsonReportGenerator.js +2 -2
  271. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  272. package/dist/reporting/reportGenerator.d.ts.map +1 -1
  273. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  274. package/dist/reporting/sarifReportGenerator.js +1 -1
  275. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  276. package/dist/scripts/benchmarkWorkers.js +1 -1
  277. package/dist/scripts/benchmarkWorkers.js.map +1 -1
  278. package/dist/search/QueryParser.d.ts.map +1 -1
  279. package/dist/search/QueryParser.js.map +1 -1
  280. package/dist/services/CodeMapGenerator.d.ts.map +1 -1
  281. package/dist/services/CodeMapGenerator.js.map +1 -1
  282. package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
  283. package/dist/services/ProjectTaskRepository.js.map +1 -1
  284. package/dist/services/auditJobService.js.map +1 -1
  285. package/dist/services/fileAccounting.d.ts.map +1 -1
  286. package/dist/services/projectTaskHelpers.js.map +1 -1
  287. package/dist/services/whitelistService.d.ts.map +1 -1
  288. package/dist/sqlite/betterSqlite3.d.ts.map +1 -1
  289. package/dist/sqlite/betterSqlite3.js.map +1 -1
  290. package/dist/sqlite/nodeSqlite.d.ts.map +1 -1
  291. package/dist/sqlite/nodeSqlite.js.map +1 -1
  292. package/dist/structuralSignature.js.map +1 -1
  293. package/dist/styles/cssAstExtractor.d.ts +2 -2
  294. package/dist/styles/cssAstExtractor.d.ts.map +1 -1
  295. package/dist/styles/cssAstExtractor.js +67 -111
  296. package/dist/styles/cssAstExtractor.js.map +1 -1
  297. package/dist/styles/styleExtractor.js.map +1 -1
  298. package/dist/styles/styleIndexer.d.ts.map +1 -1
  299. package/dist/styles/styleIndexer.js +19 -6
  300. package/dist/styles/styleIndexer.js.map +1 -1
  301. package/dist/styles/tailwindConfigLoader.js.map +1 -1
  302. package/dist/styles/tailwindProbe.d.ts.map +1 -1
  303. package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
  304. package/dist/styles/tailwindUtilityExpander.js.map +1 -1
  305. package/dist/telemetry.d.ts +1 -1
  306. package/dist/telemetry.d.ts.map +1 -1
  307. package/dist/telemetry.js.map +1 -1
  308. package/dist/tool-registry.d.ts.map +1 -1
  309. package/dist/types/crossLanguage.d.ts +1 -1
  310. package/dist/types/crossLanguage.d.ts.map +1 -1
  311. package/dist/types.d.ts +67 -19
  312. package/dist/types.d.ts.map +1 -1
  313. package/dist/types.js +6 -5
  314. package/dist/types.js.map +1 -1
  315. package/dist/ui/InteractivePrompts.d.ts.map +1 -1
  316. package/dist/ui/InteractivePrompts.js.map +1 -1
  317. package/dist/utils/astParser.d.ts +1 -17
  318. package/dist/utils/astParser.d.ts.map +1 -1
  319. package/dist/utils/astParser.js +18 -60
  320. package/dist/utils/astParser.js.map +1 -1
  321. package/dist/utils/astUtils.d.ts +12 -12
  322. package/dist/utils/astUtils.d.ts.map +1 -1
  323. package/dist/utils/astUtils.js +83 -82
  324. package/dist/utils/astUtils.js.map +1 -1
  325. package/dist/utils/componentPatterns.js.map +1 -1
  326. package/dist/utils/componentResponsibility.d.ts +8 -8
  327. package/dist/utils/componentResponsibility.d.ts.map +1 -1
  328. package/dist/utils/componentResponsibility.js +33 -38
  329. package/dist/utils/componentResponsibility.js.map +1 -1
  330. package/dist/utils/dependencyExtractor.d.ts +3 -3
  331. package/dist/utils/dependencyExtractor.d.ts.map +1 -1
  332. package/dist/utils/dependencyExtractor.js +26 -30
  333. package/dist/utils/dependencyExtractor.js.map +1 -1
  334. package/dist/utils/fileDiscovery.d.ts.map +1 -1
  335. package/dist/utils/fileDiscovery.js +65 -0
  336. package/dist/utils/fileDiscovery.js.map +1 -1
  337. package/dist/utils/jsDocParser.d.ts +6 -6
  338. package/dist/utils/jsDocParser.d.ts.map +1 -1
  339. package/dist/utils/jsDocParser.js +18 -21
  340. package/dist/utils/jsDocParser.js.map +1 -1
  341. package/dist/utils/reactDetection.d.ts +7 -7
  342. package/dist/utils/reactDetection.d.ts.map +1 -1
  343. package/dist/utils/reactDetection.js +42 -49
  344. package/dist/utils/reactDetection.js.map +1 -1
  345. package/dist/version.generated.d.ts +1 -1
  346. package/dist/version.generated.d.ts.map +1 -1
  347. package/dist/version.generated.js +1 -1
  348. package/dist/version.generated.js.map +1 -1
  349. package/dist/workers/auditWorker.js.map +1 -1
  350. package/package.json +4 -3
  351. package/plugin/.claude-plugin/plugin.json +1 -1
  352. package/plugin/README.md +5 -4
  353. package/plugin/hooks/hooks.json +11 -0
  354. package/plugin/scripts/hook-common.sh +85 -31
  355. package/plugin/scripts/hook-warm.sh +44 -0
  356. package/plugin/skills/code-auditor/SKILL-RULE-KINDS.md +28 -21
  357. package/plugin/skills/code-auditor/SKILL.md +10 -10
  358. package/server.json +2 -2
@@ -19,19 +19,27 @@ import { createHash } from 'crypto';
19
19
  import path from 'path';
20
20
  import { RULE_REGISTRY } from './analyzers/ruleRegistry.js';
21
21
  import { TYPESCRIPT_EXTENSIONS, JAVASCRIPT_EXTENSIONS, getLanguageFromPath } from './utils/fileDiscovery.js';
22
- import { walkAST, isExported, getLineAndColumn, calculateComplexity, getFunctionBody, } from './languages/adapterBridge.js';
22
+ import { walkAST, isExported, getNodeText, getFieldNode, getLineAndColumn, calculateComplexity, getFunctionBody, } from './languages/adapterBridge.js';
23
23
  import { buildImportMap, extractFunctionCalls, } from './utils/dependencyExtractor.js';
24
- import { resolveDependency, basenameNoExt } from './graph/importGraph.js';
24
+ import { classifyImportSpecifier, DEFAULT_VIRTUAL_MODULES } from './graph/importClassification.js';
25
25
  import { isReactComponent, detectComponentType, getComponentName, } from './utils/reactDetection.js';
26
26
  import { findTableReferences, checkNamingConventions, checkQueryPatterns, checkSQLInjection, checkUnresolvedQueries, getNearestTableSuggestions, } from './analyzers/universal/schema/codeAnalysis.js';
27
27
  import { passesFileGate, extractTablesFromRegistry, } from './analyzers/universal/schema/discovery.js';
28
- import { applyMigrationOps } from './analyzers/universal/schema/migrations.js';
28
+ import { applyMigrationOps, stripIdentifier } from './analyzers/universal/schema/migrations.js';
29
29
  // ── Rule ID helpers ──────────────────────────────────────────────────────────
30
30
  function getRuleIdsFor(name) {
31
31
  return Object.entries(RULE_REGISTRY)
32
32
  .filter(([, entry]) => entry.analyzer === name)
33
33
  .map(([id]) => id);
34
34
  }
35
+ /** Join a list of names as prose: "a", "a and b", "a, b and c". */
36
+ function joinEnglish(names) {
37
+ if (names.length === 0)
38
+ return '';
39
+ if (names.length === 1)
40
+ return names[0];
41
+ return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
42
+ }
35
43
  // ── Lazy import singleton ────────────────────────────────────────────────────
36
44
  function lazySingleton(loader) {
37
45
  let promise = null;
@@ -153,10 +161,6 @@ function computeContentHash(body, signature) {
153
161
  const normalized = (body ?? '').replace(/\s+/g, ' ').trim() + '|' + (signature ?? '').trim();
154
162
  return createHash('sha256').update(normalized).digest('hex');
155
163
  }
156
- /** Raw text from an ASTNode (tree-sitter node stored on `.raw`). */
157
- function rawText(node) {
158
- return node.raw?.text ?? '';
159
- }
160
164
  export function createFunctionIndexVisitor() {
161
165
  return {
162
166
  name: 'function-index',
@@ -177,8 +181,18 @@ export function createFunctionIndexVisitor() {
177
181
  table: 'functions',
178
182
  data: { _action: 'clear-by-file', file_path: filePath },
179
183
  });
184
+ // Spec 60 — import classification is TS/JS-only: the normalization probe
185
+ // list has no `.go` extension, so applying it to Go relative imports would
186
+ // manufacture false `internal-broken` rows. Clear + re-emit only for TS/JS.
187
+ const isTsJs = lang === 'typescript' || lang === 'javascript';
188
+ if (isTsJs) {
189
+ indexFacts.push({
190
+ table: 'import_specifiers',
191
+ data: { _action: 'clear-by-file', file_path: filePath },
192
+ });
193
+ }
180
194
  // Build import map once per file for resolving call targets
181
- const importMap = buildImportMap(root);
195
+ const importMap = buildImportMap(root, sourceCode);
182
196
  // Collect function-like nodes in two passes:
183
197
  // 1) function_declaration + method_definition nodes
184
198
  // 2) arrow functions assigned to variables (variable_declarator children)
@@ -189,12 +203,11 @@ export function createFunctionIndexVisitor() {
189
203
  const nameNode = node.children?.find((c) => c.type === 'identifier');
190
204
  if (!nameNode)
191
205
  return;
192
- const name = rawText(nameNode);
206
+ const name = getNodeText(nameNode, sourceCode);
193
207
  if (!name)
194
208
  return;
195
209
  const { line } = getLineAndColumn(node);
196
- const raw = node.raw;
197
- const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
210
+ const endLine = node.location.end.line;
198
211
  const body = getFunctionBody(node, sourceCode);
199
212
  const calls = extractFunctionCalls(node, sourceCode, importMap);
200
213
  const callNames = [...new Set(calls.map((c) => c.callee))];
@@ -215,7 +228,7 @@ export function createFunctionIndexVisitor() {
215
228
  const nameNode = node.children?.find((c) => c.type === 'identifier');
216
229
  if (!nameNode)
217
230
  return;
218
- const methodName = rawText(nameNode);
231
+ const methodName = getNodeText(nameNode, sourceCode);
219
232
  if (!methodName)
220
233
  return;
221
234
  // Walk up to find class name
@@ -225,14 +238,13 @@ export function createFunctionIndexVisitor() {
225
238
  if (parent.type === 'class_declaration') {
226
239
  const cn = parent.children?.find((c) => c.type === 'identifier');
227
240
  if (cn)
228
- className = rawText(cn);
241
+ className = getNodeText(cn, sourceCode);
229
242
  break;
230
243
  }
231
244
  parent = parent.parent;
232
245
  }
233
246
  const { line } = getLineAndColumn(node);
234
- const raw = node.raw;
235
- const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
247
+ const endLine = node.location.end.line;
236
248
  const body = getFunctionBody(node, sourceCode);
237
249
  const calls = extractFunctionCalls(node, sourceCode, importMap);
238
250
  const callNames = [...new Set(calls.map((c) => c.callee))];
@@ -257,14 +269,13 @@ export function createFunctionIndexVisitor() {
257
269
  const arrowFunc = node.children?.find((c) => c.type === 'arrow_function');
258
270
  if (!nameNode || !arrowFunc)
259
271
  return;
260
- const name = rawText(nameNode);
272
+ const name = getNodeText(nameNode, sourceCode);
261
273
  if (!name)
262
274
  return;
263
275
  // Don't duplicate if already covered as function_declaration
264
276
  // (shouldn't happen — function_declaration is a different node type)
265
277
  const { line } = getLineAndColumn(arrowFunc);
266
- const raw = arrowFunc.raw;
267
- const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
278
+ const endLine = arrowFunc.location.end.line;
268
279
  const body = getFunctionBody(arrowFunc, sourceCode);
269
280
  const calls = extractFunctionCalls(arrowFunc, sourceCode, importMap);
270
281
  const callNames = [...new Set(calls.map((c) => c.callee))];
@@ -290,12 +301,12 @@ export function createFunctionIndexVisitor() {
290
301
  filePath.endsWith('.jsx') ||
291
302
  (filePath.endsWith('.js') && hasReactImport)) {
292
303
  walkAST(root, (node) => {
293
- if (!isReactComponent(node))
304
+ if (!isReactComponent(node, sourceCode))
294
305
  return;
295
- const ct = detectComponentType(node);
306
+ const ct = detectComponentType(node, sourceCode);
296
307
  if (!ct)
297
308
  return;
298
- const cName = getComponentName(node);
309
+ const cName = getComponentName(node, sourceCode);
299
310
  if (!cName || cName === 'AnonymousComponent')
300
311
  return;
301
312
  const existing = fnEntries.find((f) => f.name === cName);
@@ -308,11 +319,7 @@ export function createFunctionIndexVisitor() {
308
319
  // New entry — class component, function_expression, or memo/forwardRef
309
320
  // wrapper not already captured by passes 1 or 2.
310
321
  const { line } = getLineAndColumn(node);
311
- const raw = node.raw;
312
- const endLine = line +
313
- (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) -
314
- (raw?.startPosition?.row ?? line) +
315
- 1;
322
+ const endLine = node.location.end.line;
316
323
  const body = getFunctionBody(node, sourceCode);
317
324
  fnEntries.push({
318
325
  name: cName,
@@ -335,7 +342,6 @@ export function createFunctionIndexVisitor() {
335
342
  isExported: fn.isExported,
336
343
  complexity: fn.complexity,
337
344
  functionCalls: fn.functionCalls,
338
- body: fn.body,
339
345
  };
340
346
  const contentHash = computeContentHash(fn.body, '');
341
347
  const now = new Date().toISOString();
@@ -379,29 +385,66 @@ export function createFunctionIndexVisitor() {
379
385
  isRequire: false,
380
386
  line: imp.location.start.line,
381
387
  }));
388
+ // Spec 60.1 — classify each static import specifier and emit one row per
389
+ // occurrence to `import_specifiers`. The corpus file set is the stage-1
390
+ // discovery list, threaded here as `_infra.files` (merged into
391
+ // `context.config` by pipeline.ts). Existence is checked against that
392
+ // in-memory set, never `fs.existsSync`. Virtual-module list + tsconfig
393
+ // `paths` patterns are threaded via `_infra` and passed to the classifier.
394
+ if (isTsJs) {
395
+ // Spec 60.1 Correction 1 — corpus set is the unfiltered discovery list
396
+ // (`_infra.corpusFiles`), not the narrowed `_infra.files`. The latter is
397
+ // the audit's analysis list (already pruned by includePaths/excludePaths),
398
+ // which drops `.json`/other non-analyzed extensions and would mis-classify
399
+ // a real `./invariant-rules.schema.json` as `internal-broken`.
400
+ const corpusFiles = new Set((context.config.corpusFiles) ??
401
+ (context.config.files) ??
402
+ []);
403
+ const virtualModules = context.config.importVirtualModules
404
+ ?? DEFAULT_VIRTUAL_MODULES;
405
+ const tsconfigAliases = context.config.tsconfigAliases;
406
+ for (const imp of staticImportInfos) {
407
+ const { classification, resolvedPath } = classifyImportSpecifier(imp.source, filePath, corpusFiles, {
408
+ virtualModules,
409
+ aliasPatterns: tsconfigAliases?.pathPatterns ?? [],
410
+ pathMappings: tsconfigAliases?.paths ?? {},
411
+ baseUrl: tsconfigAliases?.baseUrl,
412
+ projectRoot: context.projectRoot,
413
+ });
414
+ indexFacts.push({
415
+ table: 'import_specifiers',
416
+ data: {
417
+ file_path: filePath,
418
+ specifier: imp.source,
419
+ classification,
420
+ resolved_path: resolvedPath ?? null,
421
+ line: imp.location.start.line,
422
+ },
423
+ conflictKey: 'file_path, specifier, line',
424
+ });
425
+ }
426
+ }
382
427
  // Dynamic import() and require() — first attempt with pure NodePattern
383
428
  // Plan note: import keyword is an anonymous tree-sitter node, so
384
429
  // hasChild cannot see it. We use `custom` with raw node access.
385
430
  const dynamicCallNodes = langAdapter.findNodes(langAst, {
386
431
  type: 'call_expression',
387
432
  custom: (node) => {
388
- const raw = node.raw;
389
- const fn = raw?.firstChild;
433
+ const fn = node.children?.[0];
390
434
  return (fn?.type === 'import') ||
391
- (fn?.type === 'identifier' && fn.text === 'require');
435
+ (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
392
436
  },
393
437
  });
394
438
  const dynamicImports = [];
395
439
  for (const node of dynamicCallNodes) {
396
- const raw = node.raw;
397
- const fn = raw?.firstChild;
440
+ const fn = node.children?.[0];
398
441
  const isImport = fn?.type === 'import';
399
- const isRequire = !isImport && (fn?.type === 'identifier' && fn.text === 'require');
400
- // Walk the raw tree to find the string argument
401
- const argsNode = raw?.children?.find((c) => c.type === 'arguments');
442
+ const isRequire = !isImport && (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
443
+ // Walk the AST children to find the string argument
444
+ const argsNode = node.children?.find((c) => c.type === 'arguments');
402
445
  const stringNode = argsNode?.children?.find((c) => c.type === 'string');
403
446
  if (stringNode) {
404
- const text = stringNode.text;
447
+ const text = getNodeText(stringNode, sourceCode);
405
448
  if (text.length >= 2) {
406
449
  dynamicImports.push({
407
450
  moduleSpecifier: text.slice(1, -1), // strip quotes
@@ -531,8 +574,8 @@ export function createStylesCssVisitor() {
531
574
  facts: {
532
575
  [filePath]: {
533
576
  declarations: extractDeclarationsFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
534
- tokens: extractTokensFromCSSAst(cssAst, cssAdapter, filePath),
535
- classUsage: extractClassUsageFromCSSAst(cssAst, cssAdapter, filePath),
577
+ tokens: extractTokensFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
578
+ classUsage: extractClassUsageFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
536
579
  },
537
580
  },
538
581
  };
@@ -815,7 +858,12 @@ export function createStylesReducer() {
815
858
  const factsConsumed = context.indexHandle.count('style_declarations')
816
859
  + context.indexHandle.count('style_tokens')
817
860
  + context.indexHandle.count('style_class_usage');
818
- return { violations: result.violations ?? [], facts: {}, factsConsumed };
861
+ return {
862
+ violations: result.violations ?? [],
863
+ facts: {},
864
+ factsConsumed,
865
+ ...((result.diagnostics?.length ?? 0) > 0 && { diagnostics: result.diagnostics }),
866
+ };
819
867
  }
820
868
  catch {
821
869
  return { violations: [], facts: {} };
@@ -940,35 +988,31 @@ export function createCrossDomainReducer() {
940
988
  // short-circuits with `notRunReason` BEFORE any corpus-wide work — the gate is in
941
989
  // the design, not retrofitted after the budget blows. This is the shape that made
942
990
  // the styles reducer 587ms; it must never run over a partial corpus.
943
- // ── Raw-node helpers (tree-sitter raw nodes are `any` here — strict is off) ──
944
- function clRawField(raw, field) {
945
- return typeof raw?.childForFieldName === 'function' ? raw.childForFieldName(field) : null;
946
- }
947
- function clRawNamedChild(raw, type) {
948
- return raw?.namedChildren?.find((c) => c.type === type) ?? null;
949
- }
991
+ // ── Entity helpers (ASTNode-based; parser-specific nodes stay in languages/) ──
950
992
  /** A function body node — `statement_block` (TS/JS) or `block` (Go). */
951
- function clRawBody(raw) {
952
- return raw?.namedChildren?.find((c) => c.type === 'statement_block' || c.type === 'block') ?? null;
993
+ function clBody(node) {
994
+ return node.children?.find((c) => c.type === 'statement_block' || c.type === 'block');
953
995
  }
954
- function clSignatureText(raw, sourceCode) {
955
- if (!raw)
956
- return '';
957
- const body = clRawBody(raw);
958
- const end = body ? body.startIndex : raw.endIndex;
959
- return sourceCode.slice(raw.startIndex, end).trim();
996
+ function clSignatureText(node, sourceCode) {
997
+ const body = clBody(node);
998
+ const end = body ? body.range[0] : node.range[1];
999
+ return sourceCode.slice(node.range[0], end).trim();
960
1000
  }
961
- function clBodyText(raw, sourceCode) {
962
- const body = clRawBody(raw);
963
- return body ? sourceCode.slice(body.startIndex, body.endIndex) : '';
1001
+ function clBodyText(node, sourceCode) {
1002
+ const body = clBody(node);
1003
+ return body ? sourceCode.slice(body.range[0], body.range[1]) : '';
964
1004
  }
965
1005
  /** Prefer the `name` field (works across TS + Go), fall back to identifier children. */
966
- function clNodeName(raw) {
967
- const field = clRawField(raw, 'name');
968
- if (field?.text)
969
- return field.text;
970
- const id = clRawNamedChild(raw, 'identifier') ?? clRawNamedChild(raw, 'property_identifier');
971
- return id?.text;
1006
+ function clNodeName(node, sourceCode) {
1007
+ const field = getFieldNode(node, 'name');
1008
+ if (field) {
1009
+ const text = getNodeText(field, sourceCode);
1010
+ if (text)
1011
+ return text;
1012
+ }
1013
+ const id = node.children?.find((c) => c.type === 'identifier')
1014
+ ?? node.children?.find((c) => c.type === 'property_identifier');
1015
+ return id ? getNodeText(id, sourceCode) : undefined;
972
1016
  }
973
1017
  function clEntityId(filePath, type, name, line) {
974
1018
  return `${filePath}::${type}::${name}::${line}`;
@@ -977,25 +1021,20 @@ function clIsExportedGo(name) {
977
1021
  return name.length > 0 && name[0] === name[0].toUpperCase();
978
1022
  }
979
1023
  /** Collect callee names from a function's body (TS `call_expression` / Go `call_expression`). */
980
- function clCollectCallees(raw) {
1024
+ function clCollectCallees(node, sourceCode) {
981
1025
  const names = new Set();
982
- const walk = (node) => {
983
- if (!node)
984
- return;
985
- if (node.type === 'call_expression') {
986
- const fn = typeof node.childForFieldName === 'function'
987
- ? node.childForFieldName('function')
988
- : null;
989
- const callee = fn ?? node.namedChildren?.[0];
990
- const text = callee?.text?.trim();
1026
+ const walk = (n) => {
1027
+ if (n.type === 'call_expression') {
1028
+ const fn = getFieldNode(n, 'function') ?? n.children?.[0];
1029
+ const text = fn ? getNodeText(fn, sourceCode).trim() : '';
991
1030
  if (text && /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(text)) {
992
1031
  names.add(text);
993
1032
  }
994
1033
  }
995
- for (const child of node.namedChildren ?? [])
1034
+ for (const child of n.children ?? [])
996
1035
  walk(child);
997
1036
  };
998
- walk(raw);
1037
+ walk(node);
999
1038
  return [...names];
1000
1039
  }
1001
1040
  /**
@@ -1007,26 +1046,20 @@ function clCollectCallees(raw) {
1007
1046
  * (`arr.map(singularize)`, `[missedClose, …]`) is invisible to it and leaves
1008
1047
  * the genuinely-referenced target looking orphaned.
1009
1048
  */
1010
- function clCollectFileReferences(raw) {
1049
+ function clCollectFileReferences(node, sourceCode) {
1011
1050
  const names = new Set();
1012
1051
  const isIdentPath = (t) => /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(t);
1013
- const walk = (node) => {
1014
- if (!node)
1015
- return;
1016
- const t = node.type;
1052
+ const walk = (n) => {
1053
+ const t = n.type;
1017
1054
  if (t === 'call_expression') {
1018
- const fn = typeof node.childForFieldName === 'function'
1019
- ? node.childForFieldName('function')
1020
- : null;
1021
- const text = (fn ?? node.namedChildren?.[0])?.text?.trim();
1055
+ const fn = getFieldNode(n, 'function') ?? n.children?.[0];
1056
+ const text = fn ? getNodeText(fn, sourceCode).trim() : '';
1022
1057
  if (text && isIdentPath(text))
1023
1058
  names.add(text);
1024
1059
  }
1025
1060
  else if (t === 'jsx_opening_element' || t === 'jsx_self_closing_element') {
1026
- const nameNode = typeof node.childForFieldName === 'function'
1027
- ? node.childForFieldName('name')
1028
- : null;
1029
- const text = (nameNode ?? node.namedChildren?.[0])?.text?.trim();
1061
+ const nameNode = getFieldNode(n, 'name') ?? n.children?.[0];
1062
+ const text = nameNode ? getNodeText(nameNode, sourceCode).trim() : '';
1030
1063
  if (text && isIdentPath(text))
1031
1064
  names.add(text);
1032
1065
  }
@@ -1036,17 +1069,17 @@ function clCollectFileReferences(raw) {
1036
1069
  // function reference. Declaration names (`name` fields) and member
1037
1070
  // accesses (`property_identifier`) are separate node types and are not
1038
1071
  // captured here.
1039
- const parent = node.parent;
1072
+ const parent = n.parent;
1040
1073
  if (parent && (parent.type === 'arguments' || parent.type === 'array')) {
1041
- const text = node.text?.trim();
1074
+ const text = getNodeText(n, sourceCode).trim();
1042
1075
  if (text && /^[A-Za-z_$][\w$]*$/.test(text))
1043
1076
  names.add(text);
1044
1077
  }
1045
1078
  }
1046
- for (const child of node.namedChildren ?? [])
1079
+ for (const child of n.children ?? [])
1047
1080
  walk(child);
1048
1081
  };
1049
- walk(raw);
1082
+ walk(node);
1050
1083
  return [...names];
1051
1084
  }
1052
1085
  function clMakeFunction(filePath, lang, name, line, signature, purpose, params, callees, exported, complexity, isMethod = false) {
@@ -1077,18 +1110,21 @@ function clMakeFunction(filePath, lang, name, line, signature, purpose, params,
1077
1110
  * (`import(someVariable)` — unresolvable, reported rather than silenced), or
1078
1111
  * `null` when the call is not a dynamic import/require.
1079
1112
  */
1080
- function clDynamicImport(raw) {
1081
- if (raw?.type !== 'call_expression')
1113
+ function clDynamicImport(node, sourceCode) {
1114
+ if (node.type !== 'call_expression')
1082
1115
  return null;
1083
- const fn = raw.firstChild;
1116
+ const fn = node.children?.[0];
1084
1117
  const isImport = fn?.type === 'import';
1085
- const isRequire = fn?.type === 'identifier' && fn?.text === 'require';
1118
+ const isRequire = fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require';
1086
1119
  if (!isImport && !isRequire)
1087
1120
  return null;
1088
- const args = raw.children?.find((c) => c.type === 'arguments');
1121
+ const args = node.children?.find((c) => c.type === 'arguments');
1089
1122
  const stringNode = args?.children?.find((c) => c.type === 'string');
1090
- if (stringNode && stringNode.text.length >= 2) {
1091
- return { specifier: stringNode.text.slice(1, -1) };
1123
+ if (stringNode) {
1124
+ const text = getNodeText(stringNode, sourceCode);
1125
+ if (text.length >= 2) {
1126
+ return { specifier: text.slice(1, -1) };
1127
+ }
1092
1128
  }
1093
1129
  // A template literal with no `${…}` substitution is a compile-time constant,
1094
1130
  // not a computed specifier — `import(\`@babel/plugin-syntax-jsx\`)` resolves
@@ -1098,15 +1134,17 @@ function clDynamicImport(raw) {
1098
1134
  if (templateNode) {
1099
1135
  const hasSubstitution = (templateNode.children ?? []).some((c) => c.type === 'template_substitution');
1100
1136
  if (!hasSubstitution) {
1101
- const t = templateNode.text;
1137
+ const t = getNodeText(templateNode, sourceCode);
1102
1138
  if (t.length >= 2 && t.startsWith('`') && t.endsWith('`')) {
1103
1139
  return { specifier: t.slice(1, -1) };
1104
1140
  }
1105
1141
  }
1106
1142
  }
1107
1143
  // Computed specifier — the target module cannot be resolved statically.
1108
- const named = args?.namedChildren?.[0];
1109
- const expression = named?.text ?? (args?.text ?? '').replace(/^\(|\)$/g, '').trim();
1144
+ const named = args?.children?.[0];
1145
+ const expression = named
1146
+ ? getNodeText(named, sourceCode)
1147
+ : getNodeText(args, sourceCode).replace(/^\(|\)$/g, '').trim();
1110
1148
  return { computed: true, expression };
1111
1149
  }
1112
1150
  /**
@@ -1119,23 +1157,26 @@ function clDynamicImport(raw) {
1119
1157
  * names), since Go has no `export` keyword. Dynamic `import()`/`require()`
1120
1158
  * with a static string argument also record an import edge (Spec 58 R2).
1121
1159
  */
1122
- function clCollectFileInfo(root, lang) {
1160
+ function clCollectFileInfo(root, lang, sourceCode) {
1123
1161
  const imports = new Set();
1124
1162
  const unresolvedDynamicImports = [];
1125
1163
  let hasExports = false;
1126
1164
  walkAST(root, (node) => {
1127
- const raw = node.raw;
1128
1165
  if (node.type === 'import_statement') {
1129
- const source = clRawField(raw, 'source');
1130
- if (source?.text)
1131
- imports.add(source.text.replace(/^['"]|['"]$/g, ''));
1166
+ const source = getFieldNode(node, 'source');
1167
+ if (source) {
1168
+ const text = getNodeText(source, sourceCode);
1169
+ imports.add(text.replace(/^['"]|['"]$/g, ''));
1170
+ }
1132
1171
  }
1133
1172
  else if (node.type === 'import_declaration') {
1134
- for (const spec of raw?.namedChildren ?? []) {
1173
+ for (const spec of node.children ?? []) {
1135
1174
  if (spec.type === 'import_spec') {
1136
- const p = clRawField(spec, 'path');
1137
- if (p?.text)
1138
- imports.add(p.text.replace(/^['"]|['"]$/g, ''));
1175
+ const p = getFieldNode(spec, 'path');
1176
+ if (p) {
1177
+ const text = getNodeText(p, sourceCode);
1178
+ imports.add(text.replace(/^['"]|['"]$/g, ''));
1179
+ }
1139
1180
  }
1140
1181
  }
1141
1182
  }
@@ -1144,109 +1185,113 @@ function clCollectFileInfo(root, lang) {
1144
1185
  // Re-exports (`export { x } from './y'`, `export * from './y'`) are import
1145
1186
  // edges for reachability: a barrel re-exporting a module marks it live,
1146
1187
  // not dead. The `source` field is present only on the `from` form.
1147
- const source = clRawField(raw, 'source');
1148
- if (source?.text)
1149
- imports.add(source.text.replace(/^['"]|['"]$/g, ''));
1188
+ const source = getFieldNode(node, 'source');
1189
+ if (source) {
1190
+ const text = getNodeText(source, sourceCode);
1191
+ imports.add(text.replace(/^['"]|['"]$/g, ''));
1192
+ }
1150
1193
  }
1151
1194
  else if (node.type === 'call_expression') {
1152
- const dyn = clDynamicImport(raw);
1195
+ const dyn = clDynamicImport(node, sourceCode);
1153
1196
  if (dyn && 'specifier' in dyn) {
1154
1197
  imports.add(dyn.specifier);
1155
1198
  }
1156
1199
  else if (dyn) {
1157
- unresolvedDynamicImports.push({ line: (raw?.startPosition?.row ?? 0) + 1, expression: dyn.expression });
1200
+ unresolvedDynamicImports.push({ line: node.location.start.line, expression: dyn.expression });
1158
1201
  }
1159
1202
  }
1160
1203
  });
1161
1204
  return { imports: [...imports], hasExports, unresolvedDynamicImports };
1162
1205
  }
1163
1206
  // ── TS/JS extraction ─────────────────────────────────────────────────────────
1164
- function clExtractTSParams(raw) {
1207
+ function clExtractTSParams(node, sourceCode) {
1165
1208
  const params = [];
1166
- const formal = clRawNamedChild(raw, 'formal_parameters');
1167
- for (const p of formal?.namedChildren ?? []) {
1209
+ const formal = node.children?.find((c) => c.type === 'formal_parameters');
1210
+ for (const p of formal?.children ?? []) {
1168
1211
  if (p.type !== 'required_parameter' && p.type !== 'optional_parameter')
1169
1212
  continue;
1170
- const name = clRawField(p, 'name')?.text ?? clRawNamedChild(p, 'identifier')?.text;
1213
+ const nameNode = getFieldNode(p, 'name') ?? p.children?.find((c) => c.type === 'identifier');
1214
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1171
1215
  if (!name)
1172
1216
  continue;
1173
- const type = clRawField(p, 'type')?.text?.trim();
1217
+ const typeNode = getFieldNode(p, 'type');
1218
+ const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
1174
1219
  params.push({ name, type, optional: p.type === 'optional_parameter', language: 'typescript' });
1175
1220
  }
1176
1221
  return params;
1177
1222
  }
1178
- function clExtractTSInterfaceParams(raw) {
1223
+ function clExtractTSInterfaceParams(node, sourceCode) {
1179
1224
  const params = [];
1180
- const walk = (node) => {
1181
- if (!node)
1182
- return;
1183
- if (node.type === 'property_signature') {
1184
- const name = clRawField(node, 'name')?.text ?? clRawNamedChild(node, 'property_identifier')?.text;
1225
+ const walk = (n) => {
1226
+ if (n.type === 'property_signature') {
1227
+ const nameNode = getFieldNode(n, 'name') ?? n.children?.find((c) => c.type === 'property_identifier');
1228
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1185
1229
  if (!name)
1186
1230
  return;
1187
- const type = clRawField(node, 'type')?.text?.trim();
1188
- const optional = node.children?.some((c) => c.type === '?') ?? false;
1231
+ const typeNode = getFieldNode(n, 'type');
1232
+ const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
1233
+ const optional = n.children?.some((c) => c.type === '?') ?? false;
1189
1234
  params.push({ name, type, optional, language: 'typescript' });
1190
1235
  }
1191
- for (const child of node.namedChildren ?? [])
1236
+ for (const child of n.children ?? [])
1192
1237
  walk(child);
1193
1238
  };
1194
- walk(raw);
1239
+ walk(node);
1195
1240
  return params;
1196
1241
  }
1197
1242
  function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
1198
- const fileReferences = clCollectFileReferences(root.raw);
1243
+ const fileReferences = clCollectFileReferences(root, sourceCode);
1199
1244
  walkAST(root, (node) => {
1200
- const raw = node.raw;
1201
1245
  if (node.type === 'function_declaration' || node.type === 'method_definition') {
1202
- const name = clNodeName(raw);
1246
+ const name = clNodeName(node, sourceCode);
1203
1247
  if (!name)
1204
1248
  return;
1205
1249
  let displayName = name;
1206
1250
  if (node.type === 'method_definition') {
1207
- let p = raw?.parent;
1251
+ let p = node.parent ?? null;
1208
1252
  while (p) {
1209
1253
  if (p.type === 'class_declaration') {
1210
- const cn = clNodeName(p);
1254
+ const cn = clNodeName(p, sourceCode);
1211
1255
  if (cn)
1212
1256
  displayName = `${cn}.${name}`;
1213
1257
  break;
1214
1258
  }
1215
- p = p.parent;
1259
+ p = p.parent ?? null;
1216
1260
  }
1217
1261
  }
1218
- const line = (raw?.startPosition?.row ?? 0) + 1;
1219
- const entity = clMakeFunction(filePath, lang, displayName, line, clSignatureText(raw, sourceCode) || raw?.text || '', clBodyText(raw, sourceCode), clExtractTSParams(raw), clCollectCallees(raw), isExported(node), calculateComplexity(node), node.type === 'method_definition');
1262
+ const line = node.location.start.line;
1263
+ const entity = clMakeFunction(filePath, lang, displayName, line, clSignatureText(node, sourceCode) || getNodeText(node, sourceCode), clBodyText(node, sourceCode), clExtractTSParams(node, sourceCode), clCollectCallees(node, sourceCode), isExported(node), calculateComplexity(node), node.type === 'method_definition');
1220
1264
  entity.metadata.fileReferences = fileReferences;
1221
1265
  out.push(entity);
1222
1266
  return;
1223
1267
  }
1224
1268
  if (node.type === 'variable_declarator') {
1225
- const arrow = clRawNamedChild(raw, 'arrow_function');
1269
+ const arrow = node.children?.find((c) => c.type === 'arrow_function');
1226
1270
  if (!arrow)
1227
1271
  return;
1228
- const name = clRawNamedChild(raw, 'identifier')?.text;
1272
+ const nameNode = node.children?.find((c) => c.type === 'identifier');
1273
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1229
1274
  if (!name)
1230
1275
  return;
1231
- const line = (raw?.startPosition?.row ?? 0) + 1;
1232
- const entity = clMakeFunction(filePath, lang, name, line, clSignatureText(arrow, sourceCode) || arrow?.text || '', clBodyText(arrow, sourceCode), [], clCollectCallees(arrow), isExported(node), calculateComplexity(node));
1276
+ const line = node.location.start.line;
1277
+ const entity = clMakeFunction(filePath, lang, name, line, clSignatureText(arrow, sourceCode) || getNodeText(arrow, sourceCode), clBodyText(arrow, sourceCode), [], clCollectCallees(arrow, sourceCode), isExported(node), calculateComplexity(node));
1233
1278
  entity.metadata.fileReferences = fileReferences;
1234
1279
  out.push(entity);
1235
1280
  return;
1236
1281
  }
1237
1282
  if (node.type === 'interface_declaration') {
1238
- const name = clNodeName(raw);
1283
+ const name = clNodeName(node, sourceCode);
1239
1284
  if (!name)
1240
1285
  return;
1241
- const line = (raw?.startPosition?.row ?? 0) + 1;
1286
+ const line = node.location.start.line;
1242
1287
  out.push({
1243
1288
  id: clEntityId(filePath, 'interface', name, line),
1244
1289
  name,
1245
1290
  language: lang,
1246
1291
  file: filePath,
1247
1292
  type: 'interface',
1248
- signature: raw?.text ?? '',
1249
- parameters: clExtractTSInterfaceParams(raw),
1293
+ signature: getNodeText(node, sourceCode),
1294
+ parameters: clExtractTSInterfaceParams(node, sourceCode),
1250
1295
  startLine: line,
1251
1296
  visibility: isExported(node) ? 'public' : 'private',
1252
1297
  calls: [],
@@ -1261,70 +1306,75 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
1261
1306
  });
1262
1307
  }
1263
1308
  // ── Go extraction ────────────────────────────────────────────────────────────
1264
- function clGoReceiverType(receiver) {
1265
- for (const child of receiver?.namedChildren ?? []) {
1309
+ function clGoReceiverType(receiver, sourceCode) {
1310
+ for (const child of receiver.children ?? []) {
1266
1311
  if (child.type === 'parameter_declaration') {
1267
- const typeNode = clRawField(child, 'type');
1312
+ const typeNode = getFieldNode(child, 'type');
1268
1313
  if (typeNode)
1269
- return typeNode.text.replace(/^\*/, '');
1314
+ return getNodeText(typeNode, sourceCode).replace(/^\*/, '');
1270
1315
  }
1271
1316
  }
1272
1317
  return undefined;
1273
1318
  }
1274
- function clExtractGoParams(raw) {
1319
+ function clExtractGoParams(node, sourceCode) {
1275
1320
  const params = [];
1276
- const paramList = clRawField(raw, 'parameters');
1277
- for (const p of paramList?.namedChildren ?? []) {
1321
+ const paramList = getFieldNode(node, 'parameters');
1322
+ for (const p of paramList?.children ?? []) {
1278
1323
  if (p.type !== 'parameter_declaration')
1279
1324
  continue;
1280
- const name = clRawField(p, 'name')?.text ?? clRawField(p, 'type')?.text;
1281
- const type = clRawField(p, 'type')?.text?.trim();
1325
+ const nameNode = getFieldNode(p, 'name') ?? getFieldNode(p, 'type');
1326
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1327
+ const typeNode = getFieldNode(p, 'type');
1328
+ const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
1282
1329
  if (!name && !type)
1283
1330
  continue;
1284
1331
  params.push({ name: name ?? type ?? '<unknown>', type, optional: false, language: 'go' });
1285
1332
  }
1286
1333
  return params;
1287
1334
  }
1288
- function clExtractGoStructFields(structNode) {
1335
+ function clExtractGoStructFields(structNode, sourceCode) {
1289
1336
  const fields = [];
1290
- for (const f of structNode?.namedChildren ?? []) {
1337
+ for (const f of structNode.children ?? []) {
1291
1338
  if (f.type !== 'field_declaration')
1292
1339
  continue;
1293
- const name = clRawField(f, 'name')?.text;
1340
+ const nameNode = getFieldNode(f, 'name');
1341
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1294
1342
  if (!name)
1295
1343
  continue;
1296
- const type = clRawField(f, 'type')?.text?.trim();
1297
- const tag = clRawField(f, 'tag')?.text;
1344
+ const typeNode = getFieldNode(f, 'type');
1345
+ const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
1346
+ const tagNode = getFieldNode(f, 'tag');
1347
+ const tag = tagNode ? getNodeText(tagNode, sourceCode) : undefined;
1298
1348
  fields.push({ name, type, isExported: clIsExportedGo(name), tag });
1299
1349
  }
1300
1350
  return fields;
1301
1351
  }
1302
1352
  function clExtractGoEntities(root, filePath, sourceCode, out) {
1303
- const fileReferences = clCollectFileReferences(root.raw);
1353
+ const fileReferences = clCollectFileReferences(root, sourceCode);
1304
1354
  walkAST(root, (node) => {
1305
- const raw = node.raw;
1306
1355
  if (node.type === 'function_declaration') {
1307
- const name = clNodeName(raw);
1356
+ const name = clNodeName(node, sourceCode);
1308
1357
  if (!name)
1309
1358
  return;
1310
- const receiver = clRawField(raw, 'receiver');
1311
- const receiverType = receiver ? clGoReceiverType(receiver) : undefined;
1359
+ const receiver = getFieldNode(node, 'receiver');
1360
+ const receiverType = receiver ? clGoReceiverType(receiver, sourceCode) : undefined;
1312
1361
  const displayName = receiverType ? `${receiverType}.${name}` : name;
1313
- const line = (raw?.startPosition?.row ?? 0) + 1;
1314
- const entity = clMakeFunction(filePath, 'go', displayName, line, clSignatureText(raw, sourceCode) || raw?.text || '', clBodyText(raw, sourceCode), clExtractGoParams(raw), clCollectCallees(raw), clIsExportedGo(name), calculateComplexity(node));
1362
+ const line = node.location.start.line;
1363
+ const entity = clMakeFunction(filePath, 'go', displayName, line, clSignatureText(node, sourceCode) || getNodeText(node, sourceCode), clBodyText(node, sourceCode), clExtractGoParams(node, sourceCode), clCollectCallees(node, sourceCode), clIsExportedGo(name), calculateComplexity(node));
1315
1364
  entity.metadata.fileReferences = fileReferences;
1316
1365
  out.push(entity);
1317
1366
  return;
1318
1367
  }
1319
1368
  if (node.type === 'type_declaration') {
1320
- for (const spec of raw?.namedChildren ?? []) {
1369
+ for (const spec of node.children ?? []) {
1321
1370
  if (spec.type !== 'type_spec')
1322
1371
  continue;
1323
- const name = clRawField(spec, 'name')?.text;
1372
+ const nameNode = getFieldNode(spec, 'name');
1373
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1324
1374
  if (!name)
1325
1375
  continue;
1326
- const typeNode = clRawField(spec, 'type');
1327
- const line = (spec?.startPosition?.row ?? 0) + 1;
1376
+ const typeNode = getFieldNode(spec, 'type');
1377
+ const line = spec.location.start.line;
1328
1378
  if (typeNode?.type === 'struct_type') {
1329
1379
  out.push({
1330
1380
  id: clEntityId(filePath, 'struct', name, line),
@@ -1332,7 +1382,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
1332
1382
  language: 'go',
1333
1383
  file: filePath,
1334
1384
  type: 'struct',
1335
- signature: spec?.text ?? '',
1385
+ signature: getNodeText(spec, sourceCode),
1336
1386
  parameters: [],
1337
1387
  startLine: line,
1338
1388
  visibility: clIsExportedGo(name) ? 'public' : 'private',
@@ -1341,7 +1391,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
1341
1391
  purpose: '',
1342
1392
  context: '',
1343
1393
  searchTokens: [name.toLowerCase()],
1344
- metadata: { fields: clExtractGoStructFields(typeNode) },
1394
+ metadata: { fields: clExtractGoStructFields(typeNode, sourceCode) },
1345
1395
  });
1346
1396
  }
1347
1397
  else if (typeNode?.type === 'interface_type') {
@@ -1351,7 +1401,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
1351
1401
  language: 'go',
1352
1402
  file: filePath,
1353
1403
  type: 'interface',
1354
- signature: spec?.text ?? '',
1404
+ signature: getNodeText(spec, sourceCode),
1355
1405
  parameters: [],
1356
1406
  startLine: line,
1357
1407
  visibility: clIsExportedGo(name) ? 'public' : 'private',
@@ -1390,7 +1440,7 @@ export function createCrossLanguageEntityVisitor() {
1390
1440
  else {
1391
1441
  clExtractTSEntities(root, filePath, sourceCode, lang, entities);
1392
1442
  }
1393
- const fileInfo = clCollectFileInfo(root, lang);
1443
+ const fileInfo = clCollectFileInfo(root, lang, sourceCode);
1394
1444
  // Go has no `export` keyword — an exported symbol is a capitalized
1395
1445
  // top-level name, which the entity extractor already records as public.
1396
1446
  const hasExports = lang === 'go'
@@ -1464,27 +1514,35 @@ function clIsEntryPointFile(fp) {
1464
1514
  return true;
1465
1515
  return false;
1466
1516
  }
1517
+ /** Resolve one import specifier to zero or one internal file via the classifier. */
1518
+ function clResolveImport(dep, sourceFile, options) {
1519
+ const { classification, resolvedPath } = classifyImportSpecifier(dep, sourceFile, options.corpusFiles, {
1520
+ virtualModules: options.virtualModules,
1521
+ aliasPatterns: options.tsconfigAliases?.pathPatterns ?? [],
1522
+ pathMappings: options.tsconfigAliases?.paths ?? {},
1523
+ baseUrl: options.tsconfigAliases?.baseUrl,
1524
+ projectRoot: options.projectRoot,
1525
+ });
1526
+ if (classification === 'internal-resolved' && resolvedPath)
1527
+ return [resolvedPath];
1528
+ return [];
1529
+ }
1467
1530
  /**
1468
1531
  * Compute file-level reachability from per-file imports.
1469
1532
  *
1470
1533
  * A file is a framework entry point (live → 1.0), imported by another file
1471
- * (live → 0.5), or referenced by nothing (dead → 0.0). The resolver mirrors
1472
- * importGraph.ts and handles relative paths, npm/alias basenames, and fuzzy
1473
- * path-segment matches.
1534
+ * (live → 0.5), or referenced by nothing (dead → 0.0). Resolution goes through
1535
+ * `classifyImportSpecifier` (relative paths and tsconfig `paths` aliases like
1536
+ * `@/`) instead of the legacy basename/fuzzy matcher, so an alias import creates
1537
+ * a real edge and a bare package import never fabricates a local one.
1474
1538
  */
1475
- function clComputeReachability(fileFacts) {
1539
+ function clComputeReachability(fileFacts, options) {
1476
1540
  const filePaths = new Set(fileFacts.keys());
1477
- const moduleToFile = new Map();
1478
- for (const fp of filePaths) {
1479
- const bn = basenameNoExt(fp);
1480
- if (!moduleToFile.has(bn))
1481
- moduleToFile.set(bn, new Set());
1482
- moduleToFile.get(bn).add(fp);
1483
- }
1541
+ const packageEntries = options.packageEntryPoints ?? new Set();
1484
1542
  const importersOf = new Map();
1485
1543
  for (const [fp, info] of fileFacts) {
1486
1544
  for (const dep of info.imports) {
1487
- const targets = resolveDependency(dep, filePaths, moduleToFile, fp);
1545
+ const targets = clResolveImport(dep, fp, options);
1488
1546
  for (const t of targets) {
1489
1547
  if (t === fp)
1490
1548
  continue;
@@ -1496,7 +1554,7 @@ function clComputeReachability(fileFacts) {
1496
1554
  }
1497
1555
  const reachability = new Map();
1498
1556
  for (const fp of filePaths) {
1499
- const entry = clIsEntryPointFile(fp);
1557
+ const entry = clIsEntryPointFile(fp) || packageEntries.has(fp);
1500
1558
  const imported = (importersOf.get(fp)?.size ?? 0) > 0;
1501
1559
  reachability.set(fp, entry ? 1 : imported ? 0.5 : 0);
1502
1560
  }
@@ -1717,7 +1775,18 @@ export function createDependencyGraphReducer() {
1717
1775
  // ── File-level reachability: unreferenced modules + ranking axis ──
1718
1776
  const fileFacts = clFileFacts(allFacts);
1719
1777
  if (fileFacts.size > 0) {
1720
- const { reachability, importersOf } = clComputeReachability(fileFacts);
1778
+ // `context.config` is the reducer's merged config (namespace + `_infra`);
1779
+ // a bare reducer context in a unit test omits it, so fall back to the
1780
+ // file facts themselves as the corpus and a no-alias classification.
1781
+ const infraConfig = (context.config ?? {});
1782
+ const packageEntrySet = new Set(infraConfig.packageEntryPoints ?? []);
1783
+ const { reachability, importersOf } = clComputeReachability(fileFacts, {
1784
+ corpusFiles: new Set(infraConfig.corpusFiles ?? [...fileFacts.keys()]),
1785
+ virtualModules: infraConfig.importVirtualModules ?? DEFAULT_VIRTUAL_MODULES,
1786
+ tsconfigAliases: infraConfig.tsconfigAliases,
1787
+ packageEntryPoints: packageEntrySet,
1788
+ projectRoot: context.projectRoot ?? '',
1789
+ });
1721
1790
  // Persist reachability to graph_cache so the post-pipeline reorder
1722
1791
  // (auditRunner hotspot block) can rank every finding by live/dead.
1723
1792
  if (context.indexHandle?.rawDb) {
@@ -1747,7 +1816,7 @@ export function createDependencyGraphReducer() {
1747
1816
  continue;
1748
1817
  if (clIsTestFile(fp))
1749
1818
  continue;
1750
- if (clIsEntryPointFile(fp))
1819
+ if (clIsEntryPointFile(fp) || packageEntrySet.has(fp))
1751
1820
  continue;
1752
1821
  if ((importersOf.get(fp)?.size ?? 0) > 0)
1753
1822
  continue;
@@ -1785,15 +1854,15 @@ export function createDependencyGraphReducer() {
1785
1854
  }
1786
1855
  const HEALTH_SEVERITY = {
1787
1856
  'break-cycles': 'severe',
1788
- 'reduce-coupling': 'high',
1789
- 'split-responsibilities': 'high',
1857
+ 'reduce-coupling': 'advisory',
1858
+ 'split-responsibilities': 'advisory',
1790
1859
  'review-orphans': 'severe',
1791
1860
  };
1792
1861
  for (const s of health.suggestions) {
1793
1862
  violations.push({
1794
1863
  file: '(multiple)',
1795
1864
  line: 0,
1796
- severity: HEALTH_SEVERITY[s.type] ?? 'high',
1865
+ severity: HEALTH_SEVERITY[s.type] ?? 'advisory',
1797
1866
  message: s.description,
1798
1867
  rule: s.type,
1799
1868
  type: s.type,
@@ -2029,7 +2098,7 @@ export function createSchemaCodeVisitor() {
2029
2098
  // Find table references (per-file, uses allTables for short-id false-positive filtering)
2030
2099
  const { references: tableRefs, unresolved } = findTableReferences(ast, adapter, sourceCode, { config: schemaConfig, provenanceContext, allTables });
2031
2100
  // Record schema usage → emit as indexFacts via the shared instance
2032
- a.recordTableUsage(ast, adapter, context.filePath, tableRefs);
2101
+ a.recordTableUsage(ast, adapter, context.filePath, tableRefs, sourceCode);
2033
2102
  const pending = a.getPendingSchemaRecords();
2034
2103
  // Emit clear-by-file + per-usage index facts
2035
2104
  if (pending.clearFiles.length > 0) {
@@ -2044,10 +2113,13 @@ export function createSchemaCodeVisitor() {
2044
2113
  file_path: usage.filePath,
2045
2114
  table_name: usage.tableName,
2046
2115
  function_name: usage.functionName,
2116
+ function_start_line: usage.functionStartLine,
2117
+ function_start_column: usage.functionStartColumn,
2047
2118
  usage_type: usage.usageType,
2048
2119
  line: usage.line,
2049
2120
  column: usage.column,
2050
2121
  raw_query: usage.rawQuery,
2122
+ origin: usage.origin ?? null,
2051
2123
  },
2052
2124
  });
2053
2125
  }
@@ -2066,9 +2138,14 @@ export function createSchemaCodeVisitor() {
2066
2138
  }
2067
2139
  // Check SQL injection
2068
2140
  violations.push(...checkSQLInjection(ast, adapter, sourceCode));
2069
- // Emit facts for the Stage 3 reducer
2141
+ // Emit facts for the Stage 3 reducer. Fluent-builder references
2142
+ // (`origin: 'query-builder'`) are excluded from the unknown-table check:
2143
+ // a scratch/test table name carried by `db('t').select(...)` is not a
2144
+ // schema catalog miss, and feeding it in flips the 10:1 fail-open ratio.
2070
2145
  const fileFacts = {
2071
- tableRefs: tableRefs.map((r) => ({
2146
+ tableRefs: tableRefs
2147
+ .filter((r) => r.origin !== 'query-builder')
2148
+ .map((r) => ({
2072
2149
  table: r.table,
2073
2150
  type: r.type,
2074
2151
  line: r.location.line,
@@ -2218,6 +2295,12 @@ export function createSchemaReducer() {
2218
2295
  return na - nb;
2219
2296
  return a.filePath.localeCompare(b.filePath);
2220
2297
  });
2298
+ // Drop provenance — the last migration that removed a table, and the tables
2299
+ // that same migration introduced. Lets unknown-table detection distinguish
2300
+ // "never existed" from "existed and was dropped": the latter is a stale code
2301
+ // reference, not a typo. `dropProvenance` ends up holding exactly the tables
2302
+ // dropped and not subsequently recreated (CREATE clears the entry).
2303
+ const dropProvenance = new Map();
2221
2304
  for (const sqlFile of sqlFiles) {
2222
2305
  const before = new Set(knownTables);
2223
2306
  applyMigrationOps(sqlFile.ops, knownTables);
@@ -2229,6 +2312,46 @@ export function createSchemaReducer() {
2229
2312
  tableProvenances.set(table, sources);
2230
2313
  }
2231
2314
  }
2315
+ // Genuinely-new tables introduced by this migration (excludes rename/rebuild
2316
+ // churn like `ALTER … RENAME TO x_old` + re-CREATE of the same name).
2317
+ const createdHere = [];
2318
+ const createdTables = new Set();
2319
+ const droppedTables = new Set();
2320
+ for (const op of sqlFile.ops) {
2321
+ const t = stripIdentifier(op.table);
2322
+ if (op.op === 'CREATE') {
2323
+ createdTables.add(t);
2324
+ if (!before.has(t))
2325
+ createdHere.push(t);
2326
+ }
2327
+ else if (op.op === 'DROP') {
2328
+ droppedTables.add(t);
2329
+ }
2330
+ }
2331
+ for (const op of sqlFile.ops) {
2332
+ const t = stripIdentifier(op.table);
2333
+ if (op.op === 'DROP') {
2334
+ dropProvenance.set(t, { migrationFile: sqlFile.filePath, createdInSameMigration: createdHere });
2335
+ }
2336
+ else if (op.op === 'CREATE') {
2337
+ dropProvenance.delete(t);
2338
+ }
2339
+ }
2340
+ // A table created and dropped within this same file is a self-contained
2341
+ // fixture — a scratch table a test or script creates, uses, then tears
2342
+ // down. Its DROP is teardown, not a migration, so it must neither surface
2343
+ // as a stale-table-reference nor leave the name unknown: keep it known and
2344
+ // clear the drop provenance this file recorded for it. (A genuine dropped
2345
+ // table stays stale because its DROP lives in a migration that never also
2346
+ // CREATEs it — e.g. recall's `generation_queue`, dropped in 0198.)
2347
+ for (const t of createdTables) {
2348
+ if (droppedTables.has(t)) {
2349
+ knownTables.add(t);
2350
+ if (dropProvenance.get(t)?.migrationFile === sqlFile.filePath) {
2351
+ dropProvenance.delete(t);
2352
+ }
2353
+ }
2354
+ }
2232
2355
  }
2233
2356
  // 1b. ORM tables from code files — with provenance from the table-source registry
2234
2357
  for (const [filePath, fact] of perFile()) {
@@ -2312,6 +2435,42 @@ export function createSchemaReducer() {
2312
2435
  const unknownRefs = allTableRefs.filter(ref => !knownTables.has(ref.table));
2313
2436
  if (unknownRefs.length / Math.max(knownTables.size, 1) <= 10) {
2314
2437
  for (const ref of unknownRefs) {
2438
+ // The table existed and was dropped in a migration — a stale code
2439
+ // reference, not a typo. Name the dropping migration and the tables
2440
+ // that migration introduced (evidence, not proof of a successor).
2441
+ const drop = dropProvenance.get(ref.table);
2442
+ if (drop) {
2443
+ const migrationName = drop.migrationFile.split('/').pop() ?? drop.migrationFile;
2444
+ const created = drop.createdInSameMigration;
2445
+ const msg = created.length > 0
2446
+ ? `${ref.table} was dropped in ${migrationName}; that migration creates ${joinEnglish(created)}.`
2447
+ : `${ref.table} was dropped in ${migrationName} and was not recreated.`;
2448
+ violations.push({
2449
+ file: ref.file,
2450
+ line: ref.line,
2451
+ column: ref.column,
2452
+ severity: 'critical',
2453
+ message: msg,
2454
+ rule: 'stale-table-reference',
2455
+ analyzer: 'schema',
2456
+ symbol: ref.table,
2457
+ resolution: {
2458
+ action: 'update-stale-reference',
2459
+ // The tables `created` are evidence the migration replaced the
2460
+ // dropped table, not proof of a drop-in successor — schemas can
2461
+ // be split or changed incompatibly, in which case the file must
2462
+ // be removed rather than updated. The summary names them as
2463
+ // context and leaves update-vs-remove to the reviewer.
2464
+ summary: created.length > 0
2465
+ ? `The table '${ref.table}' was dropped in ${migrationName}. That migration introduces ${joinEnglish(created)} — review this reference and update or remove it.`
2466
+ : `The table '${ref.table}' was dropped in ${migrationName} and was not recreated — update or remove this reference.`,
2467
+ symbols: created.length > 0 ? created : [ref.table],
2468
+ files: [ref.file],
2469
+ lines: [ref.line],
2470
+ },
2471
+ });
2472
+ continue;
2473
+ }
2315
2474
  const suggestions = getNearestTableSuggestions(ref.table, knownTables, 2);
2316
2475
  const msg = suggestions.length > 0
2317
2476
  ? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`