code-auditor-mcp 4.1.1 → 5.0.2

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 (1087) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +381 -0
  3. package/README.md +4 -4
  4. package/configs/hhra-compat.json +0 -8
  5. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts +1 -1
  6. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts.map +1 -1
  7. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js +7 -1
  8. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js.map +1 -1
  9. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts +2 -8
  10. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts.map +1 -1
  11. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js +4 -4
  12. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js.map +1 -1
  13. package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts +1 -1
  14. package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts.map +1 -1
  15. package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.js.map +1 -1
  16. package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts +2 -2
  17. package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts.map +1 -1
  18. package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.js.map +1 -1
  19. package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts +1 -1
  20. package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts.map +1 -1
  21. package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.js.map +1 -1
  22. package/dist/analyzers/applicability.d.ts +42 -0
  23. package/dist/analyzers/applicability.d.ts.map +1 -1
  24. package/dist/analyzers/applicability.js +74 -0
  25. package/dist/analyzers/applicability.js.map +1 -1
  26. package/dist/analyzers/cross-language/APIContractAnalyzer.js +18 -24
  27. package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
  28. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +30 -23
  29. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  30. package/dist/analyzers/cross-language/DependencyGraphBuilder.js +113 -76
  31. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  32. package/dist/analyzers/cross-language/SchemaValidator.d.ts +2 -0
  33. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  34. package/dist/analyzers/cross-language/SchemaValidator.js +8 -3
  35. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  36. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
  37. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +210 -148
  38. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
  39. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  40. package/dist/analyzers/documentationAnalyzer.js +23 -28
  41. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  42. package/dist/analyzers/handleIdentification.d.ts +356 -0
  43. package/dist/analyzers/handleIdentification.d.ts.map +1 -0
  44. package/dist/analyzers/handleIdentification.js +427 -0
  45. package/dist/analyzers/handleIdentification.js.map +1 -0
  46. package/dist/analyzers/manifestStaleness.d.ts +64 -0
  47. package/dist/analyzers/manifestStaleness.d.ts.map +1 -0
  48. package/dist/analyzers/manifestStaleness.js +115 -0
  49. package/dist/analyzers/manifestStaleness.js.map +1 -0
  50. package/dist/analyzers/orgFilterTiers.d.ts +73 -1
  51. package/dist/analyzers/orgFilterTiers.d.ts.map +1 -1
  52. package/dist/analyzers/orgFilterTiers.js +166 -1
  53. package/dist/analyzers/orgFilterTiers.js.map +1 -1
  54. package/dist/analyzers/provenance.d.ts +251 -57
  55. package/dist/analyzers/provenance.d.ts.map +1 -1
  56. package/dist/analyzers/provenance.js +1109 -429
  57. package/dist/analyzers/provenance.js.map +1 -1
  58. package/dist/analyzers/reactAnalyzer.d.ts +2 -0
  59. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  60. package/dist/analyzers/reactAnalyzer.js +2 -0
  61. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  62. package/dist/analyzers/receiverResolution.d.ts +183 -0
  63. package/dist/analyzers/receiverResolution.d.ts.map +1 -0
  64. package/dist/analyzers/receiverResolution.js +540 -0
  65. package/dist/analyzers/receiverResolution.js.map +1 -0
  66. package/dist/analyzers/receiverRoot.d.ts +348 -0
  67. package/dist/analyzers/receiverRoot.d.ts.map +1 -0
  68. package/dist/analyzers/receiverRoot.js +1032 -0
  69. package/dist/analyzers/receiverRoot.js.map +1 -0
  70. package/dist/analyzers/ruleRegistry.d.ts +12 -68
  71. package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
  72. package/dist/analyzers/ruleRegistry.js +32 -342
  73. package/dist/analyzers/ruleRegistry.js.map +1 -1
  74. package/dist/analyzers/ruleTiming.d.ts +10 -0
  75. package/dist/analyzers/ruleTiming.d.ts.map +1 -1
  76. package/dist/analyzers/ruleTiming.js +20 -0
  77. package/dist/analyzers/ruleTiming.js.map +1 -1
  78. package/dist/analyzers/sqlLiteral.d.ts +76 -0
  79. package/dist/analyzers/sqlLiteral.d.ts.map +1 -0
  80. package/dist/analyzers/sqlLiteral.js +419 -0
  81. package/dist/analyzers/sqlLiteral.js.map +1 -0
  82. package/dist/analyzers/tsEcosystem.d.ts +115 -0
  83. package/dist/analyzers/tsEcosystem.d.ts.map +1 -0
  84. package/dist/analyzers/tsEcosystem.js +241 -0
  85. package/dist/analyzers/tsEcosystem.js.map +1 -0
  86. package/dist/analyzers/tsExpressionDescriptor.d.ts +156 -0
  87. package/dist/analyzers/tsExpressionDescriptor.d.ts.map +1 -0
  88. package/dist/analyzers/tsExpressionDescriptor.js +374 -0
  89. package/dist/analyzers/tsExpressionDescriptor.js.map +1 -0
  90. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +6 -0
  91. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
  92. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +107 -44
  93. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
  94. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  95. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +23 -35
  96. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  97. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +287 -49
  98. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  99. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +2130 -512
  100. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  101. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  102. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +2 -18
  103. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  104. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +0 -33
  105. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  106. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +20 -143
  107. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  108. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +4 -3
  109. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  110. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +36 -13
  111. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  112. package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts.map +1 -1
  113. package/dist/analyzers/universal/UniversalSecretsAnalyzer.js +6 -0
  114. package/dist/analyzers/universal/UniversalSecretsAnalyzer.js.map +1 -1
  115. package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts +15 -0
  116. package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts.map +1 -1
  117. package/dist/analyzers/universal/UniversalSecurityAnalyzer.js +25 -0
  118. package/dist/analyzers/universal/UniversalSecurityAnalyzer.js.map +1 -1
  119. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +21 -32
  120. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  121. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +166 -196
  122. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  123. package/dist/analyzers/universal/functionConcerns.d.ts +3 -0
  124. package/dist/analyzers/universal/functionConcerns.d.ts.map +1 -1
  125. package/dist/analyzers/universal/functionConcerns.js +4 -1
  126. package/dist/analyzers/universal/functionConcerns.js.map +1 -1
  127. package/dist/analyzers/universal/schema/codeAnalysis.d.ts +310 -69
  128. package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
  129. package/dist/analyzers/universal/schema/codeAnalysis.js +709 -383
  130. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
  131. package/dist/analyzers/universal/schema/config.d.ts +3 -14
  132. package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
  133. package/dist/analyzers/universal/schema/config.js +3 -16
  134. package/dist/analyzers/universal/schema/config.js.map +1 -1
  135. package/dist/analyzers/universal/schema/discovery.d.ts +22 -1
  136. package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -1
  137. package/dist/analyzers/universal/schema/discovery.js +36 -20
  138. package/dist/analyzers/universal/schema/discovery.js.map +1 -1
  139. package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -1
  140. package/dist/analyzers/universal/schema/jsonSchema.js +13 -3
  141. package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
  142. package/dist/analyzers/universal/schema/migrations.d.ts +187 -27
  143. package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -1
  144. package/dist/analyzers/universal/schema/migrations.js +443 -186
  145. package/dist/analyzers/universal/schema/migrations.js.map +1 -1
  146. package/dist/analyzers/universal/schema/types.d.ts +17 -3
  147. package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
  148. package/dist/analyzers/universal/schema/violations.js +1 -1
  149. package/dist/analyzers/universal/schema/violations.js.map +1 -1
  150. package/dist/analyzers/universal/styleScale.d.ts +52 -0
  151. package/dist/analyzers/universal/styleScale.d.ts.map +1 -0
  152. package/dist/analyzers/universal/styleScale.js +95 -0
  153. package/dist/analyzers/universal/styleScale.js.map +1 -0
  154. package/dist/applyDataDirEnv.js +1 -1
  155. package/dist/applyDataDirEnv.js.map +1 -1
  156. package/dist/auditRouter.d.ts +26 -30
  157. package/dist/auditRouter.d.ts.map +1 -1
  158. package/dist/auditRouter.js +373 -143
  159. package/dist/auditRouter.js.map +1 -1
  160. package/dist/auditRunner.d.ts +66 -1
  161. package/dist/auditRunner.d.ts.map +1 -1
  162. package/dist/auditRunner.js +1544 -1034
  163. package/dist/auditRunner.js.map +1 -1
  164. package/dist/baseline.d.ts +30 -3
  165. package/dist/baseline.d.ts.map +1 -1
  166. package/dist/baseline.js +70 -9
  167. package/dist/baseline.js.map +1 -1
  168. package/dist/cli.js +983 -821
  169. package/dist/cli.js.map +1 -1
  170. package/dist/codeIndex/analyzerConfig.d.ts +60 -0
  171. package/dist/codeIndex/analyzerConfig.d.ts.map +1 -0
  172. package/dist/codeIndex/analyzerConfig.js +98 -0
  173. package/dist/codeIndex/analyzerConfig.js.map +1 -0
  174. package/dist/codeIndex/auditResults.d.ts +39 -0
  175. package/dist/codeIndex/auditResults.d.ts.map +1 -0
  176. package/dist/codeIndex/auditResults.js +99 -0
  177. package/dist/codeIndex/auditResults.js.map +1 -0
  178. package/dist/codeIndex/codeMap.d.ts +59 -0
  179. package/dist/codeIndex/codeMap.d.ts.map +1 -0
  180. package/dist/codeIndex/codeMap.js +72 -0
  181. package/dist/codeIndex/codeMap.js.map +1 -0
  182. package/dist/codeIndex/conventions.d.ts +33 -0
  183. package/dist/codeIndex/conventions.d.ts.map +1 -0
  184. package/dist/codeIndex/conventions.js +84 -0
  185. package/dist/codeIndex/conventions.js.map +1 -0
  186. package/dist/codeIndex/coverage.d.ts +78 -0
  187. package/dist/codeIndex/coverage.d.ts.map +1 -0
  188. package/dist/codeIndex/coverage.js +164 -0
  189. package/dist/codeIndex/coverage.js.map +1 -0
  190. package/dist/codeIndex/dependencyGraph.d.ts +97 -0
  191. package/dist/codeIndex/dependencyGraph.d.ts.map +1 -0
  192. package/dist/codeIndex/dependencyGraph.js +241 -0
  193. package/dist/codeIndex/dependencyGraph.js.map +1 -0
  194. package/dist/codeIndex/functionIndex.d.ts +111 -0
  195. package/dist/codeIndex/functionIndex.d.ts.map +1 -0
  196. package/dist/codeIndex/functionIndex.js +306 -0
  197. package/dist/codeIndex/functionIndex.js.map +1 -0
  198. package/dist/codeIndex/inferredReceivers.d.ts +32 -0
  199. package/dist/codeIndex/inferredReceivers.d.ts.map +1 -0
  200. package/dist/codeIndex/inferredReceivers.js +41 -0
  201. package/dist/codeIndex/inferredReceivers.js.map +1 -0
  202. package/dist/codeIndex/meta.d.ts +30 -0
  203. package/dist/codeIndex/meta.d.ts.map +1 -0
  204. package/dist/codeIndex/meta.js +37 -0
  205. package/dist/codeIndex/meta.js.map +1 -0
  206. package/dist/codeIndex/migrations.d.ts +96 -0
  207. package/dist/codeIndex/migrations.d.ts.map +1 -0
  208. package/dist/codeIndex/migrations.js +1302 -0
  209. package/dist/codeIndex/migrations.js.map +1 -0
  210. package/dist/codeIndex/projectTasks.d.ts +54 -0
  211. package/dist/codeIndex/projectTasks.d.ts.map +1 -0
  212. package/dist/codeIndex/projectTasks.js +81 -0
  213. package/dist/codeIndex/projectTasks.js.map +1 -0
  214. package/dist/codeIndex/provenance.d.ts +47 -0
  215. package/dist/codeIndex/provenance.d.ts.map +1 -0
  216. package/dist/codeIndex/provenance.js +48 -0
  217. package/dist/codeIndex/provenance.js.map +1 -0
  218. package/dist/codeIndex/rawSql.d.ts +54 -0
  219. package/dist/codeIndex/rawSql.d.ts.map +1 -0
  220. package/dist/codeIndex/rawSql.js +71 -0
  221. package/dist/codeIndex/rawSql.js.map +1 -0
  222. package/dist/codeIndex/schema.d.ts +99 -0
  223. package/dist/codeIndex/schema.d.ts.map +1 -0
  224. package/dist/codeIndex/schema.js +174 -0
  225. package/dist/codeIndex/schema.js.map +1 -0
  226. package/dist/codeIndex/search.d.ts +40 -0
  227. package/dist/codeIndex/search.d.ts.map +1 -0
  228. package/dist/codeIndex/search.js +316 -0
  229. package/dist/codeIndex/search.js.map +1 -0
  230. package/dist/codeIndex/shared.d.ts +27 -0
  231. package/dist/codeIndex/shared.d.ts.map +1 -0
  232. package/dist/codeIndex/shared.js +46 -0
  233. package/dist/codeIndex/shared.js.map +1 -0
  234. package/dist/codeIndex/sqliteCollection.d.ts +81 -0
  235. package/dist/codeIndex/sqliteCollection.d.ts.map +1 -0
  236. package/dist/codeIndex/sqliteCollection.js +250 -0
  237. package/dist/codeIndex/sqliteCollection.js.map +1 -0
  238. package/dist/codeIndex/whitelist.d.ts +59 -0
  239. package/dist/codeIndex/whitelist.d.ts.map +1 -0
  240. package/dist/codeIndex/whitelist.js +188 -0
  241. package/dist/codeIndex/whitelist.js.map +1 -0
  242. package/dist/codeIndexDB.d.ts +132 -271
  243. package/dist/codeIndexDB.d.ts.map +1 -1
  244. package/dist/codeIndexDB.js +365 -2729
  245. package/dist/codeIndexDB.js.map +1 -1
  246. package/dist/codeIndexService.d.ts +87 -12
  247. package/dist/codeIndexService.d.ts.map +1 -1
  248. package/dist/codeIndexService.js +110 -34
  249. package/dist/codeIndexService.js.map +1 -1
  250. package/dist/componentScanner.d.ts +35 -0
  251. package/dist/componentScanner.d.ts.map +1 -1
  252. package/dist/componentScanner.js +114 -80
  253. package/dist/componentScanner.js.map +1 -1
  254. package/dist/config/configGeneratePath.d.ts +4 -0
  255. package/dist/config/configGeneratePath.d.ts.map +1 -1
  256. package/dist/config/configGeneratePath.js +4 -0
  257. package/dist/config/configGeneratePath.js.map +1 -1
  258. package/dist/config/configLoader.d.ts +15 -0
  259. package/dist/config/configLoader.d.ts.map +1 -1
  260. package/dist/config/configLoader.js +15 -16
  261. package/dist/config/configLoader.js.map +1 -1
  262. package/dist/config/defaults.d.ts +11 -7
  263. package/dist/config/defaults.d.ts.map +1 -1
  264. package/dist/config/defaults.js +16 -14
  265. package/dist/config/defaults.js.map +1 -1
  266. package/dist/config/effectiveConfig.d.ts +17 -2
  267. package/dist/config/effectiveConfig.d.ts.map +1 -1
  268. package/dist/config/effectiveConfig.js +32 -25
  269. package/dist/config/effectiveConfig.js.map +1 -1
  270. package/dist/config/lintConfigReader.d.ts +6 -0
  271. package/dist/config/lintConfigReader.d.ts.map +1 -1
  272. package/dist/config/lintConfigReader.js +6 -0
  273. package/dist/config/lintConfigReader.js.map +1 -1
  274. package/dist/config/staticObjectExtract.d.ts +4 -0
  275. package/dist/config/staticObjectExtract.d.ts.map +1 -1
  276. package/dist/config/staticObjectExtract.js +7 -3
  277. package/dist/config/staticObjectExtract.js.map +1 -1
  278. package/dist/config/thresholdRationales.d.ts +2 -0
  279. package/dist/config/thresholdRationales.d.ts.map +1 -1
  280. package/dist/config/thresholdRationales.js +5 -2
  281. package/dist/config/thresholdRationales.js.map +1 -1
  282. package/dist/constants.d.ts +1 -1
  283. package/dist/conventions/conventionMiner.d.ts +105 -2
  284. package/dist/conventions/conventionMiner.d.ts.map +1 -1
  285. package/dist/conventions/conventionMiner.js +334 -259
  286. package/dist/conventions/conventionMiner.js.map +1 -1
  287. package/dist/coverage/coverageService.js +4 -4
  288. package/dist/coverage/coverageService.js.map +1 -1
  289. package/dist/daemon/args.d.ts +3 -0
  290. package/dist/daemon/args.d.ts.map +1 -1
  291. package/dist/daemon/args.js +3 -0
  292. package/dist/daemon/args.js.map +1 -1
  293. package/dist/daemon/core.d.ts +33 -27
  294. package/dist/daemon/core.d.ts.map +1 -1
  295. package/dist/daemon/core.js +198 -162
  296. package/dist/daemon/core.js.map +1 -1
  297. package/dist/daemon/lspServer.d.ts +8 -0
  298. package/dist/daemon/lspServer.d.ts.map +1 -1
  299. package/dist/daemon/lspServer.js +8 -0
  300. package/dist/daemon/lspServer.js.map +1 -1
  301. package/dist/daemon/main.d.ts +2 -0
  302. package/dist/daemon/main.d.ts.map +1 -1
  303. package/dist/daemon/main.js +22 -4
  304. package/dist/daemon/main.js.map +1 -1
  305. package/dist/daemon/resolve.d.ts +18 -3
  306. package/dist/daemon/resolve.d.ts.map +1 -1
  307. package/dist/daemon/resolve.js +25 -4
  308. package/dist/daemon/resolve.js.map +1 -1
  309. package/dist/daemon/socketClient.d.ts +10 -1
  310. package/dist/daemon/socketClient.d.ts.map +1 -1
  311. package/dist/daemon/socketClient.js +10 -1
  312. package/dist/daemon/socketClient.js.map +1 -1
  313. package/dist/daemon/socketServer.d.ts +13 -0
  314. package/dist/daemon/socketServer.d.ts.map +1 -1
  315. package/dist/daemon/socketServer.js +13 -0
  316. package/dist/daemon/socketServer.js.map +1 -1
  317. package/dist/dataPaths.d.ts +17 -8
  318. package/dist/dataPaths.d.ts.map +1 -1
  319. package/dist/dataPaths.js +16 -44
  320. package/dist/dataPaths.js.map +1 -1
  321. package/dist/dismissals.d.ts +22 -1
  322. package/dist/dismissals.d.ts.map +1 -1
  323. package/dist/dismissals.js +22 -1
  324. package/dist/dismissals.js.map +1 -1
  325. package/dist/enforcement/gate.d.ts +1 -0
  326. package/dist/enforcement/gate.d.ts.map +1 -1
  327. package/dist/enforcement/gate.js +1 -0
  328. package/dist/enforcement/gate.js.map +1 -1
  329. package/dist/fingerprint.d.ts +6 -0
  330. package/dist/fingerprint.d.ts.map +1 -1
  331. package/dist/fingerprint.js +46 -1
  332. package/dist/fingerprint.js.map +1 -1
  333. package/dist/functionScanner.d.ts +30 -1
  334. package/dist/functionScanner.d.ts.map +1 -1
  335. package/dist/functionScanner.js +258 -256
  336. package/dist/functionScanner.js.map +1 -1
  337. package/dist/generators/AWSQConfigGenerator.d.ts +23 -0
  338. package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
  339. package/dist/generators/AWSQConfigGenerator.js +23 -0
  340. package/dist/generators/AWSQConfigGenerator.js.map +1 -1
  341. package/dist/generators/BaseConfigGenerator.d.ts +7 -0
  342. package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
  343. package/dist/generators/BaseConfigGenerator.js +7 -0
  344. package/dist/generators/BaseConfigGenerator.js.map +1 -1
  345. package/dist/generators/ClaudeConfigGenerator.d.ts +23 -0
  346. package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
  347. package/dist/generators/ClaudeConfigGenerator.js +23 -0
  348. package/dist/generators/ClaudeConfigGenerator.js.map +1 -1
  349. package/dist/generators/ClineConfigGenerator.d.ts +23 -0
  350. package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
  351. package/dist/generators/ClineConfigGenerator.js +23 -0
  352. package/dist/generators/ClineConfigGenerator.js.map +1 -1
  353. package/dist/generators/CodeiumConfigGenerator.d.ts +23 -0
  354. package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
  355. package/dist/generators/CodeiumConfigGenerator.js +23 -0
  356. package/dist/generators/CodeiumConfigGenerator.js.map +1 -1
  357. package/dist/generators/CodexConfigGenerator.d.ts +23 -0
  358. package/dist/generators/CodexConfigGenerator.d.ts.map +1 -1
  359. package/dist/generators/CodexConfigGenerator.js +23 -0
  360. package/dist/generators/CodexConfigGenerator.js.map +1 -1
  361. package/dist/generators/ConfigGeneratorFactory.d.ts +9 -0
  362. package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
  363. package/dist/generators/ConfigGeneratorFactory.js +9 -0
  364. package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
  365. package/dist/generators/ContinueConfigGenerator.d.ts +23 -0
  366. package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
  367. package/dist/generators/ContinueConfigGenerator.js +23 -0
  368. package/dist/generators/ContinueConfigGenerator.js.map +1 -1
  369. package/dist/generators/CopilotConfigGenerator.d.ts +23 -0
  370. package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
  371. package/dist/generators/CopilotConfigGenerator.js +23 -0
  372. package/dist/generators/CopilotConfigGenerator.js.map +1 -1
  373. package/dist/generators/CursorConfigGenerator.d.ts +23 -0
  374. package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
  375. package/dist/generators/CursorConfigGenerator.js +23 -0
  376. package/dist/generators/CursorConfigGenerator.js.map +1 -1
  377. package/dist/generators/GeminiConfigGenerator.d.ts +23 -0
  378. package/dist/generators/GeminiConfigGenerator.d.ts.map +1 -1
  379. package/dist/generators/GeminiConfigGenerator.js +23 -0
  380. package/dist/generators/GeminiConfigGenerator.js.map +1 -1
  381. package/dist/generators/VSCodeConfigGenerator.d.ts +23 -0
  382. package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
  383. package/dist/generators/VSCodeConfigGenerator.js +23 -0
  384. package/dist/generators/VSCodeConfigGenerator.js.map +1 -1
  385. package/dist/generators/ZCodeConfigGenerator.d.ts +23 -0
  386. package/dist/generators/ZCodeConfigGenerator.d.ts.map +1 -1
  387. package/dist/generators/ZCodeConfigGenerator.js +23 -0
  388. package/dist/generators/ZCodeConfigGenerator.js.map +1 -1
  389. package/dist/graph/blastRadius.d.ts.map +1 -1
  390. package/dist/graph/blastRadius.js +12 -7
  391. package/dist/graph/blastRadius.js.map +1 -1
  392. package/dist/graph/callGraph.d.ts +31 -0
  393. package/dist/graph/callGraph.d.ts.map +1 -1
  394. package/dist/graph/callGraph.js +62 -32
  395. package/dist/graph/callGraph.js.map +1 -1
  396. package/dist/graph/importClassification.d.ts +28 -0
  397. package/dist/graph/importClassification.d.ts.map +1 -1
  398. package/dist/graph/importClassification.js +57 -0
  399. package/dist/graph/importClassification.js.map +1 -1
  400. package/dist/graph/importGraph.d.ts +38 -0
  401. package/dist/graph/importGraph.d.ts.map +1 -1
  402. package/dist/graph/importGraph.js +38 -0
  403. package/dist/graph/importGraph.js.map +1 -1
  404. package/dist/graph/outputFormatter.d.ts +12 -4
  405. package/dist/graph/outputFormatter.d.ts.map +1 -1
  406. package/dist/graph/outputFormatter.js +12 -62
  407. package/dist/graph/outputFormatter.js.map +1 -1
  408. package/dist/graph/resolutionEnvironment.d.ts +61 -0
  409. package/dist/graph/resolutionEnvironment.d.ts.map +1 -0
  410. package/dist/graph/resolutionEnvironment.js +57 -0
  411. package/dist/graph/resolutionEnvironment.js.map +1 -0
  412. package/dist/hooks/codex.d.ts +8 -1
  413. package/dist/hooks/codex.d.ts.map +1 -1
  414. package/dist/hooks/codex.js +7 -0
  415. package/dist/hooks/codex.js.map +1 -1
  416. package/dist/hooks/core.d.ts +6 -0
  417. package/dist/hooks/core.d.ts.map +1 -1
  418. package/dist/hooks/core.js +6 -1
  419. package/dist/hooks/core.js.map +1 -1
  420. package/dist/hooks/cursor.d.ts +11 -1
  421. package/dist/hooks/cursor.d.ts.map +1 -1
  422. package/dist/hooks/cursor.js +10 -0
  423. package/dist/hooks/cursor.js.map +1 -1
  424. package/dist/index.d.ts +4 -0
  425. package/dist/index.d.ts.map +1 -1
  426. package/dist/index.js +4 -0
  427. package/dist/index.js.map +1 -1
  428. package/dist/installConfig.d.ts +23 -5
  429. package/dist/installConfig.d.ts.map +1 -1
  430. package/dist/installConfig.js +23 -5
  431. package/dist/installConfig.js.map +1 -1
  432. package/dist/installer.d.ts +6 -0
  433. package/dist/installer.d.ts.map +1 -1
  434. package/dist/installer.js +6 -0
  435. package/dist/installer.js.map +1 -1
  436. package/dist/invariants/ruleEngine.d.ts +3 -0
  437. package/dist/invariants/ruleEngine.d.ts.map +1 -1
  438. package/dist/invariants/ruleEngine.js +156 -119
  439. package/dist/invariants/ruleEngine.js.map +1 -1
  440. package/dist/invariants/ruleValidator.d.ts +3 -0
  441. package/dist/invariants/ruleValidator.d.ts.map +1 -1
  442. package/dist/invariants/ruleValidator.js +185 -178
  443. package/dist/invariants/ruleValidator.js.map +1 -1
  444. package/dist/languages/LanguageOrchestrator.d.ts +4 -1
  445. package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
  446. package/dist/languages/LanguageOrchestrator.js +8 -19
  447. package/dist/languages/LanguageOrchestrator.js.map +1 -1
  448. package/dist/languages/RuntimeManager.d.ts +8 -0
  449. package/dist/languages/RuntimeManager.d.ts.map +1 -1
  450. package/dist/languages/RuntimeManager.js +55 -5
  451. package/dist/languages/RuntimeManager.js.map +1 -1
  452. package/dist/languages/UniversalAnalyzer.js +1 -1
  453. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  454. package/dist/languages/adapterBridge.d.ts +4 -0
  455. package/dist/languages/adapterBridge.d.ts.map +1 -1
  456. package/dist/languages/adapterBridge.js +37 -3
  457. package/dist/languages/adapterBridge.js.map +1 -1
  458. package/dist/languages/go/GoAdapter.d.ts.map +1 -1
  459. package/dist/languages/go/GoAdapter.js +11 -14
  460. package/dist/languages/go/GoAdapter.js.map +1 -1
  461. package/dist/languages/go/analyzer-darwin-amd64 +0 -0
  462. package/dist/languages/go/analyzer-darwin-arm64 +0 -0
  463. package/dist/languages/go/analyzer-linux-amd64 +0 -0
  464. package/dist/languages/go/analyzer-linux-arm64 +0 -0
  465. package/dist/languages/go/analyzer-src/analyzer.go +2 -0
  466. package/dist/languages/go/analyzer-src/dataaccess.go +388 -0
  467. package/dist/languages/go/analyzer-src/types.go +8 -0
  468. package/dist/languages/go/analyzer-windows-amd64.exe +0 -0
  469. package/dist/languages/go/database-packages.json +8 -0
  470. package/dist/languages/go/goResolution.d.ts +229 -0
  471. package/dist/languages/go/goResolution.d.ts.map +1 -0
  472. package/dist/languages/go/goResolution.js +799 -0
  473. package/dist/languages/go/goResolution.js.map +1 -0
  474. package/dist/languages/go/stdlib-packages.json +353 -0
  475. package/dist/languages/go/tenantInputs.d.ts +37 -0
  476. package/dist/languages/go/tenantInputs.d.ts.map +1 -0
  477. package/dist/languages/go/tenantInputs.js +121 -0
  478. package/dist/languages/go/tenantInputs.js.map +1 -0
  479. package/dist/languages/index.d.ts +1 -0
  480. package/dist/languages/index.d.ts.map +1 -1
  481. package/dist/languages/index.js +5 -0
  482. package/dist/languages/index.js.map +1 -1
  483. package/dist/languages/json/JsonAdapter.d.ts +177 -0
  484. package/dist/languages/json/JsonAdapter.d.ts.map +1 -0
  485. package/dist/languages/json/JsonAdapter.js +531 -0
  486. package/dist/languages/json/JsonAdapter.js.map +1 -0
  487. package/dist/languages/locationIndex.d.ts +41 -0
  488. package/dist/languages/locationIndex.d.ts.map +1 -0
  489. package/dist/languages/locationIndex.js +42 -0
  490. package/dist/languages/locationIndex.js.map +1 -0
  491. package/dist/languages/sql/dialectDetection.d.ts +57 -0
  492. package/dist/languages/sql/dialectDetection.d.ts.map +1 -0
  493. package/dist/languages/sql/dialectDetection.js +135 -0
  494. package/dist/languages/sql/dialectDetection.js.map +1 -0
  495. package/dist/languages/sql/sqlAst.d.ts +494 -0
  496. package/dist/languages/sql/sqlAst.d.ts.map +1 -0
  497. package/dist/languages/sql/sqlAst.js +1817 -0
  498. package/dist/languages/sql/sqlAst.js.map +1 -0
  499. package/dist/languages/testConventions.d.ts +12 -1
  500. package/dist/languages/testConventions.d.ts.map +1 -1
  501. package/dist/languages/testConventions.js +13 -1
  502. package/dist/languages/testConventions.js.map +1 -1
  503. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +1 -1
  504. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
  505. package/dist/languages/tree-sitter/converter.js +2 -2
  506. package/dist/languages/tree-sitter/converter.js.map +1 -1
  507. package/dist/languages/tree-sitter/parser.d.ts +3 -4
  508. package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
  509. package/dist/languages/tree-sitter/parser.js +10 -4
  510. package/dist/languages/tree-sitter/parser.js.map +1 -1
  511. package/dist/languages/tree-sitter/rawNode.d.ts +3 -0
  512. package/dist/languages/tree-sitter/rawNode.d.ts.map +1 -1
  513. package/dist/languages/tree-sitter/rawNode.js +3 -0
  514. package/dist/languages/tree-sitter/rawNode.js.map +1 -1
  515. package/dist/languages/types.d.ts +7 -0
  516. package/dist/languages/types.d.ts.map +1 -1
  517. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +36 -0
  518. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  519. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +168 -45
  520. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  521. package/dist/languages/typescript/database-packages.json +19 -0
  522. package/dist/ledger.d.ts +118 -2
  523. package/dist/ledger.d.ts.map +1 -1
  524. package/dist/ledger.js +167 -45
  525. package/dist/ledger.js.map +1 -1
  526. package/dist/legacyIndexNotice.d.ts +32 -0
  527. package/dist/legacyIndexNotice.d.ts.map +1 -0
  528. package/dist/legacyIndexNotice.js +89 -0
  529. package/dist/legacyIndexNotice.js.map +1 -0
  530. package/dist/mcp-tools/discoveryQueries.d.ts +55 -0
  531. package/dist/mcp-tools/discoveryQueries.d.ts.map +1 -0
  532. package/dist/mcp-tools/discoveryQueries.js +157 -0
  533. package/dist/mcp-tools/discoveryQueries.js.map +1 -0
  534. package/dist/mcp-tools/projectTasks.d.ts +9 -123
  535. package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
  536. package/dist/mcp-tools/projectTasks.js +180 -170
  537. package/dist/mcp-tools/projectTasks.js.map +1 -1
  538. package/dist/mcp-tools/whitelistTools.d.ts +20 -0
  539. package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
  540. package/dist/mcp-tools/whitelistTools.js +20 -0
  541. package/dist/mcp-tools/whitelistTools.js.map +1 -1
  542. package/dist/mcp-tools/workflowGuide.d.ts +9 -0
  543. package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
  544. package/dist/mcp-tools/workflowGuide.js +13 -5
  545. package/dist/mcp-tools/workflowGuide.js.map +1 -1
  546. package/dist/mcp-tools-shared.d.ts +78 -0
  547. package/dist/mcp-tools-shared.d.ts.map +1 -1
  548. package/dist/mcp-tools-shared.js +203 -340
  549. package/dist/mcp-tools-shared.js.map +1 -1
  550. package/dist/mcp-ui-simple.d.ts +2 -0
  551. package/dist/mcp-ui-simple.d.ts.map +1 -1
  552. package/dist/mcp-ui-simple.js +12 -5
  553. package/dist/mcp-ui-simple.js.map +1 -1
  554. package/dist/mcp.d.ts +6 -0
  555. package/dist/mcp.d.ts.map +1 -1
  556. package/dist/mcp.js +827 -850
  557. package/dist/mcp.js.map +1 -1
  558. package/dist/mcpAuditJobs.d.ts +29 -33
  559. package/dist/mcpAuditJobs.d.ts.map +1 -1
  560. package/dist/mcpAuditJobs.js +102 -791
  561. package/dist/mcpAuditJobs.js.map +1 -1
  562. package/dist/mcpAutoIndex.d.ts +5 -0
  563. package/dist/mcpAutoIndex.d.ts.map +1 -1
  564. package/dist/mcpAutoIndex.js +7 -2
  565. package/dist/mcpAutoIndex.js.map +1 -1
  566. package/dist/mcpDiagnostics.d.ts +7 -0
  567. package/dist/mcpDiagnostics.d.ts.map +1 -1
  568. package/dist/mcpDiagnostics.js +34 -9
  569. package/dist/mcpDiagnostics.js.map +1 -1
  570. package/dist/mcpToolErrors.d.ts +16 -0
  571. package/dist/mcpToolErrors.d.ts.map +1 -1
  572. package/dist/mcpToolErrors.js +16 -0
  573. package/dist/mcpToolErrors.js.map +1 -1
  574. package/dist/nextFile.d.ts +6 -0
  575. package/dist/nextFile.d.ts.map +1 -1
  576. package/dist/nextFile.js +6 -0
  577. package/dist/nextFile.js.map +1 -1
  578. package/dist/nextFileIncremental.d.ts +44 -4
  579. package/dist/nextFileIncremental.d.ts.map +1 -1
  580. package/dist/nextFileIncremental.js +47 -7
  581. package/dist/nextFileIncremental.js.map +1 -1
  582. package/dist/phase/batchFunctions.d.ts +38 -0
  583. package/dist/phase/batchFunctions.d.ts.map +1 -0
  584. package/dist/phase/batchFunctions.js +64 -0
  585. package/dist/phase/batchFunctions.js.map +1 -0
  586. package/dist/phase/checks.d.ts +39 -0
  587. package/dist/phase/checks.d.ts.map +1 -0
  588. package/dist/phase/checks.js +48 -0
  589. package/dist/phase/checks.js.map +1 -0
  590. package/dist/phase/codeBlocks.d.ts +25 -0
  591. package/dist/phase/codeBlocks.d.ts.map +1 -0
  592. package/dist/phase/codeBlocks.js +268 -0
  593. package/dist/phase/codeBlocks.js.map +1 -0
  594. package/dist/phase/colorMath.d.ts +41 -0
  595. package/dist/phase/colorMath.d.ts.map +1 -0
  596. package/dist/phase/colorMath.js +109 -0
  597. package/dist/phase/colorMath.js.map +1 -0
  598. package/dist/phase/config.d.ts +29 -0
  599. package/dist/phase/config.d.ts.map +1 -0
  600. package/dist/phase/config.js +54 -0
  601. package/dist/phase/config.js.map +1 -0
  602. package/dist/phase/consumed.d.ts +47 -0
  603. package/dist/phase/consumed.d.ts.map +1 -0
  604. package/dist/phase/consumed.js +22 -0
  605. package/dist/phase/consumed.js.map +1 -0
  606. package/dist/phase/conventionMining.d.ts +59 -0
  607. package/dist/phase/conventionMining.d.ts.map +1 -0
  608. package/dist/phase/conventionMining.js +152 -0
  609. package/dist/phase/conventionMining.js.map +1 -0
  610. package/dist/phase/coverage.d.ts +65 -0
  611. package/dist/phase/coverage.d.ts.map +1 -0
  612. package/dist/phase/coverage.js +121 -0
  613. package/dist/phase/coverage.js.map +1 -0
  614. package/dist/phase/dag.d.ts +49 -0
  615. package/dist/phase/dag.d.ts.map +1 -0
  616. package/dist/phase/dag.js +120 -0
  617. package/dist/phase/dag.js.map +1 -0
  618. package/dist/phase/dataAccessCalls.d.ts +33 -0
  619. package/dist/phase/dataAccessCalls.d.ts.map +1 -0
  620. package/dist/phase/dataAccessCalls.js +42 -0
  621. package/dist/phase/dataAccessCalls.js.map +1 -0
  622. package/dist/phase/dataAccessCallsCandidates.d.ts +25 -0
  623. package/dist/phase/dataAccessCallsCandidates.d.ts.map +1 -0
  624. package/dist/phase/dataAccessCallsCandidates.js +27 -0
  625. package/dist/phase/dataAccessCallsCandidates.js.map +1 -0
  626. package/dist/phase/dynamicSql.d.ts +18 -0
  627. package/dist/phase/dynamicSql.d.ts.map +1 -0
  628. package/dist/phase/dynamicSql.js +20 -0
  629. package/dist/phase/dynamicSql.js.map +1 -0
  630. package/dist/phase/exportForm.d.ts +23 -0
  631. package/dist/phase/exportForm.d.ts.map +1 -0
  632. package/dist/phase/exportForm.js +29 -0
  633. package/dist/phase/exportForm.js.map +1 -0
  634. package/dist/phase/exportSymbols.d.ts +21 -0
  635. package/dist/phase/exportSymbols.d.ts.map +1 -0
  636. package/dist/phase/exportSymbols.js +28 -0
  637. package/dist/phase/exportSymbols.js.map +1 -0
  638. package/dist/phase/fileHeader.d.ts +15 -0
  639. package/dist/phase/fileHeader.d.ts.map +1 -0
  640. package/dist/phase/fileHeader.js +33 -0
  641. package/dist/phase/fileHeader.js.map +1 -0
  642. package/dist/phase/fileImports.d.ts +39 -0
  643. package/dist/phase/fileImports.d.ts.map +1 -0
  644. package/dist/phase/fileImports.js +138 -0
  645. package/dist/phase/fileImports.js.map +1 -0
  646. package/dist/phase/fileSymbols.d.ts +25 -0
  647. package/dist/phase/fileSymbols.d.ts.map +1 -0
  648. package/dist/phase/fileSymbols.js +302 -0
  649. package/dist/phase/fileSymbols.js.map +1 -0
  650. package/dist/phase/functionBodies.d.ts +28 -0
  651. package/dist/phase/functionBodies.d.ts.map +1 -0
  652. package/dist/phase/functionBodies.js +67 -0
  653. package/dist/phase/functionBodies.js.map +1 -0
  654. package/dist/phase/functionIndex.d.ts +28 -0
  655. package/dist/phase/functionIndex.d.ts.map +1 -0
  656. package/dist/phase/functionIndex.js +152 -0
  657. package/dist/phase/functionIndex.js.map +1 -0
  658. package/dist/phase/goFunctionAnalysis.d.ts +47 -0
  659. package/dist/phase/goFunctionAnalysis.d.ts.map +1 -0
  660. package/dist/phase/goFunctionAnalysis.js +291 -0
  661. package/dist/phase/goFunctionAnalysis.js.map +1 -0
  662. package/dist/phase/goFunctions.d.ts +38 -0
  663. package/dist/phase/goFunctions.d.ts.map +1 -0
  664. package/dist/phase/goFunctions.js +154 -0
  665. package/dist/phase/goFunctions.js.map +1 -0
  666. package/dist/phase/goImports.d.ts +29 -0
  667. package/dist/phase/goImports.d.ts.map +1 -0
  668. package/dist/phase/goImports.js +68 -0
  669. package/dist/phase/goImports.js.map +1 -0
  670. package/dist/phase/goPackageBindings.d.ts +22 -0
  671. package/dist/phase/goPackageBindings.d.ts.map +1 -0
  672. package/dist/phase/goPackageBindings.js +30 -0
  673. package/dist/phase/goPackageBindings.js.map +1 -0
  674. package/dist/phase/goSwitches.d.ts +28 -0
  675. package/dist/phase/goSwitches.d.ts.map +1 -0
  676. package/dist/phase/goSwitches.js +52 -0
  677. package/dist/phase/goSwitches.js.map +1 -0
  678. package/dist/phase/importForm.d.ts +20 -0
  679. package/dist/phase/importForm.d.ts.map +1 -0
  680. package/dist/phase/importForm.js +27 -0
  681. package/dist/phase/importForm.js.map +1 -0
  682. package/dist/phase/importSpecifiers.d.ts +20 -0
  683. package/dist/phase/importSpecifiers.d.ts.map +1 -0
  684. package/dist/phase/importSpecifiers.js +30 -0
  685. package/dist/phase/importSpecifiers.js.map +1 -0
  686. package/dist/phase/imports.d.ts +18 -0
  687. package/dist/phase/imports.d.ts.map +1 -0
  688. package/dist/phase/imports.js +25 -0
  689. package/dist/phase/imports.js.map +1 -0
  690. package/dist/phase/jsonDocument.d.ts +26 -0
  691. package/dist/phase/jsonDocument.d.ts.map +1 -0
  692. package/dist/phase/jsonDocument.js +36 -0
  693. package/dist/phase/jsonDocument.js.map +1 -0
  694. package/dist/phase/localBinding.d.ts +33 -0
  695. package/dist/phase/localBinding.d.ts.map +1 -0
  696. package/dist/phase/localBinding.js +208 -0
  697. package/dist/phase/localBinding.js.map +1 -0
  698. package/dist/phase/loopQueries.d.ts +31 -0
  699. package/dist/phase/loopQueries.d.ts.map +1 -0
  700. package/dist/phase/loopQueries.js +44 -0
  701. package/dist/phase/loopQueries.js.map +1 -0
  702. package/dist/phase/loopQueryCandidates.d.ts +26 -0
  703. package/dist/phase/loopQueryCandidates.d.ts.map +1 -0
  704. package/dist/phase/loopQueryCandidates.js +28 -0
  705. package/dist/phase/loopQueryCandidates.js.map +1 -0
  706. package/dist/phase/oracles.d.ts +216 -0
  707. package/dist/phase/oracles.d.ts.map +1 -0
  708. package/dist/phase/oracles.js +484 -0
  709. package/dist/phase/oracles.js.map +1 -0
  710. package/dist/phase/phaseModel.d.ts +190 -0
  711. package/dist/phase/phaseModel.d.ts.map +1 -0
  712. package/dist/phase/phaseModel.js +491 -0
  713. package/dist/phase/phaseModel.js.map +1 -0
  714. package/dist/phase/producers.d.ts +450 -0
  715. package/dist/phase/producers.d.ts.map +1 -0
  716. package/dist/phase/producers.js +1030 -0
  717. package/dist/phase/producers.js.map +1 -0
  718. package/dist/phase/querySiteCandidates.d.ts +27 -0
  719. package/dist/phase/querySiteCandidates.d.ts.map +1 -0
  720. package/dist/phase/querySiteCandidates.js +66 -0
  721. package/dist/phase/querySiteCandidates.js.map +1 -0
  722. package/dist/phase/querySites.d.ts +30 -0
  723. package/dist/phase/querySites.d.ts.map +1 -0
  724. package/dist/phase/querySites.js +93 -0
  725. package/dist/phase/querySites.js.map +1 -0
  726. package/dist/phase/reachability.d.ts +61 -0
  727. package/dist/phase/reachability.d.ts.map +1 -0
  728. package/dist/phase/reachability.js +104 -0
  729. package/dist/phase/reachability.js.map +1 -0
  730. package/dist/phase/reactComponents.d.ts +33 -0
  731. package/dist/phase/reactComponents.d.ts.map +1 -0
  732. package/dist/phase/reactComponents.js +47 -0
  733. package/dist/phase/reactComponents.js.map +1 -0
  734. package/dist/phase/receiverActivity.d.ts +35 -0
  735. package/dist/phase/receiverActivity.d.ts.map +1 -0
  736. package/dist/phase/receiverActivity.js +66 -0
  737. package/dist/phase/receiverActivity.js.map +1 -0
  738. package/dist/phase/receiverConsumers.d.ts +207 -0
  739. package/dist/phase/receiverConsumers.d.ts.map +1 -0
  740. package/dist/phase/receiverConsumers.js +1063 -0
  741. package/dist/phase/receiverConsumers.js.map +1 -0
  742. package/dist/phase/receiverProvenance.d.ts +120 -0
  743. package/dist/phase/receiverProvenance.d.ts.map +1 -0
  744. package/dist/phase/receiverProvenance.js +377 -0
  745. package/dist/phase/receiverProvenance.js.map +1 -0
  746. package/dist/phase/routing.d.ts +47 -0
  747. package/dist/phase/routing.d.ts.map +1 -0
  748. package/dist/phase/routing.js +62 -0
  749. package/dist/phase/routing.js.map +1 -0
  750. package/dist/phase/rules/conventions.d.ts +61 -0
  751. package/dist/phase/rules/conventions.d.ts.map +1 -0
  752. package/dist/phase/rules/conventions.js +403 -0
  753. package/dist/phase/rules/conventions.js.map +1 -0
  754. package/dist/phase/rules/crossDomain.d.ts +102 -0
  755. package/dist/phase/rules/crossDomain.d.ts.map +1 -0
  756. package/dist/phase/rules/crossDomain.js +918 -0
  757. package/dist/phase/rules/crossDomain.js.map +1 -0
  758. package/dist/phase/rules/dataAccess.d.ts +62 -0
  759. package/dist/phase/rules/dataAccess.d.ts.map +1 -0
  760. package/dist/phase/rules/dataAccess.js +480 -0
  761. package/dist/phase/rules/dataAccess.js.map +1 -0
  762. package/dist/phase/rules/dependencyGraph.d.ts +44 -0
  763. package/dist/phase/rules/dependencyGraph.d.ts.map +1 -0
  764. package/dist/phase/rules/dependencyGraph.js +260 -0
  765. package/dist/phase/rules/dependencyGraph.js.map +1 -0
  766. package/dist/phase/rules/documentation.d.ts +45 -0
  767. package/dist/phase/rules/documentation.d.ts.map +1 -0
  768. package/dist/phase/rules/documentation.js +340 -0
  769. package/dist/phase/rules/documentation.js.map +1 -0
  770. package/dist/phase/rules/dry.d.ts +109 -0
  771. package/dist/phase/rules/dry.d.ts.map +1 -0
  772. package/dist/phase/rules/dry.js +803 -0
  773. package/dist/phase/rules/dry.js.map +1 -0
  774. package/dist/phase/rules/fileDocumentation.d.ts +36 -0
  775. package/dist/phase/rules/fileDocumentation.d.ts.map +1 -0
  776. package/dist/phase/rules/fileDocumentation.js +134 -0
  777. package/dist/phase/rules/fileDocumentation.js.map +1 -0
  778. package/dist/phase/rules/functionBodies.d.ts +33 -0
  779. package/dist/phase/rules/functionBodies.d.ts.map +1 -0
  780. package/dist/phase/rules/functionBodies.js +71 -0
  781. package/dist/phase/rules/functionBodies.js.map +1 -0
  782. package/dist/phase/rules/goRules.d.ts +49 -0
  783. package/dist/phase/rules/goRules.d.ts.map +1 -0
  784. package/dist/phase/rules/goRules.js +209 -0
  785. package/dist/phase/rules/goRules.js.map +1 -0
  786. package/dist/phase/rules/querySites.d.ts +29 -0
  787. package/dist/phase/rules/querySites.d.ts.map +1 -0
  788. package/dist/phase/rules/querySites.js +94 -0
  789. package/dist/phase/rules/querySites.js.map +1 -0
  790. package/dist/phase/rules/react.d.ts +38 -0
  791. package/dist/phase/rules/react.d.ts.map +1 -0
  792. package/dist/phase/rules/react.js +202 -0
  793. package/dist/phase/rules/react.js.map +1 -0
  794. package/dist/phase/rules/registry.d.ts +170 -0
  795. package/dist/phase/rules/registry.d.ts.map +1 -0
  796. package/dist/phase/rules/registry.js +103 -0
  797. package/dist/phase/rules/registry.js.map +1 -0
  798. package/dist/phase/rules/schema.d.ts +79 -0
  799. package/dist/phase/rules/schema.d.ts.map +1 -0
  800. package/dist/phase/rules/schema.js +273 -0
  801. package/dist/phase/rules/schema.js.map +1 -0
  802. package/dist/phase/rules/schemaJson.d.ts +32 -0
  803. package/dist/phase/rules/schemaJson.d.ts.map +1 -0
  804. package/dist/phase/rules/schemaJson.js +81 -0
  805. package/dist/phase/rules/schemaJson.js.map +1 -0
  806. package/dist/phase/rules/schemaValidator.d.ts +38 -0
  807. package/dist/phase/rules/schemaValidator.d.ts.map +1 -0
  808. package/dist/phase/rules/schemaValidator.js +107 -0
  809. package/dist/phase/rules/schemaValidator.js.map +1 -0
  810. package/dist/phase/rules/secrets.d.ts +34 -0
  811. package/dist/phase/rules/secrets.d.ts.map +1 -0
  812. package/dist/phase/rules/secrets.js +208 -0
  813. package/dist/phase/rules/secrets.js.map +1 -0
  814. package/dist/phase/rules/security.d.ts +30 -0
  815. package/dist/phase/rules/security.d.ts.map +1 -0
  816. package/dist/phase/rules/security.js +85 -0
  817. package/dist/phase/rules/security.js.map +1 -0
  818. package/dist/phase/rules/securityDefects.d.ts +37 -0
  819. package/dist/phase/rules/securityDefects.d.ts.map +1 -0
  820. package/dist/phase/rules/securityDefects.js +167 -0
  821. package/dist/phase/rules/securityDefects.js.map +1 -0
  822. package/dist/phase/rules/solid.d.ts +61 -0
  823. package/dist/phase/rules/solid.d.ts.map +1 -0
  824. package/dist/phase/rules/solid.js +562 -0
  825. package/dist/phase/rules/solid.js.map +1 -0
  826. package/dist/phase/rules/styles.d.ts +77 -0
  827. package/dist/phase/rules/styles.d.ts.map +1 -0
  828. package/dist/phase/rules/styles.js +868 -0
  829. package/dist/phase/rules/styles.js.map +1 -0
  830. package/dist/phase/rules/unreferencedModule.d.ts +26 -0
  831. package/dist/phase/rules/unreferencedModule.d.ts.map +1 -0
  832. package/dist/phase/rules/unreferencedModule.js +61 -0
  833. package/dist/phase/rules/unreferencedModule.js.map +1 -0
  834. package/dist/phase/runner.d.ts +549 -0
  835. package/dist/phase/runner.d.ts.map +1 -0
  836. package/dist/phase/runner.js +1098 -0
  837. package/dist/phase/runner.js.map +1 -0
  838. package/dist/phase/schemaCode.d.ts +34 -0
  839. package/dist/phase/schemaCode.d.ts.map +1 -0
  840. package/dist/phase/schemaCode.js +53 -0
  841. package/dist/phase/schemaCode.js.map +1 -0
  842. package/dist/phase/schemaObjects.d.ts +37 -0
  843. package/dist/phase/schemaObjects.d.ts.map +1 -0
  844. package/dist/phase/schemaObjects.js +139 -0
  845. package/dist/phase/schemaObjects.js.map +1 -0
  846. package/dist/phase/schemaUsage.d.ts +30 -0
  847. package/dist/phase/schemaUsage.d.ts.map +1 -0
  848. package/dist/phase/schemaUsage.js +99 -0
  849. package/dist/phase/schemaUsage.js.map +1 -0
  850. package/dist/phase/schemaUsageCandidates.d.ts +32 -0
  851. package/dist/phase/schemaUsageCandidates.d.ts.map +1 -0
  852. package/dist/phase/schemaUsageCandidates.js +165 -0
  853. package/dist/phase/schemaUsageCandidates.js.map +1 -0
  854. package/dist/phase/schemaValidations.d.ts +34 -0
  855. package/dist/phase/schemaValidations.d.ts.map +1 -0
  856. package/dist/phase/schemaValidations.js +53 -0
  857. package/dist/phase/schemaValidations.js.map +1 -0
  858. package/dist/phase/secretCandidates.d.ts +28 -0
  859. package/dist/phase/secretCandidates.d.ts.map +1 -0
  860. package/dist/phase/secretCandidates.js +132 -0
  861. package/dist/phase/secretCandidates.js.map +1 -0
  862. package/dist/phase/securityCandidates.d.ts +29 -0
  863. package/dist/phase/securityCandidates.d.ts.map +1 -0
  864. package/dist/phase/securityCandidates.js +469 -0
  865. package/dist/phase/securityCandidates.js.map +1 -0
  866. package/dist/phase/seeded-defects.d.ts +18 -0
  867. package/dist/phase/seeded-defects.d.ts.map +1 -0
  868. package/dist/phase/seeded-defects.js +61 -0
  869. package/dist/phase/seeded-defects.js.map +1 -0
  870. package/dist/phase/stringLiterals.d.ts +24 -0
  871. package/dist/phase/stringLiterals.d.ts.map +1 -0
  872. package/dist/phase/stringLiterals.js +35 -0
  873. package/dist/phase/stringLiterals.js.map +1 -0
  874. package/dist/phase/stylesCss.d.ts +25 -0
  875. package/dist/phase/stylesCss.d.ts.map +1 -0
  876. package/dist/phase/stylesCss.js +34 -0
  877. package/dist/phase/stylesCss.js.map +1 -0
  878. package/dist/phase/stylesMarkup.d.ts +31 -0
  879. package/dist/phase/stylesMarkup.d.ts.map +1 -0
  880. package/dist/phase/stylesMarkup.js +48 -0
  881. package/dist/phase/stylesMarkup.js.map +1 -0
  882. package/dist/phase/stylesSource.d.ts +36 -0
  883. package/dist/phase/stylesSource.d.ts.map +1 -0
  884. package/dist/phase/stylesSource.js +50 -0
  885. package/dist/phase/stylesSource.js.map +1 -0
  886. package/dist/phase/typeDeclarations.d.ts +33 -0
  887. package/dist/phase/typeDeclarations.d.ts.map +1 -0
  888. package/dist/phase/typeDeclarations.js +76 -0
  889. package/dist/phase/typeDeclarations.js.map +1 -0
  890. package/dist/phase/types.d.ts +2199 -0
  891. package/dist/phase/types.d.ts.map +1 -0
  892. package/dist/phase/types.js +27 -0
  893. package/dist/phase/types.js.map +1 -0
  894. package/dist/phase/withinFileProvenance.d.ts +50 -0
  895. package/dist/phase/withinFileProvenance.d.ts.map +1 -0
  896. package/dist/phase/withinFileProvenance.js +112 -0
  897. package/dist/phase/withinFileProvenance.js.map +1 -0
  898. package/dist/pipeline.d.ts +37 -30
  899. package/dist/pipeline.d.ts.map +1 -1
  900. package/dist/pipeline.js +131 -326
  901. package/dist/pipeline.js.map +1 -1
  902. package/dist/pipelineAdapters.d.ts +104 -21
  903. package/dist/pipelineAdapters.d.ts.map +1 -1
  904. package/dist/pipelineAdapters.js +1144 -984
  905. package/dist/pipelineAdapters.js.map +1 -1
  906. package/dist/pipelineTypes.d.ts +21 -0
  907. package/dist/pipelineTypes.d.ts.map +1 -1
  908. package/dist/pipelineTypes.js +21 -10
  909. package/dist/pipelineTypes.js.map +1 -1
  910. package/dist/presets/presets.d.ts +9 -2
  911. package/dist/presets/presets.d.ts.map +1 -1
  912. package/dist/presets/presets.js +15 -26
  913. package/dist/presets/presets.js.map +1 -1
  914. package/dist/reporting/csvReportGenerator.d.ts +14 -3
  915. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  916. package/dist/reporting/csvReportGenerator.js +14 -3
  917. package/dist/reporting/csvReportGenerator.js.map +1 -1
  918. package/dist/reporting/htmlReportGenerator.d.ts +5 -1
  919. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  920. package/dist/reporting/htmlReportGenerator.js +5 -1
  921. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  922. package/dist/reporting/jsonReportGenerator.d.ts +9 -2
  923. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  924. package/dist/reporting/jsonReportGenerator.js +19 -5
  925. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  926. package/dist/reporting/reportGenerator.d.ts +9 -2
  927. package/dist/reporting/reportGenerator.d.ts.map +1 -1
  928. package/dist/reporting/reportGenerator.js +9 -2
  929. package/dist/reporting/reportGenerator.js.map +1 -1
  930. package/dist/reporting/sarifReportGenerator.d.ts +14 -0
  931. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  932. package/dist/reporting/sarifReportGenerator.js +14 -0
  933. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  934. package/dist/reporting/sizeDistribution.d.ts +19 -2
  935. package/dist/reporting/sizeDistribution.d.ts.map +1 -1
  936. package/dist/reporting/sizeDistribution.js +23 -5
  937. package/dist/reporting/sizeDistribution.js.map +1 -1
  938. package/dist/ruleAliases.d.ts +6 -8
  939. package/dist/ruleAliases.d.ts.map +1 -1
  940. package/dist/ruleAliases.js +29 -4
  941. package/dist/ruleAliases.js.map +1 -1
  942. package/dist/search/QueryParser.d.ts +1 -0
  943. package/dist/search/QueryParser.d.ts.map +1 -1
  944. package/dist/search/QueryParser.js +32 -91
  945. package/dist/search/QueryParser.js.map +1 -1
  946. package/dist/services/CodeMapGenerator.d.ts +19 -20
  947. package/dist/services/CodeMapGenerator.d.ts.map +1 -1
  948. package/dist/services/CodeMapGenerator.js +214 -194
  949. package/dist/services/CodeMapGenerator.js.map +1 -1
  950. package/dist/services/ProjectTaskRepository.d.ts +65 -3
  951. package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
  952. package/dist/services/ProjectTaskRepository.js +94 -25
  953. package/dist/services/ProjectTaskRepository.js.map +1 -1
  954. package/dist/services/auditJobService.d.ts +17 -1
  955. package/dist/services/auditJobService.d.ts.map +1 -1
  956. package/dist/services/auditJobService.js +16 -0
  957. package/dist/services/auditJobService.js.map +1 -1
  958. package/dist/services/fileAccounting.d.ts +43 -5
  959. package/dist/services/fileAccounting.d.ts.map +1 -1
  960. package/dist/services/fileAccounting.js +48 -9
  961. package/dist/services/fileAccounting.js.map +1 -1
  962. package/dist/services/projectTaskHelpers.d.ts +45 -0
  963. package/dist/services/projectTaskHelpers.d.ts.map +1 -1
  964. package/dist/services/projectTaskHelpers.js +45 -0
  965. package/dist/services/projectTaskHelpers.js.map +1 -1
  966. package/dist/services/whitelistService.d.ts +40 -5
  967. package/dist/services/whitelistService.d.ts.map +1 -1
  968. package/dist/services/whitelistService.js +87 -46
  969. package/dist/services/whitelistService.js.map +1 -1
  970. package/dist/sqlite/betterSqlite3.d.ts +35 -1
  971. package/dist/sqlite/betterSqlite3.d.ts.map +1 -1
  972. package/dist/sqlite/betterSqlite3.js +35 -1
  973. package/dist/sqlite/betterSqlite3.js.map +1 -1
  974. package/dist/sqlite/driver.d.ts +23 -0
  975. package/dist/sqlite/driver.d.ts.map +1 -1
  976. package/dist/sqlite/driver.js +23 -0
  977. package/dist/sqlite/driver.js.map +1 -1
  978. package/dist/sqlite/nodeSqlite.d.ts +35 -1
  979. package/dist/sqlite/nodeSqlite.d.ts.map +1 -1
  980. package/dist/sqlite/nodeSqlite.js +35 -1
  981. package/dist/sqlite/nodeSqlite.js.map +1 -1
  982. package/dist/structuralSignature.d.ts +9 -0
  983. package/dist/structuralSignature.d.ts.map +1 -1
  984. package/dist/structuralSignature.js +9 -0
  985. package/dist/structuralSignature.js.map +1 -1
  986. package/dist/styles/cssAstExtractor.d.ts +15 -0
  987. package/dist/styles/cssAstExtractor.d.ts.map +1 -1
  988. package/dist/styles/cssAstExtractor.js +15 -0
  989. package/dist/styles/cssAstExtractor.js.map +1 -1
  990. package/dist/styles/normalizer.d.ts +6 -0
  991. package/dist/styles/normalizer.d.ts.map +1 -1
  992. package/dist/styles/normalizer.js +6 -0
  993. package/dist/styles/normalizer.js.map +1 -1
  994. package/dist/styles/styleExtractor.d.ts +29 -2
  995. package/dist/styles/styleExtractor.d.ts.map +1 -1
  996. package/dist/styles/styleExtractor.js +67 -48
  997. package/dist/styles/styleExtractor.js.map +1 -1
  998. package/dist/styles/styleIndexer.d.ts +8 -0
  999. package/dist/styles/styleIndexer.d.ts.map +1 -1
  1000. package/dist/styles/styleIndexer.js +135 -138
  1001. package/dist/styles/styleIndexer.js.map +1 -1
  1002. package/dist/styles/tailwindConfigLoader.d.ts +12 -0
  1003. package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
  1004. package/dist/styles/tailwindConfigLoader.js +12 -0
  1005. package/dist/styles/tailwindConfigLoader.js.map +1 -1
  1006. package/dist/styles/tailwindExpander.d.ts +3 -0
  1007. package/dist/styles/tailwindExpander.d.ts.map +1 -1
  1008. package/dist/styles/tailwindExpander.js +8 -4
  1009. package/dist/styles/tailwindExpander.js.map +1 -1
  1010. package/dist/styles/tailwindProbe.d.ts +9 -0
  1011. package/dist/styles/tailwindProbe.d.ts.map +1 -1
  1012. package/dist/styles/tailwindProbe.js +181 -166
  1013. package/dist/styles/tailwindProbe.js.map +1 -1
  1014. package/dist/styles/tailwindUtilityExpander.d.ts +13 -0
  1015. package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
  1016. package/dist/styles/tailwindUtilityExpander.js +13 -0
  1017. package/dist/styles/tailwindUtilityExpander.js.map +1 -1
  1018. package/dist/symbols.d.ts +13 -11
  1019. package/dist/symbols.d.ts.map +1 -1
  1020. package/dist/symbols.js +14 -21
  1021. package/dist/symbols.js.map +1 -1
  1022. package/dist/telemetry.d.ts +31 -3
  1023. package/dist/telemetry.d.ts.map +1 -1
  1024. package/dist/telemetry.js +31 -3
  1025. package/dist/telemetry.js.map +1 -1
  1026. package/dist/tool-registry.d.ts +37 -1
  1027. package/dist/tool-registry.d.ts.map +1 -1
  1028. package/dist/tool-registry.js +37 -1
  1029. package/dist/tool-registry.js.map +1 -1
  1030. package/dist/types.d.ts +114 -71
  1031. package/dist/types.d.ts.map +1 -1
  1032. package/dist/types.js +7 -16
  1033. package/dist/types.js.map +1 -1
  1034. package/dist/ui/InteractivePrompts.d.ts +12 -0
  1035. package/dist/ui/InteractivePrompts.d.ts.map +1 -1
  1036. package/dist/ui/InteractivePrompts.js +12 -0
  1037. package/dist/ui/InteractivePrompts.js.map +1 -1
  1038. package/dist/utils/astParser.d.ts +15 -0
  1039. package/dist/utils/astParser.d.ts.map +1 -1
  1040. package/dist/utils/astParser.js +15 -0
  1041. package/dist/utils/astParser.js.map +1 -1
  1042. package/dist/utils/astUtils.d.ts +60 -0
  1043. package/dist/utils/astUtils.d.ts.map +1 -1
  1044. package/dist/utils/astUtils.js +350 -362
  1045. package/dist/utils/astUtils.js.map +1 -1
  1046. package/dist/utils/contentHash.d.ts +22 -0
  1047. package/dist/utils/contentHash.d.ts.map +1 -0
  1048. package/dist/utils/contentHash.js +26 -0
  1049. package/dist/utils/contentHash.js.map +1 -0
  1050. package/dist/utils/dependencyExtractor.d.ts +28 -0
  1051. package/dist/utils/dependencyExtractor.d.ts.map +1 -1
  1052. package/dist/utils/dependencyExtractor.js +28 -1
  1053. package/dist/utils/dependencyExtractor.js.map +1 -1
  1054. package/dist/utils/errorMessage.d.ts +10 -0
  1055. package/dist/utils/errorMessage.d.ts.map +1 -0
  1056. package/dist/utils/errorMessage.js +12 -0
  1057. package/dist/utils/errorMessage.js.map +1 -0
  1058. package/dist/utils/fileDiscovery.d.ts +61 -2
  1059. package/dist/utils/fileDiscovery.d.ts.map +1 -1
  1060. package/dist/utils/fileDiscovery.js +131 -23
  1061. package/dist/utils/fileDiscovery.js.map +1 -1
  1062. package/dist/utils/reactDetection.d.ts +34 -5
  1063. package/dist/utils/reactDetection.d.ts.map +1 -1
  1064. package/dist/utils/reactDetection.js +73 -52
  1065. package/dist/utils/reactDetection.js.map +1 -1
  1066. package/dist/version.generated.d.ts +1 -1
  1067. package/dist/version.generated.js +1 -1
  1068. package/dist/workers/auditWorker.js +7 -3
  1069. package/dist/workers/auditWorker.js.map +1 -1
  1070. package/examples/.auditrc.example.json +0 -8
  1071. package/examples/.auditrc.json +0 -11
  1072. package/examples/.code-auditor.json +0 -2
  1073. package/examples/nextjs.auditrc.json +0 -8
  1074. package/examples/node-api.auditrc.json +0 -7
  1075. package/examples/react.auditrc.json +0 -6
  1076. package/examples/tolerant-solid.auditrc.json +0 -5
  1077. package/package.json +18 -9
  1078. package/plugin/.claude-plugin/manifest.json +16 -0
  1079. package/plugin/.claude-plugin/plugin.json +1 -1
  1080. package/plugin/hooks/hooks.json +7 -3
  1081. package/plugin/scripts/hook-audit.sh +46 -16
  1082. package/plugin/scripts/hook-common.sh +81 -12
  1083. package/plugin/scripts/hook-drift-check.sh +76 -0
  1084. package/plugin/scripts/hook-self-audit.sh +22 -14
  1085. package/plugin/scripts/hook-warm.sh +5 -6
  1086. package/plugin/skills/code-auditor/SKILL.md +1 -1
  1087. package/server.json +10 -4
@@ -1,247 +1,65 @@
1
1
  /**
2
2
  * Code Index Database using SQLite (better-sqlite3) + FTS5
3
3
  * Replaces LokiJS + FlexSearch with durable, transactional storage.
4
+ *
5
+ * `CodeIndexDB` is the connection owner and transaction boundary, plus the
6
+ * cross-concern orchestrators (sync, diff detection, churn/hotspot/convention
7
+ * mining). Each storage concern — functions, search, coverage, dependency
8
+ * graph, schema usage, whitelist, audit results, analyzer configs, code maps,
9
+ * project tasks, meta, provenance, inferred receivers, conventions, raw SQL —
10
+ * lives in its own module under `codeIndex/` and is exposed as a public field.
4
11
  */
5
- import { createHash } from 'crypto';
6
- import { openSqlite } from './sqlite/driver.js';
12
+ import { openSqlite, DB_BUSY_TIMEOUT_MS } from './sqlite/driver.js';
7
13
  import { promises as fs } from 'fs';
8
14
  import path from 'path';
9
15
  import { discoverFiles, ALL_EXTENSIONS } from './utils/fileDiscovery.js';
10
- import { ProjectTaskRepository } from './services/ProjectTaskRepository.js';
11
- import { mineConventions, computeMinerInputHash } from './conventions/conventionMiner.js';
16
+ import { computeContentHash } from './utils/contentHash.js';
17
+ import { errorMessage } from './utils/errorMessage.js';
18
+ import { getPersistedStorageRoot, resolvePersistedIndexPath } from './dataPaths.js';
19
+ import { ContextualError, getErrnoCode } from './mcpToolErrors.js';
12
20
  import { extractChurn } from './churn/churnExtractor.js';
13
21
  import { computeHotspots } from './hotspots/hotspotScorer.js';
14
- import { populateCallGraphCache, getGraphStats as getGs } from './graph/callGraph.js';
22
+ import { populateCallGraphCache } from './graph/callGraph.js';
15
23
  import { populateImportGraphCache } from './graph/importGraph.js';
16
- import { QueryParser, compileToSQL } from './search/QueryParser.js';
17
- import { getPersistedStorageRoot, resolvePersistedIndexPath } from './dataPaths.js';
18
- import { ContextualError, getErrnoCode } from './mcpToolErrors.js';
19
- import { WhitelistType, WhitelistStatus } from './types/whitelist.js';
20
- // ── SQLite busy timeout ─────────────────────────────────────────────────
21
- // The database is shared across forked processes (a detached audit runner and
22
- // its shard workers). better-sqlite3's default busy timeout (~5s) is too short
23
- // for a running job's long completion transaction, so a queued job's lease
24
- // `BEGIN IMMEDIATE` (or the runner's own writes) would throw SQLITE_BUSY
25
- // ("database is locked") instead of waiting. A generous timeout makes
26
- // contention block gracefully; the lease loop also retries SQLITE_BUSY.
27
- export const DB_BUSY_TIMEOUT_MS = 30_000;
28
- // ── Content hash ────────────────────────────────────────────────────────
29
- function computeContentHash(body, signature) {
30
- const normalized = (body ?? '').replace(/\s+/g, ' ').trim() + '|' + (signature ?? '').trim();
31
- return createHash('sha256').update(normalized).digest('hex');
32
- }
24
+ import { CoverageIndex } from './codeIndex/coverage.js';
25
+ import { DependencyGraphIndex } from './codeIndex/dependencyGraph.js';
26
+ import { FunctionIndex } from './codeIndex/functionIndex.js';
27
+ import { SearchIndex } from './codeIndex/search.js';
28
+ import { SchemaMigrations, migrateFromLokiJS } from './codeIndex/migrations.js';
29
+ import { MetaStore } from './codeIndex/meta.js';
30
+ import { ProvenanceStore } from './codeIndex/provenance.js';
31
+ import { InferredReceiversStore } from './codeIndex/inferredReceivers.js';
32
+ import { CodeMapIndex } from './codeIndex/codeMap.js';
33
+ import { AnalyzerConfigIndex } from './codeIndex/analyzerConfig.js';
34
+ import { AuditResultsIndex } from './codeIndex/auditResults.js';
35
+ import { WhitelistIndex } from './codeIndex/whitelist.js';
36
+ import { SchemaIndex } from './codeIndex/schema.js';
37
+ import { ProjectTasksIndex } from './codeIndex/projectTasks.js';
38
+ import { ConventionsIndex } from './codeIndex/conventions.js';
39
+ import { RawSqlIndex } from './codeIndex/rawSql.js';
40
+ // Re-exported for the security conformance test (`codeIndexDB-security.spec.ts`);
41
+ // the implementations live in `codeIndex/shared.ts` so every extracted module and
42
+ // the facade share one copy instead of drifting.
43
+ export { assertSqlIdentifier, escapeRegExpLiteral } from './codeIndex/shared.js';
44
+ export { DB_BUSY_TIMEOUT_MS };
33
45
  // ── Path containment ─────────────────────────────────────────────────────
34
46
  /** True when `filePath` is `root` itself or a descendant of `root` (not outside it). */
35
47
  function isPathUnderRoot(root, filePath) {
36
48
  const rel = path.relative(root, filePath);
37
49
  return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
38
50
  }
39
- const SQL_IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
40
- /** Reject a value that is not a plain SQL identifier. Table and column names are
41
- * interpolated into SQL (SQLite cannot parameterize identifiers), so a value that
42
- * fails this check is a would-be injection surface and is refused, never run. */
43
- export function assertSqlIdentifier(name, context) {
44
- if (!SQL_IDENTIFIER_RE.test(name)) {
45
- throw new Error(`invalid SQL identifier in ${context}: ${JSON.stringify(name)}`);
46
- }
47
- }
48
- /** Escape regex metacharacters so a string matches itself literally. */
49
- export function escapeRegExpLiteral(s) {
50
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
51
- }
52
- class SqliteCollectionAdapter {
53
- db;
54
- tableName;
55
- constructor(db, tableName) {
56
- this.db = db;
57
- this.tableName = tableName;
58
- assertSqlIdentifier(this.tableName, 'table name');
59
- }
60
- /** Return all rows, or rows matching the query. */
61
- find(query) {
62
- if (!query) {
63
- return this.db.prepare(`SELECT *, rowid as "$loki" FROM "${this.tableName}"`).all()
64
- .map(r => this.unbindRow(r));
65
- }
66
- const clauses = [];
67
- const params = {};
68
- for (const [key, value] of Object.entries(query)) {
69
- if (key !== '$loki')
70
- assertSqlIdentifier(key, 'column name');
71
- if (value === null || value === undefined) {
72
- clauses.push(`"${key}" IS NULL`);
73
- }
74
- else if (key === '$loki' && typeof value === 'object' && value.$in) {
75
- // Handle $loki: { $in: [...] }
76
- const placeholders = value.$in.map((_, i) => `@in_${i}`);
77
- clauses.push(`rowid IN (${placeholders.join(', ')})`);
78
- value.$in.forEach((v, i) => { params[`in_${i}`] = this.bindable(v); });
79
- }
80
- else if (key === 'expiresAt' && typeof value === 'object' && value.$lt) {
81
- clauses.push(`"expiresAt" < @expiresAt`);
82
- params['expiresAt'] = value.$lt instanceof Date ? value.$lt.toISOString() : String(value.$lt);
83
- }
84
- else if (key === 'timestamp' && typeof value === 'object' && value.$lt) {
85
- clauses.push(`"timestamp" < @timestamp`);
86
- params['timestamp'] = value.$lt instanceof Date ? value.$lt.toISOString() : String(value.$lt);
87
- }
88
- else {
89
- clauses.push(`"${key}" = @${key}`);
90
- params[key] = this.bindable(value);
91
- }
92
- }
93
- const sql = `SELECT *, rowid as "$loki" FROM "${this.tableName}"${clauses.length ? ' WHERE ' + clauses.join(' AND ') : ''}`;
94
- return this.db.prepare(sql).all(params).map(r => this.unbindRow(r));
95
- }
96
- findOne(query) {
97
- const clauses = [];
98
- const params = {};
99
- for (const [key, value] of Object.entries(query)) {
100
- if (key !== '$loki')
101
- assertSqlIdentifier(key, 'column name');
102
- if (value === null || value === undefined) {
103
- clauses.push(`"${key}" IS NULL`);
104
- }
105
- else {
106
- clauses.push(`"${key}" = @${key}`);
107
- params[key] = this.bindable(value);
108
- }
109
- }
110
- const sql = `SELECT *, rowid as "$loki" FROM "${this.tableName}"${clauses.length ? ' WHERE ' + clauses.join(' AND ') : ''} LIMIT 1`;
111
- const row = this.db.prepare(sql).get(params) ?? null;
112
- return row ? this.unbindRow(row) : null;
113
- }
114
- /** Convert a doc value to something SQLite can bind (primitives + Buffer, null). */
115
- bindable(v) {
116
- if (v === null || v === undefined)
117
- return null;
118
- if (typeof v === 'object' && !Buffer.isBuffer(v))
119
- return JSON.stringify(v);
120
- return v;
121
- }
122
- /** Try to parse a value that looks like serialized JSON back to its native form. */
123
- unbindable(v) {
124
- if (typeof v === 'string' && (v.startsWith('[') || v.startsWith('{'))) {
125
- try {
126
- return JSON.parse(v);
127
- }
128
- catch { /* not JSON, leave as string */ }
129
- }
130
- return v;
131
- }
132
- unbindRow(row) {
133
- if (!row)
134
- return row;
135
- const out = {};
136
- for (const [k, v] of Object.entries(row)) {
137
- out[k] = this.unbindable(v);
138
- }
139
- return out;
140
- }
141
- insert(doc) {
142
- const keys = Object.keys(doc);
143
- for (const k of keys)
144
- assertSqlIdentifier(k, 'column name');
145
- const vals = keys.map(k => `@${k}`);
146
- const sql = `INSERT INTO "${this.tableName}" ("${keys.join('", "')}") VALUES (${vals.join(', ')})`;
147
- const params = {};
148
- for (const k of keys)
149
- params[k] = this.bindable(doc[k]);
150
- const info = this.db.prepare(sql).run(params);
151
- return { ...doc, $loki: Number(info.lastInsertRowid) };
152
- }
153
- update(doc) {
154
- const keys = Object.keys(doc).filter(k => k !== '$loki' && k !== 'meta');
155
- for (const k of keys)
156
- assertSqlIdentifier(k, 'column name');
157
- const sets = keys.map(k => `"${k}" = @${k}`);
158
- const params = {};
159
- for (const k of keys)
160
- params[k] = this.bindable(doc[k]);
161
- params['_rowid'] = doc.$loki;
162
- this.db.prepare(`UPDATE "${this.tableName}" SET ${sets.join(', ')} WHERE rowid = @_rowid`).run(params);
163
- }
164
- remove(doc) {
165
- if (doc.$loki !== undefined) {
166
- this.db.prepare(`DELETE FROM "${this.tableName}" WHERE rowid = @_rowid`).run({ _rowid: doc.$loki });
167
- }
168
- }
169
- /** Remove all rows matching a query. */
170
- findAndRemove(query) {
171
- const clauses = [];
172
- const params = {};
173
- for (const [key, value] of Object.entries(query)) {
174
- if (key !== '$loki')
175
- assertSqlIdentifier(key, 'column name');
176
- if (value === null || value === undefined) {
177
- clauses.push(`"${key}" IS NULL`);
178
- }
179
- else if (key === 'timestamp' && typeof value === 'object' && value.$lt) {
180
- clauses.push(`"timestamp" < @timestamp`);
181
- params['timestamp'] = value.$lt instanceof Date ? value.$lt.toISOString() : String(value.$lt);
182
- }
183
- else {
184
- clauses.push(`"${key}" = @${key}`);
185
- params[key] = value;
186
- }
187
- }
188
- this.db.prepare(`DELETE FROM "${this.tableName}"${clauses.length ? ' WHERE ' + clauses.join(' AND ') : ''}`).run(params);
189
- }
190
- clear() {
191
- this.db.prepare(`DELETE FROM "${this.tableName}"`).run();
192
- }
193
- chain() {
194
- return {
195
- _table: this.tableName,
196
- _db: this.db,
197
- _query: null,
198
- _result: null,
199
- find(query) {
200
- this._query = query ?? null;
201
- return this;
202
- },
203
- where(fn) {
204
- if (this._result === null) {
205
- this._result = this._query !== null
206
- ? new SqliteCollectionAdapter(this._db, this._table).find(this._query)
207
- : new SqliteCollectionAdapter(this._db, this._table).find();
208
- }
209
- this._result = this._result.filter(fn);
210
- return this;
211
- },
212
- simplesort(field, opts = {}) {
213
- if (this._result === null) {
214
- this._result = this._query !== null
215
- ? new SqliteCollectionAdapter(this._db, this._table).find(this._query)
216
- : new SqliteCollectionAdapter(this._db, this._table).find();
217
- }
218
- this._result.sort((a, b) => {
219
- const av = a[field] ?? '';
220
- const bv = b[field] ?? '';
221
- if (av < bv)
222
- return opts.desc ? 1 : -1;
223
- if (av > bv)
224
- return opts.desc ? -1 : 1;
225
- return 0;
226
- });
227
- return this;
228
- },
229
- limit(n) {
230
- if (this._result === null) {
231
- this._result = this._query !== null
232
- ? new SqliteCollectionAdapter(this._db, this._table).find(this._query)
233
- : new SqliteCollectionAdapter(this._db, this._table).find();
234
- }
235
- this._result = this._result.slice(0, n);
236
- return this;
237
- },
238
- data() {
239
- return this._result ?? [];
240
- }
241
- };
242
- }
243
- }
51
+ // ── Hoisted SQL / identifier constants ─────────────────────────────────
52
+ const PRAGMA_JOURNAL_WAL = 'journal_mode = WAL';
53
+ const PRAGMA_FOREIGN_KEYS_ON = 'foreign_keys = ON';
54
+ const SQL_DISTINCT_FILE_PATHS = 'SELECT DISTINCT file_path FROM functions';
55
+ const SQL_DELETE_FUNCTIONS_IN_FILEPATHS = 'DELETE FROM functions WHERE file_path IN (SELECT value FROM json_each(?))';
244
56
  // ── Main class ──────────────────────────────────────────────────────────
57
+ /**
58
+ * SQLite-backed code index for a single project. Connection owner and
59
+ * transaction boundary; storage concerns live on the public module fields
60
+ * (`functionIndex`, `search`, `coverage`, `graph`, `whitelist`, `schema`,
61
+ * `projectTasks`, `meta`, …) and cross-concern orchestration stays here.
62
+ */
245
63
  export class CodeIndexDB {
246
64
  static instance;
247
65
  /** Subclasses (e.g. EnhancedCodeIndexDB) need access for extra tables without `as any`. */
@@ -257,31 +75,51 @@ export class CodeIndexDB {
257
75
  * *cause* (e.g. missing better-sqlite3 binding) instead of the generic
258
76
  * "Database not initialized". Cleared on success and on close(). */
259
77
  initFailure = null;
260
- // Collection adapters (preserve naming for internal clarity)
261
- functionsAdapter;
262
- whitelistAdapter;
263
- auditResultsAdapter;
264
- analyzerConfigAdapter;
265
- codeMapAdapter;
266
- schemaAdapter;
267
- schemaUsageAdapter;
268
- tasksAdapter;
269
- conventionsAdapter;
270
- fileChurnAdapter;
271
- functionChurnAdapter;
272
- pairHistoryAdapter;
273
- hotspotScoresAdapter;
274
- graphCacheAdapter;
275
- taskRepository = null;
276
- stmts = new Map();
78
+ // ── Concern modules (public fields — assigned in initializeInternal) ──
79
+ migrations;
80
+ functionIndex;
81
+ search;
82
+ coverage;
83
+ graph;
84
+ meta;
85
+ provenance;
86
+ inferredReceivers;
87
+ codeMap;
88
+ analyzerConfig;
89
+ auditResults;
90
+ whitelist;
91
+ schema;
92
+ projectTasks;
93
+ conventions;
94
+ rawSql;
95
+ /**
96
+ * Read-only facade analyzers consume — the raw-SQL, meta, and coverage
97
+ * concerns composed into the `IndexHandle` contract. Built once at
98
+ * initialization; a plain object field (not delegator methods), so it adds
99
+ * nothing to `solid/class-size`.
100
+ */
101
+ indexHandle;
277
102
  // ── Schema version ──────────────────────────────────────────────────
278
- static SCHEMA_VERSION = 16;
103
+ static SCHEMA_VERSION = 19;
104
+ /**
105
+ * Create an index handle for the given SQLite file (in-memory by default).
106
+ *
107
+ * @param dbPath - Path to the SQLite database file, or ':memory:'.
108
+ */
279
109
  constructor(dbPath = ':memory:') {
280
110
  this.dbPath = dbPath === ':memory:' ? dbPath : path.resolve(dbPath);
281
111
  }
282
112
  // ── Singleton ───────────────────────────────────────────────────────
283
113
  /** The project root this singleton was opened for (used for mismatch detection). */
284
114
  static currentProjectRoot;
115
+ /**
116
+ * Return the process-wide singleton, reopening the store when the requested
117
+ * path differs from the one currently open.
118
+ *
119
+ * @param dbPath - Optional explicit database file path.
120
+ * @param projectRoot - The project root the store is scoped to.
121
+ * @returns The singleton index instance.
122
+ */
285
123
  static getInstance(dbPath, projectRoot) {
286
124
  let resolved;
287
125
  if (dbPath !== undefined && dbPath !== '') {
@@ -330,6 +168,12 @@ export class CodeIndexDB {
330
168
  return CodeIndexDB.currentProjectRoot;
331
169
  }
332
170
  // ── Lifecycle ───────────────────────────────────────────────────────
171
+ /**
172
+ * Initialize the database schema and adapters once, memoized via a shared
173
+ * promise so concurrent callers await the same initialization.
174
+ *
175
+ * @returns A promise that resolves once initialization completes.
176
+ */
333
177
  async initialize() {
334
178
  if (this.isInitialized)
335
179
  return;
@@ -381,17 +225,17 @@ export class CodeIndexDB {
381
225
  throw e;
382
226
  const code = getErrnoCode(e);
383
227
  throw new ContextualError(`Failed to prepare code index storage: ${e instanceof Error ? e.message : String(e)}`, { ...(code && { errnoCode: code }), storageRoot, dbPath: this.dbPath,
384
- hint: 'Ensure the storage directory is writable (default: <node_modules>/.cache/code-auditor when CODE_AUDITOR_DATA_DIR is unset).' }, e instanceof Error ? e : undefined);
228
+ hint: 'Ensure the storage directory is writable (default: a user-level OS cache dir when CODE_AUDITOR_DATA_DIR is unset).' }, e instanceof Error ? e : undefined);
385
229
  }
386
230
  }
387
231
  // Check for LokiJS migration
388
- const migrationResult = this.maybeMigrateFromLokiJS();
232
+ const migrationResult = migrateFromLokiJS(this.dbPath);
389
233
  // Open SQLite database (with auto-recovery for corrupted files)
390
234
  let retried = false;
391
235
  try {
392
236
  this.db = openSqlite(this.dbPath, { timeoutMs: DB_BUSY_TIMEOUT_MS });
393
- this.db.pragma('journal_mode = WAL');
394
- this.db.pragma('foreign_keys = ON');
237
+ this.db.pragma(PRAGMA_JOURNAL_WAL);
238
+ this.db.pragma(PRAGMA_FOREIGN_KEYS_ON);
395
239
  }
396
240
  catch (e) {
397
241
  const msg = e instanceof Error ? e.message : String(e);
@@ -410,8 +254,8 @@ export class CodeIndexDB {
410
254
  }
411
255
  catch { /* ignore */ }
412
256
  this.db = openSqlite(this.dbPath, { timeoutMs: DB_BUSY_TIMEOUT_MS });
413
- this.db.pragma('journal_mode = WAL');
414
- this.db.pragma('foreign_keys = ON');
257
+ this.db.pragma(PRAGMA_JOURNAL_WAL);
258
+ this.db.pragma(PRAGMA_FOREIGN_KEYS_ON);
415
259
  }
416
260
  else {
417
261
  const code = getErrnoCode(e);
@@ -420,26 +264,41 @@ export class CodeIndexDB {
420
264
  }
421
265
  }
422
266
  // Create schema
423
- this.createSchema();
424
- // Initialize collection adapters
425
- this.functionsAdapter = new SqliteCollectionAdapter(this.db, 'functions');
426
- this.whitelistAdapter = new SqliteCollectionAdapter(this.db, 'whitelist');
427
- this.auditResultsAdapter = new SqliteCollectionAdapter(this.db, 'audit_results');
428
- this.analyzerConfigAdapter = new SqliteCollectionAdapter(this.db, 'analyzer_configs');
429
- this.codeMapAdapter = new SqliteCollectionAdapter(this.db, 'code_maps');
430
- this.schemaAdapter = new SqliteCollectionAdapter(this.db, 'schema_definitions');
431
- this.schemaUsageAdapter = new SqliteCollectionAdapter(this.db, 'schema_usage');
432
- this.tasksAdapter = new SqliteCollectionAdapter(this.db, 'project_tasks');
433
- this.conventionsAdapter = new SqliteCollectionAdapter(this.db, 'conventions');
434
- this.fileChurnAdapter = new SqliteCollectionAdapter(this.db, 'file_churn');
435
- this.functionChurnAdapter = new SqliteCollectionAdapter(this.db, 'function_churn');
436
- this.pairHistoryAdapter = new SqliteCollectionAdapter(this.db, 'dry_pair_history');
437
- this.hotspotScoresAdapter = new SqliteCollectionAdapter(this.db, 'hotspot_scores');
438
- this.graphCacheAdapter = new SqliteCollectionAdapter(this.db, 'graph_cache');
267
+ this.migrations = new SchemaMigrations(this.db);
268
+ this.migrations.createSchema(CodeIndexDB.SCHEMA_VERSION);
269
+ // Construct concern modules against the shared handle.
270
+ this.functionIndex = new FunctionIndex(this.db);
271
+ this.search = new SearchIndex(this.db, this.functionIndex);
272
+ this.coverage = new CoverageIndex(this.db);
273
+ this.graph = new DependencyGraphIndex(this.db);
274
+ this.meta = new MetaStore(this.db);
275
+ this.provenance = new ProvenanceStore(this.db);
276
+ this.inferredReceivers = new InferredReceiversStore(this.db);
277
+ this.codeMap = new CodeMapIndex(this.db);
278
+ this.analyzerConfig = new AnalyzerConfigIndex(this.db);
279
+ this.auditResults = new AuditResultsIndex(this.db);
280
+ this.whitelist = new WhitelistIndex(this.db);
281
+ this.schema = new SchemaIndex(this.db);
282
+ this.projectTasks = new ProjectTasksIndex(this.db);
283
+ this.conventions = new ConventionsIndex(this.db);
284
+ this.rawSql = new RawSqlIndex(this.db);
285
+ // Compose the read-only analyzer facade from the raw-SQL, meta, and
286
+ // coverage modules. This is the same shape the detached-audit runner builds
287
+ // inline; keeping it here gives every consumer one source of truth.
288
+ this.indexHandle = {
289
+ query: (sql, params) => this.rawSql.query(sql, params),
290
+ count: (table) => this.rawSql.count(table),
291
+ tableHasRows: (table) => this.rawSql.tableHasRows(table),
292
+ run: (sql, params) => this.rawSql.run(sql, params),
293
+ exec: (sql) => this.rawSql.exec(sql),
294
+ getMeta: (key) => this.meta.getMeta(key),
295
+ getUntestedTopDecile: (decile) => this.coverage.getUntestedTopDecile(decile),
296
+ rawDb: this.db,
297
+ };
439
298
  // Init defaults
440
299
  const whitelistCount = this.db.prepare('SELECT COUNT(*) as cnt FROM whitelist').get().cnt;
441
300
  if (whitelistCount === 0) {
442
- await this.initializeDefaultWhitelists();
301
+ await this.whitelist.initializeDefaultWhitelists();
443
302
  }
444
303
  // Auto-sync if we just migrated
445
304
  if (migrationResult.migrated) {
@@ -456,1028 +315,6 @@ export class CodeIndexDB {
456
315
  this.isInitialized = true;
457
316
  this.initFailure = null;
458
317
  }
459
- // ── Schema migrations ────────────────────────────────────────────────
460
- runMigrations() {
461
- // Read the currently stored schema version (if any)
462
- const row = this.db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
463
- const currentVersion = row ? parseInt(row.value, 10) : 0;
464
- // Migration 1 → 2: Unique index on (name, file_path, line_number)
465
- // Previously the unique index was on (name, file_path) only, which caused
466
- // same-named functions at different lines in the same file to collide.
467
- if (currentVersion < 2) {
468
- this.db.exec(`
469
- DROP INDEX IF EXISTS idx_functions_name_file;
470
- CREATE UNIQUE INDEX IF NOT EXISTS idx_functions_name_file_line ON functions(name, file_path, line_number);
471
- `);
472
- }
473
- // Migration 2 → 3: Style intelligence tables (Spec 10)
474
- if (currentVersion < 3) {
475
- this.db.exec(`
476
- CREATE TABLE IF NOT EXISTS style_declarations (
477
- id INTEGER PRIMARY KEY AUTOINCREMENT,
478
- property TEXT NOT NULL,
479
- raw_value TEXT NOT NULL,
480
- normalized_value TEXT,
481
- mechanism TEXT NOT NULL,
482
- file_path TEXT NOT NULL,
483
- line INTEGER NOT NULL,
484
- context TEXT,
485
- variant_context TEXT,
486
- token_ref TEXT,
487
- content_hash TEXT,
488
- created_at TEXT DEFAULT (datetime('now'))
489
- );
490
- CREATE INDEX IF NOT EXISTS idx_style_decls_property ON style_declarations(property);
491
- CREATE INDEX IF NOT EXISTS idx_style_decls_mechanism ON style_declarations(mechanism);
492
- CREATE INDEX IF NOT EXISTS idx_style_decls_file_path ON style_declarations(file_path);
493
- CREATE INDEX IF NOT EXISTS idx_style_decls_token_ref ON style_declarations(token_ref);
494
- CREATE INDEX IF NOT EXISTS idx_style_decls_content_hash ON style_declarations(content_hash);
495
-
496
- CREATE VIRTUAL TABLE IF NOT EXISTS style_declarations_fts USING fts5(
497
- property, raw_value, normalized_value,
498
- content='style_declarations', content_rowid='id',
499
- tokenize='porter unicode61'
500
- );
501
-
502
- CREATE TABLE IF NOT EXISTS style_tokens (
503
- id INTEGER PRIMARY KEY AUTOINCREMENT,
504
- name TEXT NOT NULL,
505
- value TEXT NOT NULL,
506
- file_path TEXT NOT NULL,
507
- mechanism TEXT NOT NULL,
508
- created_at TEXT DEFAULT (datetime('now'))
509
- );
510
- CREATE INDEX IF NOT EXISTS idx_style_tokens_name ON style_tokens(name);
511
-
512
- CREATE TABLE IF NOT EXISTS style_class_usage (
513
- id INTEGER PRIMARY KEY AUTOINCREMENT,
514
- class_name TEXT NOT NULL,
515
- file_path TEXT NOT NULL,
516
- line INTEGER NOT NULL,
517
- mechanism TEXT NOT NULL,
518
- unresolvable INTEGER DEFAULT 0,
519
- created_at TEXT DEFAULT (datetime('now'))
520
- );
521
- CREATE INDEX IF NOT EXISTS idx_style_class_usage_name ON style_class_usage(class_name);
522
- CREATE INDEX IF NOT EXISTS idx_style_class_usage_file ON style_class_usage(file_path);
523
- CREATE INDEX IF NOT EXISTS idx_style_class_usage_unres ON style_class_usage(unresolvable);
524
- `);
525
- }
526
- // Migration 3 → 4: Convention mining tables (Spec 12)
527
- if (currentVersion < 4) {
528
- this.db.exec(`
529
- CREATE TABLE IF NOT EXISTS conventions (
530
- id INTEGER PRIMARY KEY AUTOINCREMENT,
531
- domain TEXT NOT NULL,
532
- rule_id TEXT NOT NULL,
533
- antecedent TEXT,
534
- consequent TEXT,
535
- pattern TEXT,
536
- directory TEXT,
537
- file_path TEXT,
538
- line INTEGER,
539
- support INTEGER DEFAULT 0,
540
- total_cases INTEGER DEFAULT 0,
541
- confidence REAL DEFAULT 0,
542
- exemplar_file TEXT,
543
- exemplar_line INTEGER,
544
- hash TEXT,
545
- created_at TEXT DEFAULT (datetime('now'))
546
- );
547
- CREATE INDEX IF NOT EXISTS idx_conv_domain ON conventions(domain);
548
- CREATE INDEX IF NOT EXISTS idx_conv_rule_id ON conventions(rule_id);
549
- CREATE INDEX IF NOT EXISTS idx_conv_directory ON conventions(directory);
550
- CREATE INDEX IF NOT EXISTS idx_conv_hash ON conventions(hash);
551
- `);
552
- }
553
- // Migration 4 → 5: Hotspots & temporal analysis (Spec 13)
554
- if (currentVersion < 5) {
555
- this.db.exec(`
556
- CREATE TABLE IF NOT EXISTS file_churn (
557
- file_path TEXT PRIMARY KEY,
558
- commit_count INTEGER NOT NULL DEFAULT 0,
559
- lines_added INTEGER NOT NULL DEFAULT 0,
560
- lines_deleted INTEGER NOT NULL DEFAULT 0,
561
- distinct_authors INTEGER NOT NULL DEFAULT 0,
562
- dominant_author TEXT,
563
- dominant_author_share REAL DEFAULT 0,
564
- last_touched TEXT
565
- );
566
- CREATE INDEX IF NOT EXISTS idx_file_churn_cc ON file_churn(commit_count);
567
-
568
- CREATE TABLE IF NOT EXISTS function_churn (
569
- id INTEGER PRIMARY KEY AUTOINCREMENT,
570
- function_id INTEGER REFERENCES functions(id) ON DELETE CASCADE,
571
- function_name TEXT NOT NULL,
572
- file_path TEXT NOT NULL,
573
- commit_count INTEGER NOT NULL DEFAULT 0,
574
- distinct_authors INTEGER NOT NULL DEFAULT 0,
575
- dominant_author TEXT,
576
- dominant_author_share REAL DEFAULT 0,
577
- renamed INTEGER DEFAULT 0,
578
- confidence REAL DEFAULT 1.0
579
- );
580
- CREATE INDEX IF NOT EXISTS idx_func_churn_fid ON function_churn(function_id);
581
-
582
- CREATE TABLE IF NOT EXISTS hotspot_scores (
583
- target TEXT PRIMARY KEY,
584
- type TEXT NOT NULL,
585
- score REAL NOT NULL DEFAULT 0,
586
- churn_pct REAL NOT NULL DEFAULT 0,
587
- complexity_pct REAL NOT NULL DEFAULT 0,
588
- commit_count INTEGER NOT NULL DEFAULT 0,
589
- distinct_authors INTEGER NOT NULL DEFAULT 0,
590
- dominant_author TEXT,
591
- dominant_author_share REAL DEFAULT 0,
592
- bus_factor_risk INTEGER DEFAULT 0,
593
- complexity INTEGER NOT NULL DEFAULT 0,
594
- updated_at TEXT DEFAULT (datetime('now'))
595
- );
596
- CREATE INDEX IF NOT EXISTS idx_hotspot_scores_score ON hotspot_scores(score DESC);
597
-
598
- CREATE TABLE IF NOT EXISTS dry_pair_history (
599
- id INTEGER PRIMARY KEY AUTOINCREMENT,
600
- pair_fingerprint TEXT NOT NULL,
601
- file1 TEXT NOT NULL,
602
- symbol1 TEXT,
603
- line1 INTEGER NOT NULL,
604
- content_hash1 TEXT NOT NULL,
605
- file2 TEXT NOT NULL,
606
- symbol2 TEXT,
607
- line2 INTEGER NOT NULL,
608
- content_hash2 TEXT NOT NULL,
609
- similarity REAL NOT NULL,
610
- timestamp TEXT DEFAULT (datetime('now')),
611
- run_id TEXT NOT NULL
612
- );
613
- CREATE INDEX IF NOT EXISTS idx_dph_fingerprint ON dry_pair_history(pair_fingerprint);
614
- CREATE INDEX IF NOT EXISTS idx_dph_run ON dry_pair_history(run_id);
615
- `);
616
- }
617
- // Migration 5 → 6: Graph cache for call/import graph construction (Spec 14)
618
- if (currentVersion < 6) {
619
- this.db.exec(`
620
- CREATE TABLE IF NOT EXISTS graph_cache (
621
- graph_type TEXT NOT NULL,
622
- node_key TEXT NOT NULL,
623
- neighbor_key TEXT NOT NULL,
624
- weight REAL NOT NULL,
625
- PRIMARY KEY (graph_type, node_key, neighbor_key)
626
- );
627
- CREATE INDEX IF NOT EXISTS idx_gc_type_node ON graph_cache(graph_type, node_key);
628
- CREATE INDEX IF NOT EXISTS idx_gc_type_neighbor ON graph_cache(graph_type, neighbor_key);
629
- `);
630
- }
631
- // Migration 6 → 7: Coverage data for cross-domain analysis (Spec 15)
632
- if (currentVersion < 7) {
633
- this.db.exec(`
634
- CREATE TABLE IF NOT EXISTS coverage_data (
635
- id INTEGER PRIMARY KEY AUTOINCREMENT,
636
- function_name TEXT NOT NULL,
637
- file_path TEXT NOT NULL,
638
- line_number INTEGER,
639
- basis TEXT NOT NULL DEFAULT 'static-reach',
640
- covered INTEGER NOT NULL DEFAULT 0,
641
- source TEXT,
642
- imported_at TEXT DEFAULT (datetime('now')),
643
- UNIQUE(function_name, file_path, line_number)
644
- );
645
- CREATE INDEX IF NOT EXISTS idx_cov_basis ON coverage_data(basis);
646
- CREATE INDEX IF NOT EXISTS idx_cov_covered ON coverage_data(covered);
647
- CREATE INDEX IF NOT EXISTS idx_cov_function ON coverage_data(function_name);
648
- `);
649
- }
650
- // Migration 7 → 8: export_kind on conventions (Spec 22 R5.1)
651
- if (currentVersion < 8) {
652
- const cols = this.db
653
- .prepare(`PRAGMA table_info('conventions')`)
654
- .all();
655
- if (!cols.some((c) => c.name === 'export_kind')) {
656
- this.db.exec(`ALTER TABLE conventions ADD COLUMN export_kind TEXT`);
657
- }
658
- }
659
- // Migration 8 → 9: Detached-run lifecycle + queryable coverage (Spec 41).
660
- // `findings_ledger_runs` gains the job-lifecycle columns (status, timing,
661
- // provenance, lease heartbeat, progress, stderr log); the one genuinely new
662
- // table is `findings_ledger_coverage` (per-rule state + reason rows that
663
- // `writeAuditToLedger` previously dropped).
664
- if (currentVersion < 9) {
665
- const runCols = this.db
666
- .prepare(`PRAGMA table_info('findings_ledger_runs')`)
667
- .all();
668
- const hasRunCol = (name) => runCols.some((c) => c.name === name);
669
- const newRunCols = [
670
- ['status', "TEXT NOT NULL DEFAULT 'completed'"],
671
- ['project_root', 'TEXT'],
672
- ['started_at', 'TEXT'],
673
- ['heartbeat_at', 'TEXT'],
674
- ['finished_at', 'TEXT'],
675
- ['error', 'TEXT'],
676
- ['progress_json', 'TEXT'],
677
- ['stderr_log', 'TEXT'],
678
- ['content_hash', 'TEXT'],
679
- ['files_count', 'INTEGER'],
680
- ['file_manifest_json', 'TEXT'],
681
- ];
682
- for (const [name, decl] of newRunCols) {
683
- if (!hasRunCol(name)) {
684
- this.db.exec(`ALTER TABLE findings_ledger_runs ADD COLUMN ${name} ${decl}`);
685
- }
686
- }
687
- this.db.exec(`
688
- CREATE TABLE IF NOT EXISTS findings_ledger_coverage (
689
- run_id TEXT NOT NULL REFERENCES findings_ledger_runs(run_id) ON DELETE CASCADE,
690
- analyzer TEXT NOT NULL,
691
- rule_id TEXT NOT NULL,
692
- state TEXT NOT NULL,
693
- count INTEGER NOT NULL DEFAULT 0,
694
- reason TEXT,
695
- PRIMARY KEY (run_id, analyzer, rule_id)
696
- );
697
- CREATE INDEX IF NOT EXISTS idx_ledger_coverage_run ON findings_ledger_coverage(run_id);
698
- `);
699
- }
700
- // Migration 9 → 10: PID-based lease liveness (Spec 41 Amendment B).
701
- // The heartbeat is no longer the primary liveness signal for the common
702
- // case (a healthy child whose synchronous `syncFileIndex` phase starves the
703
- // event loop, so its heartbeat interval cannot fire). The child records its
704
- // PID + process start time + hostname at lease claim; `reclaimStaleRunning`
705
- // then skips any `running` row whose PID is still the same live process, and
706
- // only reclaims when the process is genuinely gone (or on a foreign host,
707
- // where the PID means nothing and the heartbeat stays the fallback).
708
- if (currentVersion < 10) {
709
- const runCols = this.db
710
- .prepare(`PRAGMA table_info('findings_ledger_runs')`)
711
- .all();
712
- const hasRunCol = (name) => runCols.some((c) => c.name === name);
713
- const newRunCols = [
714
- ['runner_pid', 'INTEGER'],
715
- ['runner_pid_started_at', 'TEXT'],
716
- ['runner_host', 'TEXT'],
717
- ];
718
- for (const [name, decl] of newRunCols) {
719
- if (!hasRunCol(name)) {
720
- this.db.exec(`ALTER TABLE findings_ledger_runs ADD COLUMN ${name} ${decl}`);
721
- }
722
- }
723
- }
724
- // Migration 10 → 11: unread stylesheet sources (Spec 45 R5).
725
- // Records stylesheets whose dialect the style indexer cannot read, so
726
- // styles/undefined-class findings can carry them as incomplete-definition
727
- // context instead of asserting a class is undefined against the whole project.
728
- if (currentVersion < 11) {
729
- this.db.exec(`
730
- CREATE TABLE IF NOT EXISTS style_unread_sources (
731
- id INTEGER PRIMARY KEY AUTOINCREMENT,
732
- file_path TEXT NOT NULL UNIQUE,
733
- reason TEXT NOT NULL,
734
- created_at TEXT DEFAULT (datetime('now'))
735
- );
736
- `);
737
- }
738
- // Migration 11 → 12: defined-class catalog (Spec 45 — styles/undefined-class).
739
- // A dedicated (class_name, file_path) table so the undefined-class detector
740
- // can resolve a class name with an indexed `class_name IN (...)` lookup
741
- // instead of loading every style_declarations row and regex-extracting
742
- // selectors from `context` in JS. Populated by the style indexer on insert;
743
- // backfilled here once so an in-place upgrade does not leave the catalog
744
- // empty (unchanged-file hashing would otherwise skip the re-extract).
745
- if (currentVersion < 12) {
746
- this.db.exec(`
747
- CREATE TABLE IF NOT EXISTS style_defined_classes (
748
- id INTEGER PRIMARY KEY AUTOINCREMENT,
749
- class_name TEXT NOT NULL,
750
- file_path TEXT NOT NULL,
751
- created_at TEXT DEFAULT (datetime('now')),
752
- UNIQUE(class_name, file_path)
753
- );
754
- CREATE INDEX IF NOT EXISTS idx_style_defined_class_name ON style_defined_classes(class_name);
755
- `);
756
- const definedRows = this.db
757
- .prepare('SELECT DISTINCT context, file_path FROM style_declarations WHERE context IS NOT NULL')
758
- .all();
759
- const insertDefined = this.db.prepare('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)');
760
- const backfill = this.db.transaction(() => {
761
- for (const r of definedRows) {
762
- for (const m of r.context.matchAll(/\.([a-zA-Z0-9_-]+)/g)) {
763
- insertDefined.run(m[1], r.file_path);
764
- }
765
- }
766
- });
767
- backfill();
768
- }
769
- // Migration 12 → 13: import_specifiers (Spec 60 — import classification at
770
- // emission). One row per static import specifier occurrence, classified
771
- // package / unresolved-alias / internal-resolved / internal-broken /
772
- // unresolved-virtual (Spec 60.1 widened the three-class scheme to five;
773
- // `classification` is free TEXT, no constraint, so no further migration).
774
- // Nothing consumes it yet (Spec 61 persists edges; Spec 62 derives coverage).
775
- // Flat — resolved_path inline, no lookup table, no join.
776
- if (currentVersion < 13) {
777
- this.db.exec(`
778
- CREATE TABLE IF NOT EXISTS import_specifiers (
779
- file_path TEXT NOT NULL,
780
- specifier TEXT NOT NULL,
781
- classification TEXT NOT NULL,
782
- resolved_path TEXT,
783
- line INTEGER,
784
- PRIMARY KEY (file_path, specifier, line)
785
- );
786
- CREATE INDEX IF NOT EXISTS idx_import_specifiers_class ON import_specifiers(classification);
787
- CREATE INDEX IF NOT EXISTS idx_import_specifiers_resolved ON import_specifiers(resolved_path);
788
- `);
789
- }
790
- // Migration 13 → 14: schema_usage identity becomes a coordinate (Spec 61
791
- // Amendment A). function_name stops holding source text and becomes the
792
- // nullable display name (null for anonymous handlers); identity is the
793
- // inline (file_path, function_start_line, function_start_column) coordinate.
794
- // function_name drops its NOT NULL so anonymous handlers store NULL, distinct
795
- // from 'top-level' (function_start_line IS NULL). Old rows keep their
796
- // (now-legacy) names but are regenerated on the next per-file sync.
797
- if (currentVersion < 14) {
798
- const suCols = this.db
799
- .prepare(`PRAGMA table_info('schema_usage')`)
800
- .all();
801
- if (!suCols.some((c) => c.name === 'function_start_line')) {
802
- this.db.exec(`
803
- ALTER TABLE schema_usage RENAME TO schema_usage_old;
804
- CREATE TABLE schema_usage (
805
- id INTEGER PRIMARY KEY AUTOINCREMENT,
806
- schema_id TEXT,
807
- table_name TEXT NOT NULL,
808
- file_path TEXT NOT NULL,
809
- function_name TEXT,
810
- function_start_line INTEGER,
811
- function_start_column INTEGER,
812
- usage_type TEXT NOT NULL,
813
- line INTEGER,
814
- "column" INTEGER,
815
- raw_query TEXT,
816
- parameters TEXT,
817
- origin TEXT,
818
- recorded_at TEXT DEFAULT (datetime('now'))
819
- );
820
- INSERT INTO schema_usage (id, schema_id, table_name, file_path, function_name, usage_type, line, "column", raw_query, parameters, recorded_at)
821
- SELECT id, schema_id, table_name, file_path, function_name, usage_type, line, "column", raw_query, parameters, recorded_at FROM schema_usage_old;
822
- DROP TABLE schema_usage_old;
823
- CREATE INDEX IF NOT EXISTS idx_schema_usage_table ON schema_usage(table_name);
824
- CREATE INDEX IF NOT EXISTS idx_schema_usage_file ON schema_usage(file_path);
825
- CREATE INDEX IF NOT EXISTS idx_schema_usage_function ON schema_usage(function_name);
826
- CREATE INDEX IF NOT EXISTS idx_schema_usage_usage_type ON schema_usage(usage_type);
827
- `);
828
- }
829
- }
830
- // Migration 14 → 15: tool's own git sha on the run (the second axis next to
831
- // tool_version). `git_sha` already records the *audited project* commit; this
832
- // records the *code-auditor* commit so a count change is attributable to a
833
- // tool commit, not just a version. NULL when running from a published
834
- // install (no .git) — best-effort, and tool_version stays always-present.
835
- if (currentVersion < 15) {
836
- const runCols = this.db
837
- .prepare(`PRAGMA table_info('findings_ledger_runs')`)
838
- .all();
839
- if (!runCols.some((c) => c.name === 'tool_git_sha')) {
840
- this.db.exec(`ALTER TABLE findings_ledger_runs ADD COLUMN tool_git_sha TEXT`);
841
- }
842
- }
843
- // Migration 15 → 16: schema_usage gains an `origin` column. Rows produced by
844
- // the knex-style fluent-builder read extractor carry `origin = 'query-builder'`;
845
- // every other extractor (raw SQL, tagged template, .sql, ORM) leaves it NULL.
846
- // The cross-domain lifecycle rules use it to exempt tables whose usage is
847
- // entirely query-builder — scratch/test tables built and consumed through the
848
- // fluent builder are not one-sided lifecycle defects.
849
- if (currentVersion < 16) {
850
- const suCols = this.db
851
- .prepare(`PRAGMA table_info('schema_usage')`)
852
- .all();
853
- if (!suCols.some((c) => c.name === 'origin')) {
854
- this.db.exec(`ALTER TABLE schema_usage ADD COLUMN origin TEXT`);
855
- }
856
- }
857
- }
858
- // ── SQLite schema ───────────────────────────────────────────────────
859
- createSchema() {
860
- this.db.exec(`
861
- CREATE TABLE IF NOT EXISTS meta (
862
- key TEXT PRIMARY KEY,
863
- value TEXT NOT NULL
864
- );
865
-
866
- CREATE TABLE IF NOT EXISTS functions (
867
- id INTEGER PRIMARY KEY AUTOINCREMENT,
868
- name TEXT NOT NULL,
869
- file_path TEXT NOT NULL,
870
- line_number INTEGER,
871
- start_line INTEGER,
872
- end_line INTEGER,
873
- language TEXT DEFAULT 'typescript',
874
- entity_type TEXT DEFAULT 'function',
875
- component_type TEXT,
876
- signature TEXT,
877
- return_type TEXT,
878
- complexity INTEGER DEFAULT 0,
879
- is_exported INTEGER DEFAULT 0,
880
- has_jsdoc INTEGER DEFAULT 0,
881
- jsdoc_description TEXT,
882
- jsdoc_tags TEXT,
883
- parameters TEXT,
884
- type_info TEXT,
885
- hooks TEXT,
886
- props TEXT,
887
- used_imports TEXT,
888
- unused_imports TEXT,
889
- import_usage TEXT,
890
- has_unused_imports INTEGER DEFAULT 0,
891
- dependency_depth INTEGER DEFAULT 0,
892
- purpose TEXT DEFAULT '',
893
- context TEXT DEFAULT '',
894
- body TEXT,
895
- content_hash TEXT,
896
- last_modified TEXT,
897
- metadata_json TEXT,
898
- created_at TEXT DEFAULT (datetime('now')),
899
- updated_at TEXT DEFAULT (datetime('now'))
900
- );
901
-
902
- CREATE INDEX IF NOT EXISTS idx_functions_name ON functions(name);
903
- CREATE INDEX IF NOT EXISTS idx_functions_file_path ON functions(file_path);
904
- CREATE INDEX IF NOT EXISTS idx_functions_language ON functions(language);
905
- CREATE INDEX IF NOT EXISTS idx_functions_entity_type ON functions(entity_type);
906
- CREATE INDEX IF NOT EXISTS idx_functions_complexity ON functions(complexity);
907
- CREATE INDEX IF NOT EXISTS idx_functions_content_hash ON functions(content_hash);
908
- CREATE INDEX IF NOT EXISTS idx_functions_is_exported ON functions(is_exported);
909
- CREATE UNIQUE INDEX IF NOT EXISTS idx_functions_name_file_line ON functions(name, file_path, line_number);
910
-
911
- CREATE VIRTUAL TABLE IF NOT EXISTS functions_fts USING fts5(
912
- name, signature, jsdoc_description, purpose, context, body,
913
- content='functions', content_rowid='id',
914
- tokenize='porter unicode61'
915
- );
916
-
917
- CREATE TRIGGER IF NOT EXISTS functions_ai AFTER INSERT ON functions BEGIN
918
- INSERT INTO functions_fts(rowid, name, signature, jsdoc_description, purpose, context, body)
919
- VALUES (new.id, new.name, new.signature, new.jsdoc_description, new.purpose, new.context, new.body);
920
- END;
921
- CREATE TRIGGER IF NOT EXISTS functions_ad AFTER DELETE ON functions BEGIN
922
- INSERT INTO functions_fts(functions_fts, rowid, name, signature, jsdoc_description, purpose, context, body)
923
- VALUES ('delete', old.id, old.name, old.signature, old.jsdoc_description, old.purpose, old.context, old.body);
924
- END;
925
- CREATE TRIGGER IF NOT EXISTS functions_au AFTER UPDATE ON functions BEGIN
926
- INSERT INTO functions_fts(functions_fts, rowid, name, signature, jsdoc_description, purpose, context, body)
927
- VALUES ('delete', old.id, old.name, old.signature, old.jsdoc_description, old.purpose, old.context, old.body);
928
- INSERT INTO functions_fts(rowid, name, signature, jsdoc_description, purpose, context, body)
929
- VALUES (new.id, new.name, new.signature, new.jsdoc_description, new.purpose, new.context, new.body);
930
- END;
931
-
932
- CREATE TABLE IF NOT EXISTS function_calls (
933
- caller_id INTEGER NOT NULL REFERENCES functions(id) ON DELETE CASCADE,
934
- callee_name TEXT NOT NULL,
935
- PRIMARY KEY (caller_id, callee_name)
936
- );
937
- CREATE INDEX IF NOT EXISTS idx_function_calls_callee ON function_calls(callee_name);
938
-
939
- CREATE TABLE IF NOT EXISTS function_dependencies (
940
- function_id INTEGER NOT NULL REFERENCES functions(id) ON DELETE CASCADE,
941
- dependency TEXT NOT NULL,
942
- PRIMARY KEY (function_id, dependency)
943
- );
944
- CREATE INDEX IF NOT EXISTS idx_function_dependencies_dep ON function_dependencies(dependency);
945
-
946
- CREATE TABLE IF NOT EXISTS whitelist (
947
- id INTEGER PRIMARY KEY AUTOINCREMENT,
948
- name TEXT NOT NULL,
949
- type TEXT NOT NULL,
950
- status TEXT DEFAULT 'Active',
951
- category TEXT,
952
- description TEXT,
953
- patterns TEXT,
954
- added_by TEXT DEFAULT 'system',
955
- added_at TEXT DEFAULT (datetime('now')),
956
- updated_at TEXT,
957
- metadata_json TEXT
958
- );
959
- CREATE INDEX IF NOT EXISTS idx_whitelist_name ON whitelist(name);
960
- CREATE INDEX IF NOT EXISTS idx_whitelist_type ON whitelist(type);
961
- CREATE INDEX IF NOT EXISTS idx_whitelist_status ON whitelist(status);
962
-
963
- CREATE TABLE IF NOT EXISTS audit_results (
964
- audit_id TEXT PRIMARY KEY,
965
- timestamp TEXT NOT NULL,
966
- project_path TEXT NOT NULL,
967
- summary_json TEXT NOT NULL,
968
- analyzer_results_json TEXT NOT NULL,
969
- violations_json TEXT,
970
- recommendations_json TEXT,
971
- metadata_json TEXT,
972
- expires_at TEXT NOT NULL
973
- );
974
- CREATE INDEX IF NOT EXISTS idx_audit_results_timestamp ON audit_results(timestamp);
975
- CREATE INDEX IF NOT EXISTS idx_audit_results_project_path ON audit_results(project_path);
976
-
977
- CREATE TABLE IF NOT EXISTS analyzer_configs (
978
- id INTEGER PRIMARY KEY AUTOINCREMENT,
979
- analyzer_name TEXT NOT NULL,
980
- project_path TEXT,
981
- is_global INTEGER DEFAULT 0,
982
- config_json TEXT NOT NULL DEFAULT '{}',
983
- version TEXT,
984
- created_by TEXT DEFAULT 'system',
985
- created_at TEXT DEFAULT (datetime('now')),
986
- updated_at TEXT DEFAULT (datetime('now')),
987
- metadata_json TEXT
988
- );
989
- CREATE UNIQUE INDEX IF NOT EXISTS idx_analyzer_configs_key
990
- ON analyzer_configs(analyzer_name, COALESCE(project_path, '__global__'), is_global);
991
-
992
- CREATE TABLE IF NOT EXISTS code_maps (
993
- map_id TEXT NOT NULL,
994
- section_type TEXT NOT NULL,
995
- content TEXT NOT NULL,
996
- metadata_json TEXT DEFAULT '{}',
997
- timestamp TEXT DEFAULT (datetime('now')),
998
- size INTEGER DEFAULT 0,
999
- PRIMARY KEY (map_id, section_type)
1000
- );
1001
- CREATE INDEX IF NOT EXISTS idx_code_maps_timestamp ON code_maps(timestamp);
1002
-
1003
- CREATE TABLE IF NOT EXISTS schema_definitions (
1004
- schema_id TEXT PRIMARY KEY,
1005
- schema_name TEXT,
1006
- schema_json TEXT NOT NULL,
1007
- metadata_json TEXT,
1008
- indexed_at TEXT DEFAULT (datetime('now'))
1009
- );
1010
-
1011
- CREATE TABLE IF NOT EXISTS schema_usage (
1012
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1013
- schema_id TEXT,
1014
- table_name TEXT NOT NULL,
1015
- file_path TEXT NOT NULL,
1016
- function_name TEXT,
1017
- function_start_line INTEGER,
1018
- function_start_column INTEGER,
1019
- usage_type TEXT NOT NULL,
1020
- line INTEGER,
1021
- "column" INTEGER,
1022
- raw_query TEXT,
1023
- parameters TEXT,
1024
- origin TEXT,
1025
- recorded_at TEXT DEFAULT (datetime('now'))
1026
- );
1027
- CREATE INDEX IF NOT EXISTS idx_schema_usage_table ON schema_usage(table_name);
1028
- CREATE INDEX IF NOT EXISTS idx_schema_usage_file ON schema_usage(file_path);
1029
- CREATE INDEX IF NOT EXISTS idx_schema_usage_function ON schema_usage(function_name);
1030
- CREATE INDEX IF NOT EXISTS idx_schema_usage_usage_type ON schema_usage(usage_type);
1031
-
1032
- CREATE TABLE IF NOT EXISTS coverage_data (
1033
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1034
- function_name TEXT NOT NULL,
1035
- file_path TEXT NOT NULL,
1036
- line_number INTEGER,
1037
- basis TEXT NOT NULL DEFAULT 'static-reach',
1038
- covered INTEGER NOT NULL DEFAULT 0,
1039
- source TEXT,
1040
- imported_at TEXT DEFAULT (datetime('now')),
1041
- UNIQUE(function_name, file_path, line_number)
1042
- );
1043
- CREATE INDEX IF NOT EXISTS idx_cov_basis ON coverage_data(basis);
1044
- CREATE INDEX IF NOT EXISTS idx_cov_covered ON coverage_data(covered);
1045
- CREATE INDEX IF NOT EXISTS idx_cov_function ON coverage_data(function_name);
1046
-
1047
- CREATE TABLE IF NOT EXISTS project_tasks (
1048
- taskId TEXT PRIMARY KEY,
1049
- projectPath TEXT NOT NULL,
1050
- title TEXT NOT NULL,
1051
- description TEXT DEFAULT '',
1052
- status TEXT DEFAULT 'pending',
1053
- priority TEXT DEFAULT 'medium',
1054
- labels TEXT DEFAULT '[]',
1055
- source TEXT DEFAULT 'manual',
1056
- parentTaskId TEXT,
1057
- blockedBy TEXT DEFAULT '[]',
1058
- dueAt TEXT,
1059
- sortOrder INTEGER DEFAULT 0,
1060
- relatedFiles TEXT DEFAULT '[]',
1061
- relatedSymbols TEXT DEFAULT '[]',
1062
- fingerprint TEXT,
1063
- metadata TEXT DEFAULT '{}',
1064
- createdAt TEXT DEFAULT (datetime('now')),
1065
- updatedAt TEXT DEFAULT (datetime('now')),
1066
- completedAt TEXT
1067
- );
1068
- CREATE INDEX IF NOT EXISTS idx_project_tasks_project ON project_tasks(projectPath);
1069
- CREATE INDEX IF NOT EXISTS idx_project_tasks_status ON project_tasks(status);
1070
- CREATE INDEX IF NOT EXISTS idx_project_tasks_source ON project_tasks(source);
1071
- CREATE INDEX IF NOT EXISTS idx_project_tasks_fingerprint ON project_tasks(fingerprint);
1072
- CREATE INDEX IF NOT EXISTS idx_project_tasks_sort ON project_tasks(sortOrder);
1073
-
1074
- -- Spec 11 R1 — Findings Ledger: append-only audit history
1075
- CREATE TABLE IF NOT EXISTS findings_ledger_runs (
1076
- run_id TEXT PRIMARY KEY,
1077
- timestamp TEXT NOT NULL,
1078
- git_sha TEXT,
1079
- git_dirty INTEGER NOT NULL DEFAULT 0,
1080
- tool_version TEXT NOT NULL,
1081
- tool_git_sha TEXT,
1082
- command TEXT NOT NULL,
1083
- surface TEXT NOT NULL,
1084
- scope TEXT NOT NULL,
1085
- target TEXT NOT NULL,
1086
- duration_ms INTEGER NOT NULL DEFAULT 0,
1087
- exit_status INTEGER NOT NULL DEFAULT 0,
1088
- metadata_json TEXT DEFAULT '{}',
1089
- status TEXT NOT NULL DEFAULT 'completed',
1090
- project_root TEXT,
1091
- started_at TEXT,
1092
- heartbeat_at TEXT,
1093
- finished_at TEXT,
1094
- error TEXT,
1095
- progress_json TEXT,
1096
- stderr_log TEXT,
1097
- content_hash TEXT,
1098
- files_count INTEGER,
1099
- file_manifest_json TEXT,
1100
- runner_pid INTEGER,
1101
- runner_pid_started_at TEXT,
1102
- runner_host TEXT
1103
- );
1104
- CREATE TABLE IF NOT EXISTS findings_ledger_findings (
1105
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1106
- run_id TEXT NOT NULL REFERENCES findings_ledger_runs(run_id) ON DELETE CASCADE,
1107
- analyzer TEXT NOT NULL,
1108
- rule TEXT NOT NULL,
1109
- severity TEXT NOT NULL,
1110
- message TEXT NOT NULL,
1111
- file TEXT NOT NULL,
1112
- line INTEGER,
1113
- symbol TEXT DEFAULT '',
1114
- fingerprint TEXT NOT NULL
1115
- );
1116
- CREATE TABLE IF NOT EXISTS findings_ledger_coverage (
1117
- run_id TEXT NOT NULL REFERENCES findings_ledger_runs(run_id) ON DELETE CASCADE,
1118
- analyzer TEXT NOT NULL,
1119
- rule_id TEXT NOT NULL,
1120
- state TEXT NOT NULL,
1121
- count INTEGER NOT NULL DEFAULT 0,
1122
- reason TEXT,
1123
- PRIMARY KEY (run_id, analyzer, rule_id)
1124
- );
1125
- CREATE INDEX IF NOT EXISTS idx_ledger_runs_surface ON findings_ledger_runs(surface);
1126
- CREATE INDEX IF NOT EXISTS idx_ledger_runs_timestamp ON findings_ledger_runs(timestamp);
1127
- CREATE INDEX IF NOT EXISTS idx_ledger_coverage_run ON findings_ledger_coverage(run_id);
1128
- CREATE INDEX IF NOT EXISTS idx_ledger_findings_run ON findings_ledger_findings(run_id);
1129
- CREATE INDEX IF NOT EXISTS idx_ledger_findings_fp ON findings_ledger_findings(fingerprint);
1130
- CREATE INDEX IF NOT EXISTS idx_ledger_findings_rule ON findings_ledger_findings(analyzer, rule);
1131
-
1132
- -- Spec 10: Style intelligence tables
1133
- CREATE TABLE IF NOT EXISTS style_declarations (
1134
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1135
- property TEXT NOT NULL,
1136
- raw_value TEXT NOT NULL,
1137
- normalized_value TEXT,
1138
- mechanism TEXT NOT NULL,
1139
- file_path TEXT NOT NULL,
1140
- line INTEGER NOT NULL,
1141
- context TEXT,
1142
- variant_context TEXT,
1143
- token_ref TEXT,
1144
- content_hash TEXT,
1145
- created_at TEXT DEFAULT (datetime('now'))
1146
- );
1147
- CREATE INDEX IF NOT EXISTS idx_style_decls_property ON style_declarations(property);
1148
- CREATE INDEX IF NOT EXISTS idx_style_decls_mechanism ON style_declarations(mechanism);
1149
- CREATE INDEX IF NOT EXISTS idx_style_decls_file_path ON style_declarations(file_path);
1150
- CREATE INDEX IF NOT EXISTS idx_style_decls_token_ref ON style_declarations(token_ref);
1151
- CREATE INDEX IF NOT EXISTS idx_style_decls_content_hash ON style_declarations(content_hash);
1152
-
1153
- CREATE VIRTUAL TABLE IF NOT EXISTS style_declarations_fts USING fts5(
1154
- property, raw_value, normalized_value,
1155
- content='style_declarations', content_rowid='id',
1156
- tokenize='porter unicode61'
1157
- );
1158
-
1159
- CREATE TABLE IF NOT EXISTS style_tokens (
1160
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1161
- name TEXT NOT NULL,
1162
- value TEXT NOT NULL,
1163
- file_path TEXT NOT NULL,
1164
- mechanism TEXT NOT NULL,
1165
- created_at TEXT DEFAULT (datetime('now'))
1166
- );
1167
- CREATE INDEX IF NOT EXISTS idx_style_tokens_name ON style_tokens(name);
1168
-
1169
- CREATE TABLE IF NOT EXISTS style_class_usage (
1170
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1171
- class_name TEXT NOT NULL,
1172
- file_path TEXT NOT NULL,
1173
- line INTEGER NOT NULL,
1174
- mechanism TEXT NOT NULL,
1175
- unresolvable INTEGER DEFAULT 0,
1176
- created_at TEXT DEFAULT (datetime('now'))
1177
- );
1178
- CREATE INDEX IF NOT EXISTS idx_style_class_usage_name ON style_class_usage(class_name);
1179
- CREATE INDEX IF NOT EXISTS idx_style_class_usage_file ON style_class_usage(file_path);
1180
- CREATE INDEX IF NOT EXISTS idx_style_class_usage_unres ON style_class_usage(unresolvable);
1181
-
1182
- -- Spec 45 R5: Stylesheet sources the style indexer could not read.
1183
- -- When any rows exist, styles/undefined-class findings carry them as
1184
- -- incomplete-definition context instead of going silent.
1185
- CREATE TABLE IF NOT EXISTS style_unread_sources (
1186
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1187
- file_path TEXT NOT NULL UNIQUE,
1188
- reason TEXT NOT NULL,
1189
- created_at TEXT DEFAULT (datetime('now'))
1190
- );
1191
-
1192
- -- Spec 12: Convention mining
1193
- CREATE TABLE IF NOT EXISTS conventions (
1194
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1195
- domain TEXT NOT NULL,
1196
- rule_id TEXT NOT NULL,
1197
- antecedent TEXT,
1198
- consequent TEXT,
1199
- pattern TEXT,
1200
- directory TEXT,
1201
- file_path TEXT,
1202
- line INTEGER,
1203
- support INTEGER DEFAULT 0,
1204
- total_cases INTEGER DEFAULT 0,
1205
- confidence REAL DEFAULT 0,
1206
- exemplar_file TEXT,
1207
- exemplar_line INTEGER,
1208
- export_kind TEXT,
1209
- hash TEXT,
1210
- created_at TEXT DEFAULT (datetime('now'))
1211
- );
1212
- CREATE INDEX IF NOT EXISTS idx_conv_domain ON conventions(domain);
1213
- CREATE INDEX IF NOT EXISTS idx_conv_rule_id ON conventions(rule_id);
1214
- CREATE INDEX IF NOT EXISTS idx_conv_directory ON conventions(directory);
1215
- CREATE INDEX IF NOT EXISTS idx_conv_file_path ON conventions(file_path);
1216
- CREATE INDEX IF NOT EXISTS idx_conv_hash ON conventions(hash);
1217
-
1218
- -- Spec 13: Hotspots & temporal analysis
1219
- CREATE TABLE IF NOT EXISTS file_churn (
1220
- file_path TEXT PRIMARY KEY,
1221
- commit_count INTEGER NOT NULL DEFAULT 0,
1222
- lines_added INTEGER NOT NULL DEFAULT 0,
1223
- lines_deleted INTEGER NOT NULL DEFAULT 0,
1224
- distinct_authors INTEGER NOT NULL DEFAULT 0,
1225
- dominant_author TEXT,
1226
- dominant_author_share REAL DEFAULT 0,
1227
- last_touched TEXT
1228
- );
1229
- CREATE INDEX IF NOT EXISTS idx_file_churn_cc ON file_churn(commit_count);
1230
-
1231
- CREATE TABLE IF NOT EXISTS function_churn (
1232
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1233
- function_id INTEGER REFERENCES functions(id) ON DELETE CASCADE,
1234
- function_name TEXT NOT NULL,
1235
- file_path TEXT NOT NULL,
1236
- commit_count INTEGER NOT NULL DEFAULT 0,
1237
- distinct_authors INTEGER NOT NULL DEFAULT 0,
1238
- dominant_author TEXT,
1239
- dominant_author_share REAL DEFAULT 0,
1240
- renamed INTEGER DEFAULT 0,
1241
- confidence REAL DEFAULT 1.0
1242
- );
1243
- CREATE INDEX IF NOT EXISTS idx_func_churn_fid ON function_churn(function_id);
1244
-
1245
- CREATE TABLE IF NOT EXISTS hotspot_scores (
1246
- target TEXT PRIMARY KEY,
1247
- type TEXT NOT NULL,
1248
- score REAL NOT NULL DEFAULT 0,
1249
- churn_pct REAL NOT NULL DEFAULT 0,
1250
- complexity_pct REAL NOT NULL DEFAULT 0,
1251
- commit_count INTEGER NOT NULL DEFAULT 0,
1252
- distinct_authors INTEGER NOT NULL DEFAULT 0,
1253
- dominant_author TEXT,
1254
- dominant_author_share REAL DEFAULT 0,
1255
- bus_factor_risk INTEGER DEFAULT 0,
1256
- complexity INTEGER NOT NULL DEFAULT 0,
1257
- updated_at TEXT DEFAULT (datetime('now'))
1258
- );
1259
- CREATE INDEX IF NOT EXISTS idx_hotspot_scores_score ON hotspot_scores(score DESC);
1260
-
1261
- CREATE TABLE IF NOT EXISTS dry_pair_history (
1262
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1263
- pair_fingerprint TEXT NOT NULL,
1264
- file1 TEXT NOT NULL,
1265
- symbol1 TEXT,
1266
- line1 INTEGER NOT NULL,
1267
- content_hash1 TEXT NOT NULL,
1268
- file2 TEXT NOT NULL,
1269
- symbol2 TEXT,
1270
- line2 INTEGER NOT NULL,
1271
- content_hash2 TEXT NOT NULL,
1272
- similarity REAL NOT NULL,
1273
- timestamp TEXT DEFAULT (datetime('now')),
1274
- run_id TEXT NOT NULL
1275
- );
1276
- CREATE INDEX IF NOT EXISTS idx_dph_fingerprint ON dry_pair_history(pair_fingerprint);
1277
- CREATE INDEX IF NOT EXISTS idx_dph_run ON dry_pair_history(run_id);
1278
- `);
1279
- // Run schema migrations
1280
- this.runMigrations();
1281
- // Record schema version
1282
- this.db.prepare(`INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)`).run(String(CodeIndexDB.SCHEMA_VERSION));
1283
- }
1284
- // ── LokiJS migration ────────────────────────────────────────────────
1285
- maybeMigrateFromLokiJS() {
1286
- if (this.dbPath === ':memory:')
1287
- return { migrated: false };
1288
- const bakPath = this.dbPath + '.loki.bak';
1289
- // If backup already exists alongside a valid SQLite db, skip
1290
- try {
1291
- const st = require('fs').statSync(bakPath);
1292
- if (st.isFile()) {
1293
- // Check if SQLite db exists
1294
- try {
1295
- require('fs').statSync(this.dbPath);
1296
- return { migrated: false };
1297
- }
1298
- catch { /* fall through */ }
1299
- }
1300
- }
1301
- catch { /* no backup */ }
1302
- // Check if dbPath is a LokiJS file
1303
- try {
1304
- const raw = require('fs').readFileSync(this.dbPath, 'utf-8');
1305
- if (!raw.startsWith('{"filename":') && !raw.includes('"collections":')) {
1306
- return { migrated: false };
1307
- }
1308
- }
1309
- catch {
1310
- return { migrated: false };
1311
- }
1312
- // It's LokiJS — migrate
1313
- try {
1314
- const content = require('fs').readFileSync(this.dbPath, 'utf-8');
1315
- const data = JSON.parse(content);
1316
- // LokiJS stores collections as an array of {name, data, ...}
1317
- const collArray = Array.isArray(data.collections)
1318
- ? data.collections
1319
- : [];
1320
- const collections = {};
1321
- for (const c of collArray) {
1322
- collections[c.name] = c.data ?? [];
1323
- }
1324
- // Rename old LokiJS file FIRST, then create fresh SQLite DB
1325
- require('fs').renameSync(this.dbPath, bakPath);
1326
- const migDb = openSqlite(this.dbPath, { timeoutMs: DB_BUSY_TIMEOUT_MS });
1327
- migDb.pragma('journal_mode = WAL');
1328
- migDb.pragma('foreign_keys = ON');
1329
- // User-authored tables only (function index gets rebuilt by sync).
1330
- // whitelist + analyzer_configs use snake_case (matching raw SQL queries in the main code).
1331
- // project_tasks uses camelCase (matching ProjectTaskRepository document fields).
1332
- migDb.exec(`
1333
- CREATE TABLE IF NOT EXISTS whitelist (
1334
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1335
- name TEXT NOT NULL,
1336
- type TEXT NOT NULL,
1337
- status TEXT DEFAULT 'Active',
1338
- category TEXT,
1339
- description TEXT,
1340
- patterns TEXT,
1341
- added_by TEXT DEFAULT 'system',
1342
- added_at TEXT DEFAULT (datetime('now')),
1343
- updated_at TEXT,
1344
- metadata_json TEXT
1345
- );
1346
-
1347
- CREATE TABLE IF NOT EXISTS analyzer_configs (
1348
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1349
- analyzer_name TEXT NOT NULL,
1350
- project_path TEXT,
1351
- is_global INTEGER DEFAULT 0,
1352
- config_json TEXT NOT NULL DEFAULT '{}',
1353
- version TEXT,
1354
- created_by TEXT DEFAULT 'system',
1355
- created_at TEXT DEFAULT (datetime('now')),
1356
- updated_at TEXT DEFAULT (datetime('now')),
1357
- metadata_json TEXT
1358
- );
1359
-
1360
- CREATE TABLE IF NOT EXISTS project_tasks (
1361
- taskId TEXT PRIMARY KEY,
1362
- projectPath TEXT NOT NULL,
1363
- title TEXT NOT NULL,
1364
- description TEXT DEFAULT '',
1365
- status TEXT DEFAULT 'pending',
1366
- priority TEXT DEFAULT 'medium',
1367
- labels TEXT DEFAULT '[]',
1368
- source TEXT DEFAULT 'manual',
1369
- parentTaskId TEXT,
1370
- blockedBy TEXT DEFAULT '[]',
1371
- dueAt TEXT,
1372
- sortOrder INTEGER DEFAULT 0,
1373
- relatedFiles TEXT DEFAULT '[]',
1374
- relatedSymbols TEXT DEFAULT '[]',
1375
- fingerprint TEXT,
1376
- metadata TEXT DEFAULT '{}',
1377
- createdAt TEXT DEFAULT (datetime('now')),
1378
- updatedAt TEXT DEFAULT (datetime('now')),
1379
- completedAt TEXT
1380
- );
1381
- CREATE INDEX IF NOT EXISTS idx_project_tasks_fingerprint ON project_tasks(fingerprint);
1382
- `);
1383
- let taskCount = 0;
1384
- let configCount = 0;
1385
- let whitelistCount = 0;
1386
- // Migrate tasks
1387
- const tasksData = collections['projectTasks'] ?? [];
1388
- const insertTask = migDb.prepare(`INSERT OR IGNORE INTO project_tasks
1389
- (taskId, projectPath, title, description, status, priority, labels, source,
1390
- parentTaskId, blockedBy, dueAt, sortOrder, relatedFiles, relatedSymbols,
1391
- fingerprint, metadata, createdAt, updatedAt, completedAt)
1392
- VALUES (@taskId, @projectPath, @title, @description, @status, @priority, @labels, @source,
1393
- @parentTaskId, @blockedBy, @dueAt, @sortOrder, @relatedFiles, @relatedSymbols,
1394
- @fingerprint, @metadata, @createdAt, @updatedAt, @completedAt)`);
1395
- for (const t of tasksData) {
1396
- insertTask.run({
1397
- taskId: t.taskId ?? '',
1398
- projectPath: t.projectPath ?? '',
1399
- title: t.title ?? '',
1400
- description: t.description ?? '',
1401
- status: t.status ?? 'pending',
1402
- priority: t.priority ?? 'medium',
1403
- labels: JSON.stringify(t.labels ?? []),
1404
- source: t.source ?? 'manual',
1405
- parentTaskId: t.parentTaskId ?? null,
1406
- blockedBy: JSON.stringify(t.blockedBy ?? []),
1407
- dueAt: t.dueAt ?? null,
1408
- sortOrder: t.sortOrder ?? 0,
1409
- relatedFiles: JSON.stringify(t.relatedFiles ?? []),
1410
- relatedSymbols: JSON.stringify(t.relatedSymbols ?? []),
1411
- fingerprint: t.fingerprint ?? null,
1412
- metadata: JSON.stringify(t.metadata ?? t.metadata_json ?? {}),
1413
- createdAt: t.createdAt ?? t.created_at ?? new Date().toISOString(),
1414
- updatedAt: t.updatedAt ?? t.updated_at ?? new Date().toISOString(),
1415
- completedAt: t.completedAt ?? t.completed_at ?? null,
1416
- });
1417
- taskCount++;
1418
- }
1419
- // Migrate analyzer configs (snake_case columns matching createSchema)
1420
- const configsData = collections['analyzerConfigs'] ?? [];
1421
- const insertConfig = migDb.prepare(`INSERT OR IGNORE INTO analyzer_configs
1422
- (analyzer_name, project_path, is_global, config_json, version, created_by, created_at, updated_at, metadata_json)
1423
- VALUES (@analyzerName, @projectPath, @isGlobal, @configJson, @version, @createdBy, @createdAt, @updatedAt, @metadataJson)`);
1424
- for (const c of configsData) {
1425
- insertConfig.run({
1426
- analyzerName: c.analyzerName ?? '',
1427
- projectPath: c.projectPath ?? null,
1428
- isGlobal: c.isGlobal ? 1 : 0,
1429
- configJson: typeof c.config_json === 'string'
1430
- ? c.config_json
1431
- : JSON.stringify(c.config ?? c.config_json ?? {}),
1432
- version: c.version ?? null,
1433
- createdBy: c.createdBy ?? c.created_by ?? 'system',
1434
- createdAt: c.createdAt ?? c.created_at ?? new Date().toISOString(),
1435
- updatedAt: c.updatedAt ?? c.updated_at ?? new Date().toISOString(),
1436
- metadataJson: JSON.stringify(c.metadata ?? c.metadata_json ?? {}),
1437
- });
1438
- configCount++;
1439
- }
1440
- // Migrate whitelist (snake_case columns matching createSchema)
1441
- const whitelistData = collections['whitelist'] ?? [];
1442
- const insertWl = migDb.prepare(`INSERT OR IGNORE INTO whitelist
1443
- (name, type, status, category, description, patterns, added_by, added_at, updated_at, metadata_json)
1444
- VALUES (@name, @type, @status, @category, @description, @patterns, @addedBy, @addedAt, @updatedAt, @metadataJson)`);
1445
- for (const w of whitelistData) {
1446
- insertWl.run({
1447
- name: w.name ?? '',
1448
- type: w.type ?? 'PlatformAPI',
1449
- status: w.status ?? 'Active',
1450
- category: w.category ?? null,
1451
- description: w.description ?? null,
1452
- patterns: JSON.stringify(w.patterns ?? []),
1453
- addedBy: w.addedBy ?? w.added_by ?? 'system',
1454
- addedAt: w.addedAt ?? w.added_at ?? new Date().toISOString(),
1455
- updatedAt: w.updatedAt ?? w.updated_at ?? null,
1456
- metadataJson: JSON.stringify(w.metadata ?? w.metadata_json ?? {}),
1457
- });
1458
- whitelistCount++;
1459
- }
1460
- migDb.close();
1461
- const countLine = [
1462
- taskCount && `${taskCount} tasks`,
1463
- configCount && `${configCount} analyzer configs`,
1464
- whitelistCount && `${whitelistCount} whitelist entries`,
1465
- ].filter(Boolean).join(', ') || '0 entries';
1466
- console.error(`[code-auditor] Migrated ${countLine} from LokiJS to SQLite. Old file saved as ${path.basename(bakPath)}`);
1467
- return { migrated: true, counts: { tasks: taskCount, configs: configCount, whitelist: whitelistCount } };
1468
- }
1469
- catch (err) {
1470
- // Migration failed — try to restore the backup
1471
- console.error('[code-auditor] LokiJS migration failed:', err instanceof Error ? err.message : String(err));
1472
- try {
1473
- if (require('fs').existsSync(bakPath) && !require('fs').existsSync(this.dbPath)) {
1474
- require('fs').renameSync(bakPath, this.dbPath);
1475
- }
1476
- }
1477
- catch { /* best effort */ }
1478
- return { migrated: false };
1479
- }
1480
- }
1481
318
  // ── Init guard ──────────────────────────────────────────────────────
1482
319
  ensureInitialized() {
1483
320
  if (!this.isInitialized) {
@@ -1489,174 +326,18 @@ export class CodeIndexDB {
1489
326
  throw new Error('Database not initialized. Call initialize() first.');
1490
327
  }
1491
328
  }
1492
- // ── Helpers: row → EnhancedFunctionMetadata ─────────────────────────
1493
- rowToFunction(row) {
1494
- return {
1495
- name: row.name,
1496
- filePath: row.file_path,
1497
- lineNumber: row.line_number,
1498
- startLine: row.start_line,
1499
- endLine: row.end_line,
1500
- language: row.language,
1501
- dependencies: [],
1502
- purpose: row.purpose ?? '',
1503
- context: row.context ?? '',
1504
- signature: row.signature ?? '',
1505
- parameters: tryParseJson(row.parameters) ?? [],
1506
- jsDoc: { description: row.jsdoc_description ?? '' },
1507
- imports: [],
1508
- body: row.body ?? '',
1509
- comments: [],
1510
- isAsync: false,
1511
- isGenerator: false,
1512
- returnType: row.return_type,
1513
- visibility: 'public',
1514
- complexity: row.complexity ?? 0,
1515
- content_hash: row.content_hash,
1516
- metadata: tryParseJson(row.metadata_json) ?? {},
1517
- hooks: tryParseJson(row.hooks),
1518
- props: tryParseJson(row.props),
1519
- };
1520
- }
1521
- functionToRow(func, lastModified) {
1522
- const enhanced = func;
1523
- const jsDoc = func.jsDoc;
1524
- return {
1525
- name: func.name,
1526
- file_path: func.filePath,
1527
- line_number: func.lineNumber ?? 0,
1528
- start_line: func.startLine ?? null,
1529
- end_line: func.endLine ?? null,
1530
- language: func.language ?? 'typescript',
1531
- entity_type: func.metadata?.entityType ?? 'function',
1532
- component_type: func.metadata?.componentType ?? null,
1533
- signature: enhanced.signature ?? '',
1534
- return_type: enhanced.returnType ?? null,
1535
- complexity: enhanced.complexity ?? func.metadata?.complexity ?? 0,
1536
- is_exported: func.metadata?.isExported ? 1 : 0,
1537
- has_jsdoc: (jsDoc && jsDoc.description) ? 1 : 0,
1538
- jsdoc_description: typeof jsDoc === 'string' ? jsDoc : (jsDoc?.description ?? ''),
1539
- jsdoc_tags: jsDoc?.tags ? JSON.stringify(jsDoc.tags) : null,
1540
- parameters: enhanced.parameters ? JSON.stringify(enhanced.parameters) : null,
1541
- type_info: func.metadata?.typeInfo ? JSON.stringify(func.metadata.typeInfo) : null,
1542
- hooks: enhanced.hooks ? JSON.stringify(enhanced.hooks) : null,
1543
- props: enhanced.props ? JSON.stringify(enhanced.props) : null,
1544
- used_imports: func.metadata?.usedImports ? JSON.stringify(func.metadata.usedImports) : null,
1545
- unused_imports: func.metadata?.unusedImports ? JSON.stringify(func.metadata.unusedImports) : null,
1546
- import_usage: func.metadata?.importUsage ? JSON.stringify(func.metadata.importUsage) : null,
1547
- has_unused_imports: func.metadata?.unusedImports?.length > 0 ? 1 : 0,
1548
- dependency_depth: func.metadata?.dependencyDepth ?? 0,
1549
- purpose: func.purpose ?? '',
1550
- context: func.context ?? '',
1551
- body: func.body ?? null,
1552
- content_hash: enhanced.content_hash ?? computeContentHash(func.body, enhanced.signature),
1553
- last_modified: lastModified ?? new Date().toISOString(),
1554
- // body lives only in the dedicated `body` column, never in metadata_json
1555
- // (previously triple-stored). JSON.stringify omits the undefined value, so
1556
- // this strips any stray `body` key at the write boundary.
1557
- metadata_json: func.metadata ? JSON.stringify({ ...func.metadata, body: undefined }) : '{}',
1558
- };
1559
- }
1560
- // ── Function CRUD ───────────────────────────────────────────────────
1561
- async registerFunction(func) {
1562
- this.ensureInitialized();
1563
- const row = this.functionToRow(func);
1564
- try {
1565
- this.db.prepare(`
1566
- INSERT INTO functions (name, file_path, line_number, start_line, end_line, language,
1567
- entity_type, component_type, signature, return_type, complexity, is_exported, has_jsdoc,
1568
- jsdoc_description, jsdoc_tags, parameters, type_info, hooks, props,
1569
- used_imports, unused_imports, import_usage, has_unused_imports, dependency_depth,
1570
- purpose, context, body, content_hash, last_modified, metadata_json)
1571
- VALUES (@name, @file_path, @line_number, @start_line, @end_line, @language,
1572
- @entity_type, @component_type, @signature, @return_type, @complexity, @is_exported, @has_jsdoc,
1573
- @jsdoc_description, @jsdoc_tags, @parameters, @type_info, @hooks, @props,
1574
- @used_imports, @unused_imports, @import_usage, @has_unused_imports, @dependency_depth,
1575
- @purpose, @context, @body, @content_hash, @last_modified, @metadata_json)
1576
- ON CONFLICT(name, file_path, line_number) DO UPDATE SET
1577
- line_number=excluded.line_number, start_line=excluded.start_line, end_line=excluded.end_line,
1578
- language=excluded.language, entity_type=excluded.entity_type, component_type=excluded.component_type,
1579
- signature=excluded.signature, return_type=excluded.return_type, complexity=excluded.complexity,
1580
- is_exported=excluded.is_exported, has_jsdoc=excluded.has_jsdoc,
1581
- jsdoc_description=excluded.jsdoc_description, jsdoc_tags=excluded.jsdoc_tags,
1582
- parameters=excluded.parameters, type_info=excluded.type_info, hooks=excluded.hooks, props=excluded.props,
1583
- used_imports=excluded.used_imports, unused_imports=excluded.unused_imports,
1584
- import_usage=excluded.import_usage, has_unused_imports=excluded.has_unused_imports,
1585
- dependency_depth=excluded.dependency_depth,
1586
- purpose=excluded.purpose, context=excluded.context, body=excluded.body,
1587
- content_hash=excluded.content_hash, last_modified=excluded.last_modified,
1588
- metadata_json=excluded.metadata_json
1589
- `).run(row);
1590
- }
1591
- catch (error) {
1592
- throw new Error(`Failed to register function: ${error instanceof Error ? error.message : 'Unknown error'}`);
1593
- }
1594
- }
1595
- async registerFunctions(functions) {
1596
- this.ensureInitialized();
1597
- let registered = 0;
1598
- let failed = 0;
1599
- const errors = [];
1600
- const insertAll = this.db.transaction((funcs) => {
1601
- for (const func of funcs) {
1602
- try {
1603
- this.registerFunction(func);
1604
- registered++;
1605
- }
1606
- catch (error) {
1607
- failed++;
1608
- errors.push({
1609
- function: func.name || 'unknown',
1610
- error: error instanceof Error ? error.message : 'Unknown error'
1611
- });
1612
- }
1613
- }
1614
- });
1615
- insertAll(functions);
1616
- return {
1617
- success: failed === 0,
1618
- registered,
1619
- failed,
1620
- errors: errors
1621
- };
1622
- }
329
+ // ── Function sync (orchestration) ────────────────────────────────────
1623
330
  /**
1624
- * Upsert/remove one file's function rows. Runs inside the caller's
1625
- * transaction (no nested `db.transaction` here) so a batch sync can wrap many
1626
- * files in a single write transaction.
331
+ * Upsert one file's functions and remove rows that no longer exist, then
332
+ * rebuild the dependency graph for that file.
333
+ *
334
+ * @param filePath - The file whose functions are being synced.
335
+ * @param currentFunctions - The functions currently present in the file.
336
+ * @param fileHash - Optional file-level content hash stamped onto each written
337
+ * row for diff detection.
338
+ * @returns Counts of added, updated, and removed rows.
1627
339
  */
1628
- syncFileIndexRow(filePath, currentFunctions, stats, lastModified) {
1629
- const existing = this.db.prepare('SELECT id, name, file_path, line_number FROM functions WHERE file_path = ?').all(filePath);
1630
- const createKey = (f) => `${f.name ?? f.name}:${f.filePath ?? f.file_path}:${f.lineNumber ?? f.line_number}`;
1631
- const currentMap = new Map(currentFunctions.map(f => [createKey(f), f]));
1632
- // Insert/update current functions
1633
- for (const func of currentFunctions) {
1634
- const exists = existing.find(e => e.name === func.name && e.line_number === func.lineNumber);
1635
- if (exists) {
1636
- const row = this.functionToRow(func, lastModified);
1637
- const keys = Object.keys(row);
1638
- const sets = keys.filter(k => k !== 'name' && k !== 'file_path').map(k => `"${k}" = @${k}`);
1639
- const params = { ...row, _id: exists.id };
1640
- this.db.prepare(`UPDATE functions SET ${sets.join(', ')} WHERE id = @_id`).run(params);
1641
- stats.updated++;
1642
- }
1643
- else {
1644
- const row = this.functionToRow(func, lastModified);
1645
- const keys = Object.keys(row);
1646
- const sql = `INSERT INTO functions ("${keys.join('", "')}") VALUES (${keys.map(k => '@' + k).join(', ')})`;
1647
- this.db.prepare(sql).run(row);
1648
- stats.added++;
1649
- }
1650
- }
1651
- // Remove stale functions
1652
- for (const e of existing) {
1653
- if (!currentMap.has(createKey(e))) {
1654
- this.db.prepare('DELETE FROM functions WHERE id = ?').run(e.id);
1655
- stats.removed++;
1656
- }
1657
- }
1658
- }
1659
- async syncFileIndex(filePath, currentFunctions) {
340
+ async syncFileIndex(filePath, currentFunctions, fileHash) {
1660
341
  this.ensureInitialized();
1661
342
  const stats = { added: 0, updated: 0, removed: 0 };
1662
343
  // Record the file's actual mtime (not wall clock) so detectModifiedFiles can
@@ -1670,8 +351,8 @@ export class CodeIndexDB {
1670
351
  // stat failed — fall back to wall clock; detectModifiedFiles will then treat
1671
352
  // an unreadable file as modified, which is the safe direction.
1672
353
  }
1673
- this.db.transaction(() => this.syncFileIndexRow(filePath, currentFunctions, stats, lastModified)).immediate();
1674
- await this.updateDependencyGraph(filePath);
354
+ this.db.transaction(() => this.functionIndex.syncFileIndexRow(filePath, currentFunctions, stats, lastModified, fileHash)).immediate();
355
+ await this.graph.updateDependencyGraph(filePath);
1675
356
  return stats;
1676
357
  }
1677
358
  /**
@@ -1681,6 +362,9 @@ export class CodeIndexDB {
1681
362
  * one transaction plus one dependency-graph rebuild per file, and the
1682
363
  * rebuild's un-scoped `SELECT` made that O(files × functions). Collapsing it
1683
364
  * to one transaction is Amendment B2.
365
+ *
366
+ * @param entries - The per-file sync entries (path plus current functions).
367
+ * @returns Counts of added, updated, and removed rows across all files.
1684
368
  */
1685
369
  async syncFileIndexBatch(entries) {
1686
370
  this.ensureInitialized();
@@ -1697,360 +381,69 @@ export class CodeIndexDB {
1697
381
  }
1698
382
  }));
1699
383
  this.db.transaction(() => {
1700
- for (const { filePath, currentFunctions } of entries) {
1701
- this.syncFileIndexRow(filePath, currentFunctions, stats, mtimes.get(filePath));
384
+ for (const { filePath, currentFunctions, fileHash } of entries) {
385
+ this.functionIndex.syncFileIndexRow(filePath, currentFunctions, stats, mtimes.get(filePath), fileHash);
1702
386
  }
1703
387
  }).immediate();
1704
- await this.updateDependencyGraph();
388
+ await this.graph.updateDependencyGraph();
1705
389
  return stats;
1706
390
  }
1707
- // ── Dependency graph ────────────────────────────────────────────────
1708
- async updateDependencyGraph(filePath) {
1709
- this.ensureInitialized();
1710
- const txn = this.db.transaction(() => {
1711
- // Clear existing call edges for scoped functions
1712
- if (filePath) {
1713
- this.db.prepare(`DELETE FROM function_calls WHERE caller_id IN (SELECT id FROM functions WHERE file_path = ?)`).run(filePath);
1714
- }
1715
- else {
1716
- this.db.prepare('DELETE FROM function_calls').run();
1717
- }
1718
- // Clear existing dependency edges for scoped functions
1719
- if (filePath) {
1720
- this.db.prepare(`DELETE FROM function_dependencies WHERE function_id IN (SELECT id FROM functions WHERE file_path = ?)`).run(filePath);
1721
- }
1722
- else {
1723
- this.db.prepare('DELETE FROM function_dependencies').run();
1724
- }
1725
- // Rebuild edges from metadata. When scoped to one file, read only that
1726
- // file's functions — a per-file call previously re-read every function
1727
- // in the index, making the detached-audit loop O(files × functions).
1728
- const allFns = filePath
1729
- ? this.db.prepare('SELECT id, name, file_path, metadata_json FROM functions WHERE file_path = ?').all(filePath)
1730
- : this.db.prepare('SELECT id, name, file_path, metadata_json FROM functions').all();
1731
- const insertCall = this.db.prepare('INSERT OR IGNORE INTO function_calls (caller_id, callee_name) VALUES (?, ?)');
1732
- const insertFnDep = this.db.prepare('INSERT OR IGNORE INTO function_dependencies (function_id, dependency) VALUES (?, ?)');
1733
- for (const fn of allFns) {
1734
- const meta = tryParseJson(fn.metadata_json) ?? {};
1735
- if (meta.functionCalls) {
1736
- for (const callee of meta.functionCalls) {
1737
- insertCall.run(fn.id, callee);
1738
- }
1739
- }
1740
- // Add specifier-level dependencies (e.g., useState, useEffect)
1741
- const usedImports = meta.usedImports ?? [];
1742
- for (const imp of usedImports) {
1743
- insertFnDep.run(fn.id, imp);
1744
- }
1745
- // Add module-level dependencies (e.g., react, express) — stored in
1746
- // metadata.dependencies since v3.0.4 to power the dep: operator
1747
- const moduleDeps = meta.dependencies ?? [];
1748
- for (const dep of moduleDeps) {
1749
- insertFnDep.run(fn.id, dep);
1750
- }
1751
- }
1752
- });
1753
- txn.immediate();
1754
- }
1755
- async getTransitiveDependencies(functionName, maxDepth = 10) {
1756
- this.ensureInitialized();
1757
- const result = [];
1758
- const visited = new Set();
1759
- // Find starting function(s)
1760
- const startFns = this.db.prepare('SELECT id FROM functions WHERE name = ?').all(functionName);
1761
- if (startFns.length === 0)
1762
- return result;
1763
- // Use recursive CTE for transitive closure
1764
- const rows = this.db.prepare(`
1765
- WITH RECURSIVE deps(id, callee_name, depth) AS (
1766
- SELECT fc.caller_id, fc.callee_name, 1
1767
- FROM function_calls fc
1768
- WHERE fc.caller_id IN (SELECT id FROM functions WHERE name = ?)
1769
- UNION
1770
- SELECT fc.caller_id, fc.callee_name, deps.depth + 1
1771
- FROM function_calls fc
1772
- JOIN deps ON fc.caller_id IN (SELECT id FROM functions WHERE name = deps.callee_name)
1773
- WHERE deps.depth < ?
1774
- )
1775
- SELECT DISTINCT callee_name as name, depth FROM deps ORDER BY depth, name
1776
- `).all(functionName, maxDepth);
1777
- return rows;
1778
- }
1779
- async getTransitiveCallers(functionName, maxDepth = 10) {
1780
- this.ensureInitialized();
1781
- const rows = this.db.prepare(`
1782
- WITH RECURSIVE callers(id, caller_name, depth) AS (
1783
- SELECT fc.caller_id, f.name, 1
1784
- FROM function_calls fc
1785
- JOIN functions f ON f.id = fc.caller_id
1786
- WHERE fc.callee_name = ?
1787
- UNION
1788
- SELECT fc.caller_id, f2.name, callers.depth + 1
1789
- FROM function_calls fc
1790
- JOIN functions f2 ON f2.id = fc.caller_id
1791
- JOIN functions f3 ON f3.name = callers.caller_name AND fc.callee_name = f3.name
1792
- WHERE callers.depth < ?
1793
- )
1794
- SELECT DISTINCT caller_name as name, depth FROM callers ORDER BY depth, name
1795
- `).all(functionName, maxDepth);
1796
- return rows;
1797
- }
1798
- async detectCircularDependencies() {
1799
- this.ensureInitialized();
1800
- // SQLite recursive CTE for cycle detection
1801
- const rows = this.db.prepare(`
1802
- WITH RECURSIVE paths(start_name, path, current_name, depth) AS (
1803
- SELECT f.name, f.name, fc.callee_name, 1
1804
- FROM functions f
1805
- JOIN function_calls fc ON fc.caller_id = f.id
1806
- UNION
1807
- SELECT paths.start_name,
1808
- paths.path || '→' || fc.callee_name,
1809
- fc.callee_name,
1810
- paths.depth + 1
1811
- FROM paths
1812
- JOIN functions f ON f.name = paths.current_name
1813
- JOIN function_calls fc ON fc.caller_id = f.id
1814
- WHERE paths.depth < 20
1815
- AND instr(paths.path, fc.callee_name) = 0
1816
- )
1817
- SELECT DISTINCT path FROM paths
1818
- WHERE current_name = start_name AND depth > 1
1819
- `).all();
1820
- return rows.map(r => r.path.split('→'));
1821
- }
1822
- async calculateDependencyDepths() {
1823
- this.ensureInitialized();
1824
- const functions = this.db.prepare('SELECT id, name FROM functions').all();
1825
- const update = this.db.prepare('UPDATE functions SET dependency_depth = ? WHERE id = ?');
1826
- // Calculate depths outside transaction since getTransitiveDependencies is async
1827
- const depths = [];
1828
- for (const fn of functions) {
1829
- const deps = await this.getTransitiveDependencies(fn.name);
1830
- const maxDepth = deps.length > 0 ? Math.max(...deps.map(d => d.depth)) : 0;
1831
- depths.push({ id: fn.id, maxDepth });
1832
- }
1833
- const txn = this.db.transaction(() => {
1834
- for (const { id, maxDepth } of depths) {
1835
- update.run(maxDepth, id);
1836
- }
1837
- });
1838
- txn();
1839
- }
1840
- // ── Search ──────────────────────────────────────────────────────────
1841
- async searchFunctions(options) {
1842
- this.ensureInitialized();
1843
- const startTime = Date.now();
1844
- const queryParser = new QueryParser();
1845
- let parsedQuery;
1846
- if (options.query) {
1847
- parsedQuery = options.parsedQuery || queryParser.parse(options.query);
1848
- }
1849
- else if (options.parsedQuery) {
1850
- parsedQuery = options.parsedQuery;
1851
- }
1852
- // If there's a parsed query, use the SQL path (handles both FTS5 and operator-only queries)
1853
- if (parsedQuery) {
1854
- const compiled = compileToSQL(parsedQuery, { defaultLimit: options.limit || 50, offset: options.offset || 0 });
1855
- return this.executeCompiledSearch(compiled, parsedQuery, options, startTime);
1856
- }
1857
- // Otherwise, get all functions and apply filters in-memory (backward compatible)
1858
- const rows = this.db.prepare('SELECT *, rowid as "$loki" FROM functions').all();
1859
- let results = rows.map((r) => this.rowToFunctionDoc(r));
1860
- // Apply filters (parsedQuery is undefined here — the if-parsedQuery branch returned early)
1861
- const combinedFilters = this.mergeFilters(options.filters, undefined);
1862
- results = this.applyFilters(results, combinedFilters);
1863
- const totalCount = results.length;
1864
- const limit = options.limit || 50;
1865
- const offset = options.offset || 0;
1866
- results = results.slice(offset, offset + limit);
1867
- const functions = results.map(doc => ({
1868
- ...doc,
1869
- score: 50
1870
- }));
1871
- return {
1872
- functions,
1873
- totalCount,
1874
- query: options.query,
1875
- parsedQuery,
1876
- executionTime: Date.now() - startTime
1877
- };
1878
- }
1879
- executeCompiledSearch(compiled, parsedQuery, options, startTime) {
1880
- let sql;
1881
- const params = { ...compiled.params };
1882
- let afterFrom = '';
1883
- // Collect JOINs first — they must precede WHERE in SQL
1884
- for (const join of compiled.joinClauses) {
1885
- afterFrom += ` ${join}`;
1886
- }
1887
- if (compiled.ftsMatch) {
1888
- // FTS5 path
1889
- sql = `SELECT f.*, f.rowid as "$loki", bm25(functions_fts) as score
1890
- FROM functions f
1891
- JOIN functions_fts ON functions_fts.rowid = f.id${afterFrom}
1892
- WHERE functions_fts MATCH @_ftsMatch`;
1893
- params['_ftsMatch'] = compiled.ftsMatch;
1894
- }
1895
- else {
1896
- sql = `SELECT f.*, f.rowid as "$loki", 0 as score FROM functions f${afterFrom} WHERE 1=1`;
1897
- }
1898
- // Add WHERE clauses
1899
- for (const clause of compiled.whereClauses) {
1900
- sql += ` AND (${clause})`;
1901
- }
1902
- // Ordering
1903
- sql += ` ORDER BY ${compiled.orderBy}`;
1904
- // Limit/offset
1905
- sql += ` LIMIT ${compiled.limit} OFFSET ${compiled.offset}`;
1906
- const rows = this.db.prepare(sql).all(params);
1907
- // Get total count (without limit)
1908
- let totalCount = rows.length;
1909
- try {
1910
- let countSql;
1911
- let countAfterFrom = '';
1912
- for (const join of compiled.joinClauses) {
1913
- countAfterFrom += ` ${join}`;
1914
- }
1915
- if (compiled.ftsMatch) {
1916
- countSql = `SELECT COUNT(*) as cnt FROM functions f
1917
- JOIN functions_fts ON functions_fts.rowid = f.id${countAfterFrom}
1918
- WHERE functions_fts MATCH @_ftsMatch`;
1919
- }
1920
- else {
1921
- countSql = `SELECT COUNT(*) as cnt FROM functions f${countAfterFrom} WHERE 1=1`;
1922
- }
1923
- for (const clause of compiled.whereClauses) {
1924
- countSql += ` AND (${clause})`;
1925
- }
1926
- const countParams = { ...params };
1927
- delete countParams['_ftsMatch'];
1928
- if (compiled.ftsMatch) {
1929
- countParams['_ftsMatch'] = compiled.ftsMatch;
1930
- }
1931
- const countResult = this.db.prepare(countSql).get(countParams);
1932
- totalCount = countResult?.cnt ?? rows.length;
1933
- }
1934
- catch {
1935
- // Fall back to results length
1936
- }
1937
- // Convert rows to FunctionDocument, applying excluded terms
1938
- let docs = rows.map((r) => this.rowToFunctionDoc(r));
1939
- if (parsedQuery.excludedTerms.length > 0) {
1940
- docs = this.excludeTermsFromResults(docs, parsedQuery.excludedTerms);
1941
- }
1942
- // Apply in-memory filters (for filters not handled by compileToSQL)
1943
- const combinedFilters = this.mergeFilters(options.filters, parsedQuery.filters);
1944
- docs = this.applyFilters(docs, combinedFilters);
1945
- const functions = docs.map(doc => ({
1946
- ...doc,
1947
- score: doc.score ?? 0
1948
- }));
1949
- return {
1950
- functions,
1951
- totalCount,
1952
- query: options.query,
1953
- parsedQuery,
1954
- executionTime: Date.now() - startTime
1955
- };
1956
- }
1957
- rowToFunctionDoc(row) {
1958
- const func = this.rowToFunction(row);
1959
- const doc = {
1960
- ...func,
1961
- $loki: row['$loki'] ?? row.id,
1962
- dependencies: [],
1963
- };
1964
- // Populate dependencies from metadata
1965
- const meta = tryParseJson(row.metadata_json);
1966
- if (meta) {
1967
- // Include both specifier-level (usedImports) and module-level (dependencies)
1968
- const usedImports = meta.usedImports ?? [];
1969
- const moduleDeps = meta.dependencies ?? [];
1970
- doc.dependencies = [...new Set([...usedImports, ...moduleDeps])];
1971
- }
1972
- return doc;
1973
- }
1974
- async findDefinition(name, filePath) {
1975
- this.ensureInitialized();
1976
- let row;
1977
- if (filePath) {
1978
- row = this.db.prepare('SELECT * FROM functions WHERE name = ? AND file_path = ?').get(name, filePath);
1979
- }
1980
- else {
1981
- row = this.db.prepare('SELECT * FROM functions WHERE name = ?').get(name);
1982
- }
1983
- if (!row)
1984
- return null;
1985
- const func = this.rowToFunction(row);
1986
- return func;
1987
- }
1988
- // ── Stats ───────────────────────────────────────────────────────────
1989
- async getAllFunctions() {
1990
- this.ensureInitialized();
1991
- const rows = this.db.prepare('SELECT * FROM functions').all();
1992
- return rows.map((r) => this.rowToFunction(r));
1993
- }
1994
- /** Row count of the `functions` table — for callers that only need `.length`. */
1995
- async getFunctionCount() {
1996
- this.ensureInitialized();
1997
- const row = this.db.prepare('SELECT COUNT(*) AS n FROM functions').get();
1998
- return row?.n ?? 0;
1999
- }
2000
- async getStats() {
2001
- this.ensureInitialized();
2002
- const totalFunctions = this.db.prepare('SELECT COUNT(*) as cnt FROM functions').get().cnt;
2003
- const languages = {};
2004
- const langRows = this.db.prepare('SELECT language, COUNT(*) as cnt FROM functions GROUP BY language').all();
2005
- for (const r of langRows) {
2006
- languages[r.language] = r.cnt;
2007
- }
2008
- const depRows = this.db.prepare('SELECT dependency as name, COUNT(*) as cnt FROM function_dependencies GROUP BY dependency ORDER BY cnt DESC LIMIT 10').all();
2009
- const topDependencies = depRows.map((r) => ({ name: r.name, count: r.cnt }));
2010
- const filesIndexed = this.db.prepare('SELECT COUNT(DISTINCT file_path) as cnt FROM functions').get().cnt;
2011
- return {
2012
- totalFunctions,
2013
- languages,
2014
- topDependencies,
2015
- filesIndexed,
2016
- lastUpdated: new Date()
2017
- };
2018
- }
2019
- /** Get graph statistics — delegates to graph/callGraph.ts (Spec 14 R1). */
2020
- getGraphStats() {
2021
- this.ensureInitialized();
2022
- return getGs(this.db);
2023
- }
2024
391
  // ── Lifecycle: clear & close ────────────────────────────────────────
392
+ /**
393
+ * Delete all index data while preserving project tasks, configs, whitelist,
394
+ * and ledger rows.
395
+ *
396
+ * @returns A promise that resolves once the index is cleared.
397
+ */
2025
398
  async clearIndex() {
2026
399
  this.ensureInitialized();
400
+ // Delete every indexed table inside a single transaction. Each DELETE is a
401
+ // literal statement (no interpolation → no sql-injection), one table per
402
+ // statement (no multi-table `exec` → no complex-query), prepared once outside
403
+ // the loop and only `.run()` inside it (no prepare-in-loop → no loop-query),
404
+ // and executed through one shared loop (one query site → no too-many-queries).
405
+ // `meta` keeps the keyed subset below; the FTS5 triggers handle functions_fts
406
+ // cleanup on the functions delete. Preserved by design: project_tasks,
407
+ // analyzer_configs, whitelist, findings_ledger_runs, findings_ledger_findings.
2027
408
  this.db.transaction(() => {
2028
- this.db.prepare('DELETE FROM functions').run();
2029
- // FTS5 triggers handle cleanup
2030
- this.db.prepare('DELETE FROM audit_results').run();
2031
- this.db.prepare('DELETE FROM code_maps').run();
2032
- this.db.prepare('DELETE FROM schema_definitions').run();
2033
- this.db.prepare('DELETE FROM schema_usage').run();
2034
- this.db.prepare('DELETE FROM conventions').run();
2035
- this.db.prepare('DELETE FROM file_churn').run();
2036
- this.db.prepare('DELETE FROM function_churn').run();
2037
- this.db.prepare('DELETE FROM hotspot_scores').run();
2038
- this.db.prepare('DELETE FROM dry_pair_history').run();
2039
- this.db.prepare('DELETE FROM graph_cache').run();
2040
- this.db.prepare("DELETE FROM meta WHERE key IN ('churn_hash', 'conventions_hash', 'style_last_sync')").run();
2041
- // Preserve: project_tasks, analyzer_configs, whitelist, findings_ledger_runs, findings_ledger_findings
409
+ const deletes = [
410
+ this.db.prepare('DELETE FROM functions'),
411
+ this.db.prepare('DELETE FROM audit_results'),
412
+ this.db.prepare('DELETE FROM code_maps'),
413
+ this.db.prepare('DELETE FROM schema_definitions'),
414
+ this.db.prepare('DELETE FROM schema_usage'),
415
+ this.db.prepare('DELETE FROM conventions'),
416
+ this.db.prepare('DELETE FROM file_churn'),
417
+ this.db.prepare('DELETE FROM function_churn'),
418
+ this.db.prepare('DELETE FROM hotspot_scores'),
419
+ this.db.prepare('DELETE FROM dry_pair_history'),
420
+ this.db.prepare('DELETE FROM graph_cache'),
421
+ this.db.prepare("DELETE FROM meta WHERE key IN ('churn_hash', 'conventions_hash', 'style_last_sync')"),
422
+ ];
423
+ for (const del of deletes)
424
+ del.run();
2042
425
  })();
2043
426
  }
427
+ /**
428
+ * Close the database handle and release resources, if initialized.
429
+ *
430
+ * @returns A promise that resolves once the handle is closed.
431
+ */
2044
432
  async close() {
2045
433
  if (this.isInitialized) {
2046
- this.taskRepository = null;
2047
- this.stmts.clear();
2048
434
  this.db.close();
2049
435
  this.isInitialized = false;
2050
436
  }
2051
437
  this.initFailure = null;
2052
438
  }
2053
439
  // ── File sync & bulk cleanup ────────────────────────────────────────
440
+ /**
441
+ * Re-scan a single file and reconcile its indexed functions, removing rows
442
+ * when the file no longer exists.
443
+ *
444
+ * @param filePath - The file to synchronize.
445
+ * @returns Counts of added, updated, and removed rows (null on scan error).
446
+ */
2054
447
  async synchronizeFile(filePath) {
2055
448
  this.ensureInitialized();
2056
449
  try {
@@ -2065,17 +458,23 @@ export class CodeIndexDB {
2065
458
  const { FunctionScanner } = await import('./functionScanner.js');
2066
459
  const scanner = new FunctionScanner();
2067
460
  const fileContent = await fs.readFile(filePath, 'utf-8');
2068
- const language = filePath.endsWith('.ts') || filePath.endsWith('.tsx') ? 'typescript' : 'javascript';
2069
- const parsedFunctions = await scanner.scanFunctions(fileContent, filePath, language);
461
+ const parsedFunctions = await scanner.scanFunctions(fileContent, filePath);
2070
462
  return await this.syncFileIndex(filePath, parsedFunctions);
2071
463
  }
2072
464
  catch (error) {
2073
- throw new Error(`Failed to sync file: ${error instanceof Error ? error.message : 'Unknown error'}`);
465
+ throw new Error(`Failed to sync file: ${errorMessage(error)}`);
2074
466
  }
2075
467
  }
468
+ /**
469
+ * Remove stale function rows whose files are deleted or no longer part of
470
+ * the discovery set.
471
+ *
472
+ * @param projectRoot - Optional project root to reconcile against.
473
+ * @returns Scan counts, removed rows, and per-file errors.
474
+ */
2076
475
  async bulkCleanup(projectRoot) {
2077
476
  this.ensureInitialized();
2078
- const files = this.db.prepare('SELECT DISTINCT file_path FROM functions').all();
477
+ const files = this.db.prepare(SQL_DISTINCT_FILE_PATHS).all();
2079
478
  const removedFiles = [];
2080
479
  const errors = [];
2081
480
  let removedCount = 0;
@@ -2098,6 +497,7 @@ export class CodeIndexDB {
2098
497
  discovered = null;
2099
498
  }
2100
499
  }
500
+ const stalePaths = [];
2101
501
  for (const { file_path: fp } of files) {
2102
502
  scannedCount++;
2103
503
  let stale = false;
@@ -2115,13 +515,30 @@ export class CodeIndexDB {
2115
515
  }
2116
516
  }
2117
517
  if (stale) {
2118
- const result = this.db.prepare('DELETE FROM functions WHERE file_path = ?').run(fp);
2119
- removedCount += result.changes;
518
+ stalePaths.push(fp);
2120
519
  removedFiles.push(fp);
2121
520
  }
2122
521
  }
522
+ // Batch the deletes — one `IN (SELECT value FROM json_each(?))` DELETE for the
523
+ // whole stale set, instead of one DELETE per stale file (loop-query / N+1).
524
+ // A single JSON-array bind expands through json_each, so a large stale set
525
+ // can't exceed the SQLite bind-parameter ceiling.
526
+ if (stalePaths.length > 0) {
527
+ const result = this.db
528
+ .prepare(SQL_DELETE_FUNCTIONS_IN_FILEPATHS)
529
+ .run(JSON.stringify(stalePaths));
530
+ removedCount += result.changes;
531
+ }
2123
532
  return { scannedCount, removedCount, removedFiles, errors };
2124
533
  }
534
+ /**
535
+ * Re-scan the whole project (or the already-indexed files) and reconcile the
536
+ * index, reporting aggregate counts.
537
+ *
538
+ * @param projectRoot - Optional project root to discover files from.
539
+ * @param progressCallback - Optional per-file progress callback.
540
+ * @returns Aggregate sync counts and errors.
541
+ */
2125
542
  async deepSync(projectRoot, progressCallback) {
2126
543
  this.ensureInitialized();
2127
544
  let syncedFiles = 0;
@@ -2139,31 +556,68 @@ export class CodeIndexDB {
2139
556
  }
2140
557
  else {
2141
558
  // Fallback: sync files already in the index
2142
- const rows = this.db.prepare('SELECT DISTINCT file_path FROM functions').all();
559
+ const rows = this.db.prepare(SQL_DISTINCT_FILE_PATHS).all();
2143
560
  files = rows.map(r => r.file_path);
2144
561
  }
2145
562
  const total = files.length;
563
+ // Parse each file and defer its index write to one batched transaction plus
564
+ // one dependency-graph rebuild after the loop. The previous per-file
565
+ // `synchronizeFile` → `syncFileIndex` path was one transaction plus one
566
+ // per-file dependency-graph rebuild per file — the same O(files × functions)
567
+ // query-in-loop N+1 Amendment B2 collapsed in the detached-audit path.
568
+ // Tree-sitter parsing stays per-file (it is the inherent cost); only the DB
569
+ // write is batched. Progress and per-file error attribution are unchanged.
570
+ const syncEntries = [];
571
+ const inaccessiblePaths = [];
2146
572
  for (let i = 0; i < files.length; i++) {
2147
573
  const fp = files[i];
2148
574
  if (progressCallback) {
2149
575
  progressCallback({ current: i + 1, total, file: fp });
2150
576
  }
577
+ let accessible = true;
2151
578
  try {
2152
- const result = await this.synchronizeFile(fp);
2153
- if (result) {
2154
- syncedFiles++;
2155
- totalAdded += result.added;
2156
- totalUpdated += result.updated;
2157
- totalRemoved += result.removed;
2158
- }
579
+ await fs.access(fp);
580
+ }
581
+ catch {
582
+ accessible = false;
583
+ }
584
+ if (!accessible) {
585
+ // File doesn't exist — defer its delete to one batched DELETE after the
586
+ // loop (the same json_each batch the stale cleanup below uses), instead
587
+ // of one `DELETE … WHERE file_path = ?` per missing file (loop-query N+1).
588
+ inaccessiblePaths.push(fp);
589
+ syncedFiles++;
590
+ continue;
591
+ }
592
+ try {
593
+ const { FunctionScanner } = await import('./functionScanner.js');
594
+ const scanner = new FunctionScanner();
595
+ const fileContent = await fs.readFile(fp, 'utf-8');
596
+ const parsedFunctions = await scanner.scanFunctions(fileContent, fp);
597
+ syncEntries.push({ filePath: fp, currentFunctions: parsedFunctions });
598
+ syncedFiles++;
2159
599
  }
2160
600
  catch (error) {
2161
601
  errors.push({
2162
602
  file: fp,
2163
- error: error instanceof Error ? error.message : String(error)
603
+ error: `Failed to sync file: ${errorMessage(error)}`
2164
604
  });
2165
605
  }
2166
606
  }
607
+ if (syncEntries.length > 0) {
608
+ const batchStats = await this.syncFileIndexBatch(syncEntries);
609
+ totalAdded += batchStats.added;
610
+ totalUpdated += batchStats.updated;
611
+ totalRemoved += batchStats.removed;
612
+ }
613
+ // Batch-delete the functions of every file that vanished between discovery
614
+ // and the scan (one json_each DELETE, not one per file).
615
+ if (inaccessiblePaths.length > 0) {
616
+ const removed = this.db
617
+ .prepare(SQL_DELETE_FUNCTIONS_IN_FILEPATHS)
618
+ .run(JSON.stringify(inaccessiblePaths));
619
+ totalRemoved += removed.changes;
620
+ }
2167
621
  // Clean up stale entries: a file is stale when it is no longer in the
2168
622
  // discovery set (deleted OR gitignored since the last sync). Comparing
2169
623
  // against discovery — not `fs.access` — makes a gitignored-but-present file
@@ -2171,7 +625,8 @@ export class CodeIndexDB {
2171
625
  // project root (files came from the index itself), fall back to the on-disk
2172
626
  // existence check.
2173
627
  const discoveredSet = projectRoot ? new Set(files) : null;
2174
- const allIndexed = this.db.prepare('SELECT DISTINCT file_path FROM functions').all();
628
+ const allIndexed = this.db.prepare(SQL_DISTINCT_FILE_PATHS).all();
629
+ const stalePaths = [];
2175
630
  for (const { file_path: fp } of allIndexed) {
2176
631
  let stale = false;
2177
632
  if (discoveredSet) {
@@ -2185,12 +640,18 @@ export class CodeIndexDB {
2185
640
  stale = true;
2186
641
  }
2187
642
  }
2188
- if (stale) {
2189
- const result = this.db.prepare('DELETE FROM functions WHERE file_path = ?').run(fp);
2190
- if (result.changes > 0) {
2191
- totalRemoved += result.changes;
2192
- }
2193
- }
643
+ if (stale)
644
+ stalePaths.push(fp);
645
+ }
646
+ // Batch the deletes — one `IN (SELECT value FROM json_each(?))` DELETE for the
647
+ // whole stale set, instead of one DELETE per stale file (loop-query / N+1). A
648
+ // single JSON-array bind expands through json_each, so a large stale set can't
649
+ // exceed the SQLite bind-parameter ceiling.
650
+ if (stalePaths.length > 0) {
651
+ const result = this.db
652
+ .prepare(SQL_DELETE_FUNCTIONS_IN_FILEPATHS)
653
+ .run(JSON.stringify(stalePaths));
654
+ totalRemoved += result.changes;
2194
655
  }
2195
656
  // Spec 13 — Extract git churn data after index is built and stale entries
2196
657
  // are cleaned up. Runs before conventions mining so hotspot data is
@@ -2210,7 +671,7 @@ export class CodeIndexDB {
2210
671
  }
2211
672
  // Spec 12 — Mine codebase conventions after sync when the functions table
2212
673
  // has changed. Content-hash-based skip: stored hash avoids re-mining.
2213
- this.mineAllConventions(projectRoot);
674
+ this.conventions.mineAllConventions(projectRoot);
2214
675
  // Spec 14 — Populate graph caches after index is built
2215
676
  try {
2216
677
  populateCallGraphCache(this.db);
@@ -2235,6 +696,9 @@ export class CodeIndexDB {
2235
696
  *
2236
697
  * Returns the list of changed/new function metadata for scoped analysis,
2237
698
  * and the set of file paths that were actually touched.
699
+ *
700
+ * @param filePaths - The file paths to re-parse and diff.
701
+ * @returns Changed and deleted functions, touched paths, and per-file errors.
2238
702
  */
2239
703
  async detectChangedFunctions(filePaths) {
2240
704
  this.ensureInitialized();
@@ -2243,43 +707,87 @@ export class CodeIndexDB {
2243
707
  const changedFilePaths = [];
2244
708
  const errors = [];
2245
709
  const { FunctionScanner } = await import('./functionScanner.js');
710
+ // First pass: split the changed set into files that no longer exist (deleted)
711
+ // and files that are still on disk. The deleted set is handled with one batched
712
+ // SELECT + DELETE below — a per-file query here was a query-in-loop N+1.
713
+ const missingFiles = [];
2246
714
  for (const filePath of filePaths) {
2247
715
  try {
2248
716
  await fs.access(filePath);
2249
717
  }
2250
718
  catch {
2251
- // File doesn't exist — remove all its functions
2252
- const removed = this.db.prepare('SELECT * FROM functions WHERE file_path = ?').all(filePath);
2253
- if (removed.length > 0) {
2254
- const parsed = removed.map((r) => this.rowToFunction(r));
2255
- deletedFunctions.push(...parsed);
2256
- this.db.prepare('DELETE FROM functions WHERE file_path = ?').run(filePath);
2257
- changedFilePaths.push(filePath);
2258
- }
719
+ missingFiles.push(filePath);
720
+ }
721
+ }
722
+ if (missingFiles.length > 0) {
723
+ const removed = this.db.prepare(`SELECT * FROM functions WHERE file_path IN (SELECT value FROM json_each(?))`).all(JSON.stringify(missingFiles));
724
+ if (removed.length > 0) {
725
+ deletedFunctions.push(...removed.map((r) => this.functionIndex.rowToFunction(r)));
726
+ this.db.prepare(SQL_DELETE_FUNCTIONS_IN_FILEPATHS).run(JSON.stringify(missingFiles));
727
+ const touched = new Set(removed.map((r) => r.file_path));
728
+ changedFilePaths.push(...missingFiles.filter((fp) => touched.has(fp)));
729
+ }
730
+ }
731
+ const missingSet = new Set(missingFiles);
732
+ // Batch-fetch the existing functions for every still-present file in one
733
+ // query, grouped in memory — a per-file `SELECT … WHERE file_path = ?` inside
734
+ // the diff loop below was a query-in-loop N+1.
735
+ const presentFiles = filePaths.filter((fp) => !missingSet.has(fp));
736
+ const existingByFile = new Map();
737
+ if (presentFiles.length > 0) {
738
+ const rows = this.db.prepare(`SELECT * FROM functions WHERE file_path IN (SELECT value FROM json_each(?))`).all(JSON.stringify(presentFiles));
739
+ for (const row of rows) {
740
+ const list = existingByFile.get(row.file_path) ?? [];
741
+ list.push(row);
742
+ existingByFile.set(row.file_path, list);
743
+ }
744
+ }
745
+ // Collect per-file sync entries here and batch them after the loop — a
746
+ // `syncFileIndex` call inside the loop was a per-file transaction plus a
747
+ // per-file dependency-graph rebuild (query-in-loop N+1). `syncFileIndexBatch`
748
+ // collapses that to one transaction and one unscoped rebuild (Amendment B2),
749
+ // and carries each file's content hash so the warm-path short-circuit keeps
750
+ // its stored `file_hash` baseline.
751
+ const syncEntries = [];
752
+ for (const filePath of filePaths) {
753
+ if (missingSet.has(filePath))
2259
754
  continue;
2260
- }
2261
755
  try {
2262
- const scanner = new FunctionScanner();
2263
756
  const fileContent = await fs.readFile(filePath, 'utf-8');
2264
- const language = (filePath.endsWith('.ts') || filePath.endsWith('.tsx'))
2265
- ? 'typescript' : 'javascript';
2266
- const currentFunctions = await scanner.scanFunctions(fileContent, filePath, language);
2267
- // Get existing functions for this file from DB
2268
- const existing = this.db.prepare('SELECT * FROM functions WHERE file_path = ?').all(filePath);
2269
- // Build a name → existing-row map
757
+ const fileHash = computeContentHash(fileContent);
758
+ const existing = existingByFile.get(filePath) ?? [];
759
+ // Short-circuit: when the file's whole-content hash round-trips to the
760
+ // stored `file_hash`, no function can be new, changed, or deleted — skip
761
+ // the tree-sitter re-scan and the re-sync. This is the warm path for
762
+ // `changed` on an already-indexed, unchanged file. The hash is content-
763
+ // based (not mtime), so it preserves the precise change-detection
764
+ // guarantee; a NULL stored hash (first sync after upgrade) never
765
+ // short-circuits, so the index self-heals.
766
+ if (existing.length > 0 && existing[0].file_hash && existing[0].file_hash === fileHash) {
767
+ continue;
768
+ }
769
+ const scanner = new FunctionScanner();
770
+ const currentFunctions = await scanner.scanFunctions(fileContent, filePath);
771
+ // Key identity on (name, line_number) — the table's unique index —
772
+ // not name alone. Two same-named functions in one file (overloads, a
773
+ // method and a free function, the same name in two scopes) otherwise
774
+ // collapse to one map entry, and an edit to the shadowed one is
775
+ // reported against the wrong row or not at all. file_path is constant
776
+ // across the loop, so (name, line_number) is the distinguishing pair.
777
+ const keyOf = (name, lineNumber) => `${name}:${lineNumber}`;
2270
778
  const existingByName = new Map();
2271
779
  for (const e of existing) {
2272
- existingByName.set(e.name, e);
780
+ existingByName.set(keyOf(e.name, e.line_number), e);
2273
781
  }
2274
- const currentNames = new Set(currentFunctions.map((f) => f.name));
782
+ const currentKeys = new Set(currentFunctions.map((f) => keyOf(f.name, f.lineNumber)));
2275
783
  let fileChanged = false;
2276
784
  for (const func of currentFunctions) {
2277
- const existingRow = existingByName.get(func.name);
785
+ const existingRow = existingByName.get(keyOf(func.name, func.lineNumber));
2278
786
  // Convert scanner output to EnhancedFunctionMetadata shape
2279
787
  const funcMeta = {
2280
788
  ...func,
2281
789
  complexity: func.complexity,
2282
- content_hash: computeContentHash(func.body, func.signature),
790
+ content_hash: computeContentHash(func.body),
2283
791
  };
2284
792
  const newHash = funcMeta.content_hash;
2285
793
  if (!existingRow) {
@@ -2295,16 +803,17 @@ export class CodeIndexDB {
2295
803
  }
2296
804
  // Detect deleted functions
2297
805
  for (const e of existing) {
2298
- if (!currentNames.has(e.name)) {
2299
- deletedFunctions.push(this.rowToFunction(e));
806
+ if (!currentKeys.has(keyOf(e.name, e.line_number))) {
807
+ deletedFunctions.push(this.functionIndex.rowToFunction(e));
2300
808
  fileChanged = true;
2301
809
  }
2302
810
  }
2303
811
  if (fileChanged) {
2304
812
  changedFilePaths.push(filePath);
2305
813
  }
2306
- // Sync the file into the index (upsert + deletes)
2307
- await this.syncFileIndex(filePath, currentFunctions);
814
+ // Defer the index write: collected above and flushed in one batch after
815
+ // the loop (see `syncEntries`).
816
+ syncEntries.push({ filePath, currentFunctions: currentFunctions, fileHash });
2308
817
  }
2309
818
  catch (error) {
2310
819
  errors.push({
@@ -2313,11 +822,18 @@ export class CodeIndexDB {
2313
822
  });
2314
823
  }
2315
824
  }
825
+ // One batched write + one dependency-graph rebuild for all changed files.
826
+ if (syncEntries.length > 0) {
827
+ await this.syncFileIndexBatch(syncEntries);
828
+ }
2316
829
  return { changedFunctions, deletedFunctions, changedFilePaths, errors };
2317
830
  }
2318
831
  /**
2319
832
  * Given a project root, detect all indexed files whose mtime is newer than
2320
833
  * the stored last_modified timestamp. Returns file paths for further processing.
834
+ *
835
+ * @param projectRoot - The project root to scan.
836
+ * @returns The file paths whose mtime is newer than the stored timestamp.
2321
837
  */
2322
838
  async detectModifiedFiles(projectRoot) {
2323
839
  this.ensureInitialized();
@@ -2345,582 +861,19 @@ export class CodeIndexDB {
2345
861
  }
2346
862
  return modifiedFiles;
2347
863
  }
864
+ // ── Cross-concern search (search + schema usage) ─────────────────────
2348
865
  /**
2349
- * Get content hashes for a set of file paths. Returns a map:
2350
- * file_path → { name → content_hash }
2351
- */
2352
- getContentHashesForFiles(filePaths) {
2353
- this.ensureInitialized();
2354
- const result = new Map();
2355
- if (filePaths.length === 0)
2356
- return result;
2357
- const placeholders = filePaths.map(() => '?').join(', ');
2358
- const rows = this.db.prepare(`SELECT file_path, name, content_hash FROM functions WHERE file_path IN (${placeholders})`).all(...filePaths);
2359
- for (const row of rows) {
2360
- if (!result.has(row.file_path)) {
2361
- result.set(row.file_path, new Map());
2362
- }
2363
- result.get(row.file_path).set(row.name, row.content_hash ?? '');
2364
- }
2365
- return result;
2366
- }
2367
- // ── Legacy search helpers (for in-memory filtering) ─────────────────
2368
- mergeFilters(optionsFilters, queryFilters) {
2369
- const merged = {};
2370
- if (optionsFilters)
2371
- Object.assign(merged, optionsFilters);
2372
- if (queryFilters) {
2373
- if (queryFilters.language)
2374
- merged.language = queryFilters.language;
2375
- if (queryFilters.filePath)
2376
- merged.filePath = queryFilters.filePath;
2377
- if (queryFilters.fileType)
2378
- merged.fileType = queryFilters.fileType;
2379
- if (queryFilters.hasJsDoc !== undefined)
2380
- merged.hasJsDoc = queryFilters.hasJsDoc;
2381
- if (queryFilters.complexity)
2382
- merged.complexity = queryFilters.complexity;
2383
- if (queryFilters.dateRange)
2384
- merged.dateRange = queryFilters.dateRange;
2385
- if (queryFilters.metadata)
2386
- merged.metadata = queryFilters.metadata;
2387
- }
2388
- return merged;
2389
- }
2390
- applyFilters(results, filters) {
2391
- if (!filters)
2392
- return results;
2393
- let filtered = results;
2394
- if (filters.language) {
2395
- filtered = filtered.filter(doc => doc.language === filters.language);
2396
- }
2397
- if (filters.filePath) {
2398
- if (filters.filePath.includes('*') || filters.filePath.includes('?')) {
2399
- // `*` and `?` are the only glob wildcards; every other character is
2400
- // escaped so it matches literally. A path like `foo[0-9].ts` no longer
2401
- // becomes an accidental regex character class.
2402
- const pattern = filters.filePath
2403
- .split(/([*?])/)
2404
- .map((seg) => (seg === '*' ? '.*' : seg === '?' ? '.' : escapeRegExpLiteral(seg)))
2405
- .join('');
2406
- const regex = new RegExp(pattern);
2407
- filtered = filtered.filter(doc => regex.test(doc.filePath));
2408
- }
2409
- else if (filters.filePath.endsWith('.ts') || filters.filePath.endsWith('.tsx') ||
2410
- filters.filePath.endsWith('.js') || filters.filePath.endsWith('.jsx')) {
2411
- filtered = filtered.filter(doc => doc.filePath.endsWith(filters.filePath));
2412
- }
2413
- else {
2414
- filtered = filtered.filter(doc => doc.filePath.includes(filters.filePath));
2415
- }
2416
- }
2417
- if (filters.fileType) {
2418
- filtered = filtered.filter(doc => doc.filePath.endsWith(filters.fileType));
2419
- }
2420
- if (filters.hasJsDoc !== undefined) {
2421
- filtered = filtered.filter(doc => {
2422
- const hasJsDoc = doc.jsDoc && doc.jsDoc.description && doc.jsDoc.description.length > 0;
2423
- return filters.hasJsDoc ? hasJsDoc : !hasJsDoc;
2424
- });
2425
- }
2426
- if (filters.complexity) {
2427
- filtered = filtered.filter(doc => {
2428
- if (!doc.complexity)
2429
- return false;
2430
- const min = filters.complexity.min || 0;
2431
- const max = filters.complexity.max || Infinity;
2432
- return doc.complexity >= min && doc.complexity <= max;
2433
- });
2434
- }
2435
- if (filters.hasAnyDependency && filters.hasAnyDependency.length > 0) {
2436
- filtered = filtered.filter(doc => filters.hasAnyDependency.some(dep => doc.dependencies.includes(dep)));
2437
- }
2438
- if (filters.metadata) {
2439
- filtered = filtered.filter(doc => {
2440
- if (!doc.metadata)
2441
- return false;
2442
- const m = filters.metadata;
2443
- if (m.entityType && doc.metadata.entityType !== m.entityType)
2444
- return false;
2445
- if (m.componentType && doc.metadata.componentType !== m.componentType)
2446
- return false;
2447
- if (m.hasHook) {
2448
- if (!doc.metadata.hooks)
2449
- return false;
2450
- const found = doc.metadata.hooks.some((h) => h.name?.toLowerCase().includes(m.hasHook.toLowerCase()));
2451
- if (!found)
2452
- return false;
2453
- }
2454
- if (m.hasProp) {
2455
- if (!doc.metadata.props)
2456
- return false;
2457
- const found = doc.metadata.props.some((p) => p.name?.toLowerCase().includes(m.hasProp.toLowerCase()));
2458
- if (!found)
2459
- return false;
2460
- }
2461
- if (m.usesDependency) {
2462
- const dep = m.usesDependency.toLowerCase();
2463
- const inFile = doc.dependencies.some(d => d.toLowerCase().includes(dep));
2464
- const inFunc = doc.metadata.usedImports?.some(i => i.toLowerCase().includes(dep)) ?? false;
2465
- if (!inFile && !inFunc)
2466
- return false;
2467
- }
2468
- if (m.callsFunction) {
2469
- const target = m.callsFunction.toLowerCase();
2470
- const calls = doc.metadata.functionCalls;
2471
- if (!calls || !calls.some(c => c.toLowerCase().includes(target)))
2472
- return false;
2473
- }
2474
- if (m.calledByFunction) {
2475
- const caller = m.calledByFunction.toLowerCase();
2476
- const calledBy = doc.metadata.calledBy;
2477
- if (!calledBy || !calledBy.some(c => c.toLowerCase().includes(caller)))
2478
- return false;
2479
- }
2480
- if (m.dependsOnModule) {
2481
- const mod = m.dependsOnModule.toLowerCase();
2482
- const inFile2 = doc.filePath.toLowerCase().includes(mod);
2483
- const inDep = doc.dependencies.some(d => d.toLowerCase().includes(mod));
2484
- const inCall = doc.metadata.functionCalls?.some(c => c.toLowerCase().includes(mod)) ?? false;
2485
- if (!inFile2 && !inDep && !inCall)
2486
- return false;
2487
- }
2488
- if (m.hasUnusedImports) {
2489
- const unused = doc.metadata.unusedImports;
2490
- if (!unused || unused.length === 0)
2491
- return false;
2492
- }
2493
- return true;
2494
- });
2495
- }
2496
- return filtered;
2497
- }
2498
- excludeTermsFromResults(results, excludedTerms) {
2499
- return results.filter(doc => {
2500
- const searchText = [
2501
- doc.name, doc.signature, doc.purpose, doc.context,
2502
- doc.jsDoc?.description, doc.returnType,
2503
- ...(doc.parameters || []).map((p) => `${p.name} ${p.description || ''}`),
2504
- ...doc.dependencies
2505
- ].filter(Boolean).join(' ').toLowerCase();
2506
- return !excludedTerms.some(term => searchText.includes(term.toLowerCase()));
2507
- });
2508
- }
2509
- // ── Whitelist ───────────────────────────────────────────────────────
2510
- async initializeDefaultWhitelists() {
2511
- const defaults = [
2512
- { name: 'Date', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
2513
- { name: 'Error', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
2514
- { name: 'Array', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
2515
- { name: 'Map', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
2516
- { name: 'Set', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
2517
- { name: 'Promise', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
2518
- { name: 'RegExp', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
2519
- { name: 'URL', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
2520
- { name: 'URLSearchParams', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
2521
- { name: 'FormData', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
2522
- { name: 'Headers', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
2523
- { name: 'fs', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['fs', 'node:fs', 'fs/promises'], addedBy: 'system', addedAt: new Date() },
2524
- { name: 'path', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['path', 'node:path'], addedBy: 'system', addedAt: new Date() },
2525
- { name: 'crypto', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['crypto', 'node:crypto'], addedBy: 'system', addedAt: new Date() },
2526
- { name: 'NextResponse', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nextjs', addedBy: 'system', addedAt: new Date() },
2527
- { name: 'NextRequest', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nextjs', addedBy: 'system', addedAt: new Date() },
2528
- { name: 'Response', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'web-api', addedBy: 'system', addedAt: new Date() },
2529
- { name: 'Request', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'web-api', addedBy: 'system', addedAt: new Date() },
2530
- { name: 'Component', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
2531
- { name: 'PureComponent', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
2532
- { name: 'Fragment', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
2533
- { name: 'StrictMode', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
2534
- { name: 'Suspense', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
2535
- { name: 'Pool', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['pg'], addedBy: 'system', addedAt: new Date() },
2536
- { name: 'Client', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['pg'], addedBy: 'system', addedAt: new Date() },
2537
- { name: 'MongoClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['mongodb'], addedBy: 'system', addedAt: new Date() },
2538
- { name: 'PrismaClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['@prisma/client'], addedBy: 'system', addedAt: new Date() },
2539
- { name: 'StackServerApp', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@stackframe/stack'], addedBy: 'system', addedAt: new Date() },
2540
- { name: 'StackClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@stackframe/stack'], addedBy: 'system', addedAt: new Date() },
2541
- { name: 'ClerkProvider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@clerk/nextjs'], addedBy: 'system', addedAt: new Date() },
2542
- { name: 'Auth0Provider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@auth0/nextjs-auth0'], addedBy: 'system', addedAt: new Date() },
2543
- { name: 'Axios', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'http', patterns: ['axios'], addedBy: 'system', addedAt: new Date() },
2544
- { name: 'HttpClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'http', addedBy: 'system', addedAt: new Date() },
2545
- { name: 'Router', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'express', patterns: ['express'], addedBy: 'system', addedAt: new Date() },
2546
- { name: 'Application', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'express', patterns: ['express'], addedBy: 'system', addedAt: new Date() },
2547
- { name: 'TestingModule', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'testing', patterns: ['@nestjs/testing'], addedBy: 'system', addedAt: new Date() },
2548
- { name: 'MockedProvider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'testing', patterns: ['@apollo/client/testing'], addedBy: 'system', addedAt: new Date() },
2549
- { name: 'EventEmitter', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['events', 'node:events'], addedBy: 'system', addedAt: new Date() },
2550
- { name: 'Readable', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
2551
- { name: 'Writable', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
2552
- { name: 'Transform', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
2553
- { name: 'Buffer', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', addedBy: 'system', addedAt: new Date() },
2554
- ];
2555
- const insert = this.db.prepare(`INSERT INTO whitelist (name, type, status, category, description, patterns, added_by, added_at, metadata_json)
2556
- VALUES (@name, @type, @status, @category, @description, @patterns, @added_by, @added_at, @metadata_json)`);
2557
- for (const entry of defaults) {
2558
- insert.run({
2559
- name: entry.name,
2560
- type: entry.type,
2561
- status: entry.status,
2562
- category: entry.category ?? null,
2563
- description: entry.description ?? null,
2564
- patterns: JSON.stringify(entry.patterns ?? []),
2565
- added_by: entry.addedBy ?? 'system',
2566
- added_at: (entry.addedAt ?? new Date()).toISOString(),
2567
- metadata_json: '{}',
2568
- });
2569
- }
2570
- }
2571
- async getWhitelist(type, status) {
2572
- this.ensureInitialized();
2573
- const rows = this.whitelistAdapter.find({
2574
- ...(type ? { type } : {}),
2575
- ...(status ? { status } : {}),
2576
- });
2577
- return rows.map((r) => ({
2578
- name: r.name,
2579
- type: r.type,
2580
- status: r.status,
2581
- category: r.category,
2582
- description: r.description,
2583
- patterns: tryParseJson(r.patterns),
2584
- addedBy: r.added_by ?? r.addedBy,
2585
- addedAt: new Date(r.added_at ?? r.addedAt),
2586
- updatedAt: r.updated_at ? new Date(r.updated_at) : undefined,
2587
- metadata: tryParseJson(r.metadata_json),
2588
- }));
2589
- }
2590
- async addWhitelistEntry(entry) {
2591
- this.ensureInitialized();
2592
- const row = this.whitelistAdapter.insert({
2593
- name: entry.name,
2594
- type: entry.type,
2595
- status: entry.status ?? WhitelistStatus.Active,
2596
- category: entry.category ?? null,
2597
- description: entry.description ?? null,
2598
- patterns: JSON.stringify(entry.patterns ?? []),
2599
- added_by: entry.addedBy ?? 'user',
2600
- added_at: new Date().toISOString(),
2601
- metadata_json: '{}',
2602
- });
2603
- return {
2604
- name: row.name,
2605
- type: row.type,
2606
- status: row.status,
2607
- category: row.category,
2608
- description: row.description,
2609
- patterns: tryParseJson(row.patterns),
2610
- addedBy: row.added_by,
2611
- addedAt: new Date(row.added_at),
2612
- };
2613
- }
2614
- async updateWhitelistStatus(name, status) {
2615
- this.ensureInitialized();
2616
- this.db.prepare('UPDATE whitelist SET status = ?, updated_at = ? WHERE name = ?')
2617
- .run(status, new Date().toISOString(), name);
2618
- }
2619
- isWhitelisted(name, type) {
2620
- if (!this.isInitialized)
2621
- return false;
2622
- const rows = this.db.prepare('SELECT name, patterns FROM whitelist WHERE type = ? AND status = ?').all(type, WhitelistStatus.Active);
2623
- return rows.some(entry => {
2624
- if (entry.name === name)
2625
- return true;
2626
- const patterns = tryParseJson(entry.patterns);
2627
- if (patterns && Array.isArray(patterns)) {
2628
- return patterns.some((p) => {
2629
- if (p.includes('*')) {
2630
- const regex = new RegExp(p.replace(/\*/g, '.*'));
2631
- return regex.test(name);
2632
- }
2633
- return p === name;
2634
- });
2635
- }
2636
- return false;
2637
- });
2638
- }
2639
- async detectWhitelistCandidates() {
2640
- return [];
2641
- }
2642
- // ── Audit results ───────────────────────────────────────────────────
2643
- async storeAuditResults(auditResult, projectPath) {
2644
- this.ensureInitialized();
2645
- const auditId = `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2646
- this.db.prepare(`INSERT INTO audit_results (audit_id, timestamp, project_path, summary_json, analyzer_results_json, violations_json, recommendations_json, metadata_json, expires_at)
2647
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(auditId, new Date().toISOString(), projectPath, JSON.stringify(auditResult.summary ?? {}), JSON.stringify(auditResult.analyzerResults ?? auditResult.results ?? {}), JSON.stringify(auditResult.violations ?? null), JSON.stringify(auditResult.recommendations ?? null), JSON.stringify(auditResult.metadata ?? {}), new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString());
2648
- this.cleanupExpiredAudits();
2649
- return auditId;
2650
- }
2651
- async getAuditResults(auditId) {
2652
- this.ensureInitialized();
2653
- const row = this.db.prepare('SELECT * FROM audit_results WHERE audit_id = ?').get(auditId);
2654
- if (!row)
2655
- return null;
2656
- if (new Date(row.expires_at) <= new Date()) {
2657
- this.db.prepare('DELETE FROM audit_results WHERE audit_id = ?').run(auditId);
2658
- return null;
2659
- }
2660
- return {
2661
- auditId: row.audit_id,
2662
- timestamp: new Date(row.timestamp),
2663
- projectPath: row.project_path,
2664
- summary: tryParseJson(row.summary_json),
2665
- analyzerResults: tryParseJson(row.analyzer_results_json),
2666
- violations: tryParseJson(row.violations_json),
2667
- recommendations: tryParseJson(row.recommendations_json),
2668
- metadata: tryParseJson(row.metadata_json),
2669
- expiresAt: new Date(row.expires_at),
2670
- };
2671
- }
2672
- async getMostRecentAuditResults(projectPath, resultScope) {
2673
- this.ensureInitialized();
2674
- const now = new Date().toISOString();
2675
- let row;
2676
- const scopeFilter = resultScope
2677
- ? "AND json_extract(metadata_json, '$.scope') = ?"
2678
- : '';
2679
- if (projectPath) {
2680
- const params = [projectPath, now];
2681
- if (resultScope)
2682
- params.push(resultScope);
2683
- row = this.db.prepare(`SELECT * FROM audit_results WHERE project_path = ? AND expires_at > ? ${scopeFilter} ORDER BY timestamp DESC, rowid DESC LIMIT 1`).get(...params);
2684
- }
2685
- else {
2686
- const params = [now];
2687
- if (resultScope)
2688
- params.push(resultScope);
2689
- row = this.db.prepare(`SELECT * FROM audit_results WHERE expires_at > ? ${scopeFilter} ORDER BY timestamp DESC, rowid DESC LIMIT 1`).get(...params);
2690
- }
2691
- if (!row)
2692
- return null;
2693
- return {
2694
- auditId: row.audit_id,
2695
- timestamp: new Date(row.timestamp),
2696
- projectPath: row.project_path,
2697
- summary: tryParseJson(row.summary_json),
2698
- analyzerResults: tryParseJson(row.analyzer_results_json),
2699
- violations: tryParseJson(row.violations_json),
2700
- recommendations: tryParseJson(row.recommendations_json),
2701
- metadata: tryParseJson(row.metadata_json),
2702
- expiresAt: new Date(row.expires_at),
2703
- };
2704
- }
2705
- hasOpenTaskByFingerprint(fingerprint) {
2706
- if (!fingerprint)
2707
- return false;
2708
- return this.getTaskRepository().findOpenByFingerprint(fingerprint).length > 0;
2709
- }
2710
- cleanupExpiredAudits() {
2711
- this.db.prepare('DELETE FROM audit_results WHERE expires_at < ?').run(new Date().toISOString());
2712
- }
2713
- // ── Analyzer configs ────────────────────────────────────────────────
2714
- async storeAnalyzerConfig(analyzerName, config, options) {
2715
- this.ensureInitialized();
2716
- const projectPath = options?.projectPath ?? null;
2717
- const isGlobal = options?.isGlobal ?? true;
2718
- const existing = this.db.prepare('SELECT id FROM analyzer_configs WHERE analyzer_name = ? AND COALESCE(project_path, \'__global__\') = ? AND is_global = ?').get(analyzerName, projectPath ?? '__global__', isGlobal ? 1 : 0);
2719
- if (existing) {
2720
- this.db.prepare('UPDATE analyzer_configs SET config_json = ?, updated_at = ?, metadata_json = ? WHERE id = ?').run(JSON.stringify(config), new Date().toISOString(), JSON.stringify(options?.metadata ?? {}), existing.id);
2721
- }
2722
- else {
2723
- this.db.prepare(`INSERT INTO analyzer_configs (analyzer_name, project_path, is_global, config_json, created_by, created_at, updated_at, metadata_json)
2724
- VALUES (?, ?, ?, ?, 'user', ?, ?, ?)`).run(analyzerName, projectPath, isGlobal ? 1 : 0, JSON.stringify(config), new Date().toISOString(), new Date().toISOString(), JSON.stringify(options?.metadata ?? {}));
2725
- }
2726
- return analyzerName;
2727
- }
2728
- async getAnalyzerConfig(analyzerName, projectPath) {
2729
- this.ensureInitialized();
2730
- if (projectPath) {
2731
- const row = this.db.prepare('SELECT config_json FROM analyzer_configs WHERE analyzer_name = ? AND project_path = ? AND is_global = 0').get(analyzerName, projectPath);
2732
- if (row)
2733
- return tryParseJson(row.config_json);
2734
- }
2735
- const globalRow = this.db.prepare('SELECT config_json FROM analyzer_configs WHERE analyzer_name = ? AND is_global = 1').get(analyzerName);
2736
- return globalRow ? tryParseJson(globalRow.config_json) : null;
2737
- }
2738
- async getAllAnalyzerConfigs(projectPath) {
2739
- this.ensureInitialized();
2740
- const configs = {};
2741
- const globals = this.db.prepare('SELECT analyzer_name, config_json FROM analyzer_configs WHERE is_global = 1').all();
2742
- for (const c of globals) {
2743
- configs[c.analyzer_name] = tryParseJson(c.config_json);
2744
- }
2745
- if (projectPath) {
2746
- const locals = this.db.prepare('SELECT analyzer_name, config_json FROM analyzer_configs WHERE project_path = ? AND is_global = 0').all(projectPath);
2747
- for (const c of locals) {
2748
- configs[c.analyzer_name] = tryParseJson(c.config_json);
2749
- }
2750
- }
2751
- return configs;
2752
- }
2753
- async deleteAnalyzerConfig(analyzerName, options) {
2754
- this.ensureInitialized();
2755
- const result = this.db.prepare('DELETE FROM analyzer_configs WHERE analyzer_name = ? AND COALESCE(project_path, \'__global__\') = ? AND is_global = ?').run(analyzerName, options?.projectPath ?? '__global__', (options?.isGlobal ?? !options?.projectPath) ? 1 : 0);
2756
- return result.changes > 0;
2757
- }
2758
- async resetAnalyzerConfigs(projectPath) {
2759
- this.ensureInitialized();
2760
- if (projectPath) {
2761
- this.db.prepare('DELETE FROM analyzer_configs WHERE project_path = ? AND is_global = 0').run(projectPath);
2762
- }
2763
- else {
2764
- this.db.prepare('DELETE FROM analyzer_configs').run();
2765
- }
2766
- }
2767
- // ── Code maps ───────────────────────────────────────────────────────
2768
- async storeCodeMapSection(mapId, sectionType, content, metadata) {
2769
- this.ensureInitialized();
2770
- this.db.prepare(`INSERT OR REPLACE INTO code_maps (map_id, section_type, content, metadata_json, timestamp, size)
2771
- VALUES (?, ?, ?, ?, ?, ?)`).run(mapId, sectionType, content, JSON.stringify(metadata ?? {}), new Date().toISOString(), content.length);
2772
- }
2773
- async getCodeMapSection(mapId, sectionType) {
2774
- this.ensureInitialized();
2775
- const row = this.db.prepare('SELECT content, metadata_json FROM code_maps WHERE map_id = ? AND section_type = ?')
2776
- .get(mapId, sectionType);
2777
- return row ? { content: row.content, metadata: tryParseJson(row.metadata_json) ?? {} } : null;
2778
- }
2779
- async listCodeMapSections(mapId) {
2780
- this.ensureInitialized();
2781
- const rows = this.db.prepare('SELECT section_type, size, timestamp FROM code_maps WHERE map_id = ?')
2782
- .all(mapId);
2783
- return rows.map((r) => ({ sectionType: r.section_type, size: r.size ?? 0, timestamp: new Date(r.timestamp) }));
2784
- }
2785
- async clearOldCodeMaps(olderThanHours = 24) {
2786
- this.ensureInitialized();
2787
- const cutoff = new Date(Date.now() - olderThanHours * 60 * 60 * 1000).toISOString();
2788
- const result = this.db.prepare('DELETE FROM code_maps WHERE timestamp < ?').run(cutoff);
2789
- return result.changes;
2790
- }
2791
- async deleteCodeMap(mapId) {
2792
- this.ensureInitialized();
2793
- const result = this.db.prepare('DELETE FROM code_maps WHERE map_id = ?').run(mapId);
2794
- return result.changes;
2795
- }
2796
- // ── Schema management ───────────────────────────────────────────────
2797
- async storeSchema(schema) {
2798
- this.ensureInitialized();
2799
- const schemaId = `schema_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2800
- const metadata = {
2801
- schemaId,
2802
- schemaName: schema.name,
2803
- indexedAt: new Date(),
2804
- tableCount: schema.databases.reduce((acc, db) => acc + db.tables.length, 0),
2805
- relationshipCount: schema.databases.reduce((acc, db) => acc + (db.relationships?.length || 0), 0),
2806
- usagePatterns: [],
2807
- discoveredPatterns: [],
2808
- violations: []
2809
- };
2810
- this.db.prepare('INSERT INTO schema_definitions (schema_id, schema_name, schema_json, metadata_json, indexed_at) VALUES (?, ?, ?, ?, ?)').run(schemaId, schema.name, JSON.stringify(schema), JSON.stringify(metadata), new Date().toISOString());
2811
- return schemaId;
2812
- }
2813
- async getSchema(schemaId) {
2814
- this.ensureInitialized();
2815
- const row = this.db.prepare('SELECT schema_json FROM schema_definitions WHERE schema_id = ?').get(schemaId);
2816
- return row ? tryParseJson(row.schema_json) : null;
2817
- }
2818
- async getAllSchemas() {
2819
- this.ensureInitialized();
2820
- const rows = this.db.prepare('SELECT schema_id, schema_json, metadata_json FROM schema_definitions').all();
2821
- return rows.map((r) => ({
2822
- schemaId: r.schema_id,
2823
- metadata: tryParseJson(r.metadata_json) ?? {},
2824
- schema: tryParseJson(r.schema_json),
2825
- }));
2826
- }
2827
- async deleteSchema(schemaId) {
2828
- this.ensureInitialized();
2829
- this.db.prepare('DELETE FROM schema_usage WHERE schema_id = ?').run(schemaId);
2830
- const result = this.db.prepare('DELETE FROM schema_definitions WHERE schema_id = ?').run(schemaId);
2831
- return result.changes > 0;
2832
- }
2833
- async recordSchemaUsage(usage, schemaId) {
2834
- this.ensureInitialized();
2835
- const existing = this.db.prepare('SELECT id FROM schema_usage WHERE table_name = ? AND file_path = ? AND function_start_line IS ? AND function_start_column IS ? AND line = ?').get(usage.tableName, usage.filePath, usage.functionStartLine ?? null, usage.functionStartColumn ?? null, usage.line);
2836
- if (existing) {
2837
- this.db.prepare('UPDATE schema_usage SET schema_id = ?, function_name = ?, usage_type = ?, "column" = ?, raw_query = ?, parameters = ?, origin = ?, recorded_at = ? WHERE id = ?').run(schemaId ?? 'default', usage.functionName ?? null, usage.usageType, usage.column ?? null, usage.rawQuery ?? null, JSON.stringify(usage.parameters ?? []), usage.origin ?? null, new Date().toISOString(), existing.id);
2838
- }
2839
- else {
2840
- this.db.prepare('INSERT INTO schema_usage (schema_id, table_name, file_path, function_name, function_start_line, function_start_column, usage_type, line, "column", raw_query, parameters, origin, recorded_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').run(schemaId ?? 'default', usage.tableName, usage.filePath, usage.functionName ?? null, usage.functionStartLine ?? null, usage.functionStartColumn ?? null, usage.usageType, usage.line ?? null, usage.column ?? null, usage.rawQuery ?? null, JSON.stringify(usage.parameters ?? []), usage.origin ?? null, new Date().toISOString());
2841
- }
2842
- }
2843
- /**
2844
- * Clear all schema_usage entries for a given file path.
2845
- * Used for idempotent per-file writes during indexing — stale entries
2846
- * from a previous scan are removed before fresh references are inserted.
866
+ * Search functions and optionally annotate results with schema usage context.
867
+ *
868
+ * @param options - Search options, including includeSchemaUsage.
869
+ * @returns The search result, with schema context when requested.
2847
870
  */
2848
- clearSchemaUsageForFile(filePath) {
2849
- this.ensureInitialized();
2850
- this.db.prepare('DELETE FROM schema_usage WHERE file_path = ?').run(filePath);
2851
- }
2852
- async getSchemaUsage(options = {}) {
2853
- this.ensureInitialized();
2854
- const clauses = [];
2855
- const params = [];
2856
- if (options.schemaId) {
2857
- clauses.push('schema_id = ?');
2858
- params.push(options.schemaId);
2859
- }
2860
- if (options.tableName) {
2861
- clauses.push('table_name = ?');
2862
- params.push(options.tableName);
2863
- }
2864
- if (options.filePath) {
2865
- clauses.push('file_path = ?');
2866
- params.push(options.filePath);
2867
- }
2868
- if (options.functionName) {
2869
- clauses.push('function_name = ?');
2870
- params.push(options.functionName);
2871
- }
2872
- if (options.usageType) {
2873
- clauses.push('usage_type = ?');
2874
- params.push(options.usageType);
2875
- }
2876
- const sql = 'SELECT * FROM schema_usage' + (clauses.length ? ' WHERE ' + clauses.join(' AND ') : '');
2877
- const rows = this.db.prepare(sql).all(...params);
2878
- return rows.map((r) => ({
2879
- tableName: r.table_name,
2880
- filePath: r.file_path,
2881
- functionName: r.function_name,
2882
- functionStartLine: r.function_start_line,
2883
- functionStartColumn: r.function_start_column,
2884
- usageType: r.usage_type,
2885
- line: r.line,
2886
- column: r.column,
2887
- rawQuery: r.raw_query,
2888
- parameters: tryParseJson(r.parameters) ?? [],
2889
- }));
2890
- }
2891
- async findFunctionsUsingTable(tableName) {
2892
- this.ensureInitialized();
2893
- const rows = this.db.prepare('SELECT function_name, file_path, usage_type, line FROM schema_usage WHERE table_name = ?').all(tableName);
2894
- return rows.map((r) => ({
2895
- functionName: r.function_name,
2896
- filePath: r.file_path,
2897
- usageType: r.usage_type,
2898
- line: r.line,
2899
- }));
2900
- }
2901
- async getSchemaStats() {
2902
- this.ensureInitialized();
2903
- const totalSchemas = this.db.prepare('SELECT COUNT(*) as cnt FROM schema_definitions').get().cnt;
2904
- const totalUsagePatterns = this.db.prepare('SELECT COUNT(*) as cnt FROM schema_usage').get().cnt;
2905
- const schemas = this.db.prepare('SELECT schema_json FROM schema_definitions').all();
2906
- const totalTables = schemas.reduce((acc, s) => {
2907
- const schema = tryParseJson(s.schema_json);
2908
- return acc + (schema?.databases?.reduce((dbAcc, db) => dbAcc + (db.tables?.length ?? 0), 0) ?? 0);
2909
- }, 0);
2910
- const mostUsedTables = this.db.prepare('SELECT table_name as tableName, COUNT(*) as usageCount FROM schema_usage GROUP BY table_name ORDER BY usageCount DESC LIMIT 10').all();
2911
- const usageByType = {};
2912
- const typeRows = this.db.prepare('SELECT usage_type, COUNT(*) as cnt FROM schema_usage GROUP BY usage_type').all();
2913
- for (const r of typeRows) {
2914
- usageByType[r.usage_type] = r.cnt;
2915
- }
2916
- return { totalSchemas, totalTables, totalUsagePatterns, mostUsedTables, usageByType };
2917
- }
2918
- async searchWithSchemaContext(query, options = {}) {
2919
- const searchResult = await this.searchFunctions(options);
871
+ async searchWithSchemaContext(options = {}) {
872
+ const searchResult = await this.search.searchFunctions(options);
2920
873
  if (!options.includeSchemaUsage)
2921
874
  return searchResult;
2922
875
  const enhancedFunctions = await Promise.all(searchResult.functions.map(async (func) => {
2923
- const schemaUsage = await this.getSchemaUsage({
876
+ const schemaUsage = await this.schema.getSchemaUsage({
2924
877
  filePath: func.filePath,
2925
878
  functionName: func.name
2926
879
  });
@@ -2937,322 +890,5 @@ export class CodeIndexDB {
2937
890
  })))];
2938
891
  return { ...searchResult, functions: enhancedFunctions, schemaContext };
2939
892
  }
2940
- // ── Project tasks ───────────────────────────────────────────────────
2941
- getTaskRepository() {
2942
- this.ensureInitialized();
2943
- if (!this.taskRepository) {
2944
- this.taskRepository = new ProjectTaskRepository(() => this.tasksAdapter, () => { });
2945
- }
2946
- return this.taskRepository;
2947
- }
2948
- async createProjectTask(input) {
2949
- return this.getTaskRepository().create(input);
2950
- }
2951
- async getProjectTask(taskId) {
2952
- return this.getTaskRepository().getById(taskId);
2953
- }
2954
- async listProjectTasks(projectPath, options) {
2955
- return this.getTaskRepository().list(projectPath, options);
2956
- }
2957
- async listProjectTasksTree(projectPath, options) {
2958
- return this.getTaskRepository().listTree(projectPath, options);
2959
- }
2960
- async listActionableProjectTasks(projectPath, options) {
2961
- return this.getTaskRepository().listActionable(projectPath, options);
2962
- }
2963
- async completeProjectTask(taskId) {
2964
- return this.getTaskRepository().complete(taskId);
2965
- }
2966
- async updateProjectTask(taskId, patch) {
2967
- return this.getTaskRepository().update(taskId, patch);
2968
- }
2969
- async deleteProjectTask(taskId, mode) {
2970
- return this.getTaskRepository().delete(taskId, mode);
2971
- }
2972
- // ── Meta key-value store ─────────────────────────────────────────────
2973
- /** Upsert a key-value pair in the meta table. */
2974
- setMeta(key, value) {
2975
- this.ensureInitialized();
2976
- this.db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)
2977
- ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(key, value);
2978
- }
2979
- /** Retrieve a value from the meta table, or null if absent. */
2980
- getMeta(key) {
2981
- this.ensureInitialized();
2982
- const row = this.db.prepare('SELECT value FROM meta WHERE key = ?').get(key);
2983
- return row?.value ?? null;
2984
- }
2985
- // ── Provenance storage (Spec-21 R2 cross-file) ─────────────────────
2986
- /** Store per-file provenance context in the meta table. */
2987
- storeFileProvenance(filePath, provenanceData) {
2988
- const key = `provenance:${filePath}`;
2989
- this.setMeta(key, JSON.stringify(provenanceData));
2990
- }
2991
- /** Retrieve per-file provenance context, or null if not stored. */
2992
- getFileProvenance(filePath) {
2993
- const raw = this.getMeta(`provenance:${filePath}`);
2994
- if (!raw)
2995
- return null;
2996
- try {
2997
- return JSON.parse(raw);
2998
- }
2999
- catch {
3000
- return null;
3001
- }
3002
- }
3003
- /** Store the inferred receiver set as a meta record. */
3004
- storeInferredReceivers(inferred) {
3005
- this.setMeta('inferred_receivers', JSON.stringify(inferred));
3006
- }
3007
- /** Retrieve the inferred receiver set, or null if not stored. */
3008
- getInferredReceivers() {
3009
- const raw = this.getMeta('inferred_receivers');
3010
- if (!raw)
3011
- return null;
3012
- try {
3013
- return JSON.parse(raw);
3014
- }
3015
- catch {
3016
- return null;
3017
- }
3018
- }
3019
- // ── Coverage data (Spec 15 R4) ──────────────────────────────────────
3020
- /** Import coverage entries, replacing any existing data for the given basis. */
3021
- importCoverageData(entries) {
3022
- this.ensureInitialized();
3023
- // Group by basis so we only clear one basis at a time
3024
- const bases = new Set(entries.map(e => e.basis));
3025
- const clearStmt = this.db.prepare('DELETE FROM coverage_data WHERE basis = ?');
3026
- for (const basis of bases) {
3027
- clearStmt.run(basis);
3028
- }
3029
- const insert = this.db.prepare(`INSERT OR REPLACE INTO coverage_data (function_name, file_path, line_number, basis, covered, source, imported_at)
3030
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`);
3031
- const tx = this.db.transaction((items) => {
3032
- for (const e of items) {
3033
- insert.run(e.functionName, e.filePath, e.lineNumber, e.basis, e.covered ? 1 : 0, e.source ?? null);
3034
- }
3035
- });
3036
- tx(entries);
3037
- }
3038
- /** Get all coverage entries for a given basis. */
3039
- getCoverageByBasis(basis) {
3040
- this.ensureInitialized();
3041
- const rows = this.db.prepare('SELECT * FROM coverage_data WHERE basis = ?').all(basis);
3042
- return rows.map((r) => ({
3043
- functionName: r.function_name,
3044
- filePath: r.file_path,
3045
- lineNumber: r.line_number,
3046
- basis: r.basis,
3047
- covered: r.covered === 1,
3048
- source: r.source,
3049
- importedAt: r.imported_at,
3050
- }));
3051
- }
3052
- /** Clear coverage data, optionally scoped to a single basis. */
3053
- clearCoverageData(basis) {
3054
- this.ensureInitialized();
3055
- if (basis) {
3056
- this.db.prepare('DELETE FROM coverage_data WHERE basis = ?').run(basis);
3057
- }
3058
- else {
3059
- this.db.prepare('DELETE FROM coverage_data').run();
3060
- }
3061
- }
3062
- /** Check if measured coverage data is stale (older than the last full sync). */
3063
- isCoverageStale() {
3064
- this.ensureInitialized();
3065
- const lastSync = this.getMeta('last_full_sync_timestamp');
3066
- if (!lastSync)
3067
- return false;
3068
- const staleRow = this.db.prepare(`SELECT COUNT(*) as cnt FROM coverage_data
3069
- WHERE basis = 'measured' AND imported_at < ?`).get(lastSync);
3070
- return (staleRow?.cnt ?? 0) > 0;
3071
- }
3072
- /** Get coverage rate by risk decile for the coverage report. */
3073
- getCoverageByRiskDecile(decileCount = 10) {
3074
- this.ensureInitialized();
3075
- // Rank functions by risk score from hotspot_scores, join with coverage_data
3076
- const rows = this.db.prepare(`
3077
- WITH ranked AS (
3078
- SELECT
3079
- f.name,
3080
- f.file_path,
3081
- f.line_number,
3082
- COALESCE(hs.score, 0.0) as risk_score,
3083
- NTILE(?) OVER (ORDER BY COALESCE(hs.score, 0.0) DESC) as decile
3084
- FROM functions f
3085
- LEFT JOIN hotspot_scores hs ON hs.target = (f.file_path || ':' || f.name)
3086
- AND hs.type = 'function'
3087
- WHERE f.is_exported = 1
3088
- ),
3089
- coverage AS (
3090
- SELECT DISTINCT function_name, file_path FROM coverage_data
3091
- WHERE covered = 1
3092
- )
3093
- SELECT
3094
- r.decile,
3095
- COUNT(*) as total,
3096
- SUM(CASE WHEN c.function_name IS NOT NULL THEN 1 ELSE 0 END) as covered
3097
- FROM ranked r
3098
- LEFT JOIN coverage c ON c.function_name = r.name AND c.file_path = r.file_path
3099
- GROUP BY r.decile
3100
- ORDER BY r.decile
3101
- `).all(decileCount);
3102
- return rows.map((r) => ({
3103
- decile: r.decile,
3104
- covered: r.covered,
3105
- total: r.total,
3106
- rate: r.total > 0 ? r.covered / r.total : 0,
3107
- }));
3108
- }
3109
- /** Get untested functions in the top risk decile. */
3110
- getUntestedTopDecile(topDecile = 0.1) {
3111
- this.ensureInitialized();
3112
- const rows = this.db.prepare(`
3113
- WITH ranked AS (
3114
- SELECT
3115
- f.name,
3116
- f.file_path,
3117
- f.line_number,
3118
- COALESCE(hs.score, 0.0) as risk_score,
3119
- PERCENT_RANK() OVER (ORDER BY COALESCE(hs.score, 0.0) DESC) as pct
3120
- FROM functions f
3121
- LEFT JOIN hotspot_scores hs ON hs.target = (f.file_path || ':' || f.name)
3122
- AND hs.type = 'function'
3123
- WHERE f.is_exported = 1
3124
- ),
3125
- best_coverage AS (
3126
- SELECT DISTINCT function_name, file_path, basis FROM coverage_data
3127
- WHERE covered = 1
3128
- )
3129
- SELECT
3130
- r.name,
3131
- r.file_path,
3132
- r.line_number,
3133
- r.risk_score,
3134
- COALESCE(bc.basis, 'static-reach') as basis
3135
- FROM ranked r
3136
- LEFT JOIN best_coverage bc ON bc.function_name = r.name AND bc.file_path = r.file_path
3137
- WHERE r.pct <= ?
3138
- AND bc.basis IS NULL
3139
- ORDER BY r.risk_score DESC
3140
- `).all(topDecile);
3141
- return rows.map((r) => ({
3142
- functionName: r.name,
3143
- filePath: r.file_path,
3144
- lineNumber: r.line_number ?? 1,
3145
- riskScore: r.risk_score,
3146
- basis: r.basis,
3147
- }));
3148
- }
3149
- /** Execute a parameterized query and return all rows. */
3150
- query(sql, params) {
3151
- this.ensureInitialized();
3152
- return params?.length
3153
- ? this.db.prepare(sql).all(...params)
3154
- : this.db.prepare(sql).all();
3155
- }
3156
- /** Count rows in a table, with optional WHERE clause. */
3157
- count(table, where, params) {
3158
- this.ensureInitialized();
3159
- assertSqlIdentifier(table, 'table name');
3160
- const whereClause = where ? ` WHERE ${where}` : '';
3161
- const row = this.db.prepare(`SELECT COUNT(*) as cnt FROM ${table}${whereClause}`).get(...(params ?? []));
3162
- return row.cnt;
3163
- }
3164
- /** Check if a table has any rows. */
3165
- tableHasRows(table) {
3166
- this.ensureInitialized();
3167
- assertSqlIdentifier(table, 'table name');
3168
- const row = this.db.prepare(`SELECT 1 FROM ${table} LIMIT 1`).get();
3169
- return row !== undefined;
3170
- }
3171
- /**
3172
- * Mine codebase conventions from the functions table and upsert into the
3173
- * conventions table. Content-hash-based skip: stored hash avoids re-mining
3174
- * when the function corpus hasn't changed since the last mine.
3175
- *
3176
- * Called both by deepSync() (after indexing) and by audit runs (so the
3177
- * conventions analyzer has data to query even without an explicit sync).
3178
- */
3179
- mineAllConventions(projectRoot, getSource) {
3180
- this.ensureInitialized();
3181
- const miningConfig = {
3182
- minCorpus: 20,
3183
- pairConfidence: 0.9,
3184
- modeShare: 0.8,
3185
- maxConventionsPerDomain: 200,
3186
- };
3187
- const newHash = computeMinerInputHash(this.db, miningConfig);
3188
- const oldHashRow = this.db.prepare("SELECT value FROM meta WHERE key = 'conventions_hash'").get();
3189
- if (!oldHashRow || oldHashRow.value !== newHash) {
3190
- const conventions = mineConventions(this.db, miningConfig, projectRoot, getSource);
3191
- const deleteStmt = this.db.prepare('DELETE FROM conventions');
3192
- const insertStmt = this.db.prepare(`INSERT INTO conventions
3193
- (domain, rule_id, antecedent, consequent, pattern, directory,
3194
- file_path, line, support, total_cases, confidence,
3195
- exemplar_file, exemplar_line, export_kind, hash)
3196
- VALUES (@domain, @rule_id, @antecedent, @consequent, @pattern,
3197
- @directory, @file_path, @line, @support, @total_cases,
3198
- @confidence, @exemplar_file, @exemplar_line, @export_kind, @hash)`);
3199
- const upsertAll = this.db.transaction(() => {
3200
- deleteStmt.run();
3201
- for (const c of conventions) {
3202
- insertStmt.run({
3203
- domain: c.domain,
3204
- rule_id: c.rule_id,
3205
- antecedent: c.antecedent ?? null,
3206
- consequent: c.consequent ?? null,
3207
- pattern: c.pattern ?? null,
3208
- directory: c.directory ?? null,
3209
- file_path: c.file_path ?? null,
3210
- line: c.line ?? null,
3211
- support: c.support,
3212
- total_cases: c.total_cases,
3213
- confidence: c.confidence,
3214
- exemplar_file: c.exemplar_file ?? null,
3215
- exemplar_line: c.exemplar_line ?? null,
3216
- export_kind: c.export_kind ?? null,
3217
- hash: c.hash ?? null,
3218
- });
3219
- }
3220
- });
3221
- upsertAll();
3222
- // Only store the hash when conventions were actually produced.
3223
- // Storing it on an empty mine would be a poison pill — the hash
3224
- // would match on every subsequent run, skipping mining forever.
3225
- if (conventions.length > 0) {
3226
- this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('conventions_hash', ?)").run(newHash);
3227
- }
3228
- }
3229
- }
3230
- /** Execute a DML statement (INSERT/UPDATE/DELETE) and return its result. */
3231
- run(sql, params) {
3232
- this.ensureInitialized();
3233
- return params?.length
3234
- ? this.db.prepare(sql).run(...params)
3235
- : this.db.prepare(sql).run();
3236
- }
3237
- /** Execute raw SQL (multi-statement) — for schema setup / bulk operations. */
3238
- exec(sql) {
3239
- this.ensureInitialized();
3240
- this.db.exec(sql);
3241
- }
3242
- }
3243
- // ── Helpers ────────────────────────────────────────────────────────────
3244
- function tryParseJson(val) {
3245
- if (val === null || val === undefined)
3246
- return null;
3247
- if (typeof val === 'object')
3248
- return val;
3249
- if (typeof val !== 'string')
3250
- return val;
3251
- try {
3252
- return JSON.parse(val);
3253
- }
3254
- catch {
3255
- return val;
3256
- }
3257
893
  }
3258
894
  //# sourceMappingURL=codeIndexDB.js.map