code-auditor-mcp 3.1.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (357) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +665 -1
  3. package/README.md +85 -11
  4. package/dist/__test_tmp__/spec18_test.d.ts +2 -0
  5. package/dist/__test_tmp__/spec18_test.d.ts.map +1 -0
  6. package/dist/__test_tmp__/spec18_test.js +5 -0
  7. package/dist/__test_tmp__/spec18_test.js.map +1 -0
  8. package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts +6 -0
  9. package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/spec-20/scripts/tool.js +12 -0
  11. package/dist/__tests__/fixtures/spec-20/scripts/tool.js.map +1 -0
  12. package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts +7 -0
  13. package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts.map +1 -0
  14. package/dist/__tests__/fixtures/spec-20/src/demo-module.js +13 -0
  15. package/dist/__tests__/fixtures/spec-20/src/demo-module.js.map +1 -0
  16. package/dist/analyzers/analyzerUtils.js +2 -2
  17. package/dist/analyzers/analyzerUtils.js.map +1 -1
  18. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +1 -1
  19. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
  20. package/dist/analyzers/cross-language/APIContractAnalyzer.js +2 -2
  21. package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
  22. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts +3 -0
  23. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
  24. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js +3 -3
  25. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
  26. package/dist/analyzers/cross-language/SchemaValidator.d.ts +1 -1
  27. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  28. package/dist/analyzers/cross-language/SchemaValidator.js +2 -2
  29. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  30. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +83 -0
  31. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -0
  32. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +611 -0
  33. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -0
  34. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  35. package/dist/analyzers/documentationAnalyzer.js +8 -4
  36. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  37. package/dist/analyzers/orm/adapterRegistry.d.ts +41 -0
  38. package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -0
  39. package/dist/analyzers/orm/adapterRegistry.js +73 -0
  40. package/dist/analyzers/orm/adapterRegistry.js.map +1 -0
  41. package/dist/analyzers/orm/drizzleAdapter.d.ts +42 -0
  42. package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -0
  43. package/dist/analyzers/orm/drizzleAdapter.js +166 -0
  44. package/dist/analyzers/orm/drizzleAdapter.js.map +1 -0
  45. package/dist/analyzers/orm/index.d.ts +14 -0
  46. package/dist/analyzers/orm/index.d.ts.map +1 -0
  47. package/dist/analyzers/orm/index.js +24 -0
  48. package/dist/analyzers/orm/index.js.map +1 -0
  49. package/dist/analyzers/orm/prismaAdapter.d.ts +45 -0
  50. package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -0
  51. package/dist/analyzers/orm/prismaAdapter.js +170 -0
  52. package/dist/analyzers/orm/prismaAdapter.js.map +1 -0
  53. package/dist/analyzers/orm/types.d.ts +63 -0
  54. package/dist/analyzers/orm/types.d.ts.map +1 -0
  55. package/dist/analyzers/orm/types.js +10 -0
  56. package/dist/analyzers/orm/types.js.map +1 -0
  57. package/dist/analyzers/provenance.d.ts +133 -0
  58. package/dist/analyzers/provenance.d.ts.map +1 -0
  59. package/dist/analyzers/provenance.js +1041 -0
  60. package/dist/analyzers/provenance.js.map +1 -0
  61. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  62. package/dist/analyzers/reactAnalyzer.js +117 -1
  63. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  64. package/dist/analyzers/ruleRegistry.d.ts +38 -0
  65. package/dist/analyzers/ruleRegistry.d.ts.map +1 -0
  66. package/dist/analyzers/ruleRegistry.js +123 -0
  67. package/dist/analyzers/ruleRegistry.js.map +1 -0
  68. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +58 -0
  69. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -0
  70. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +445 -0
  71. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -0
  72. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +52 -0
  73. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  74. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +108 -26
  75. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  76. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +64 -0
  77. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  78. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +259 -53
  79. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  80. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  81. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +5 -15
  82. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  83. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  84. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +10 -10
  85. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  86. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +25 -0
  87. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  88. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +250 -59
  89. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  90. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +103 -0
  91. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -0
  92. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +750 -0
  93. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -0
  94. package/dist/auditRunner.d.ts.map +1 -1
  95. package/dist/auditRunner.js +483 -3
  96. package/dist/auditRunner.js.map +1 -1
  97. package/dist/baseline.d.ts +98 -0
  98. package/dist/baseline.d.ts.map +1 -0
  99. package/dist/baseline.js +184 -0
  100. package/dist/baseline.js.map +1 -0
  101. package/dist/churn/churnExtractor.d.ts +32 -0
  102. package/dist/churn/churnExtractor.d.ts.map +1 -0
  103. package/dist/churn/churnExtractor.js +261 -0
  104. package/dist/churn/churnExtractor.js.map +1 -0
  105. package/dist/cli.js +1215 -11
  106. package/dist/cli.js.map +1 -1
  107. package/dist/codeIndexDB-enhanced.js +1 -1
  108. package/dist/codeIndexDB-enhanced.js.map +1 -1
  109. package/dist/codeIndexDB.d.ts +101 -0
  110. package/dist/codeIndexDB.d.ts.map +1 -1
  111. package/dist/codeIndexDB.js +661 -5
  112. package/dist/codeIndexDB.js.map +1 -1
  113. package/dist/config/configLoader.d.ts.map +1 -1
  114. package/dist/config/configLoader.js +95 -1
  115. package/dist/config/configLoader.js.map +1 -1
  116. package/dist/config/defaults.d.ts +72 -1
  117. package/dist/config/defaults.d.ts.map +1 -1
  118. package/dist/config/defaults.js +146 -15
  119. package/dist/config/defaults.js.map +1 -1
  120. package/dist/config/pathProfiles.d.ts +38 -0
  121. package/dist/config/pathProfiles.d.ts.map +1 -0
  122. package/dist/config/pathProfiles.js +47 -0
  123. package/dist/config/pathProfiles.js.map +1 -0
  124. package/dist/conventions/conventionMiner.d.ts +63 -0
  125. package/dist/conventions/conventionMiner.d.ts.map +1 -0
  126. package/dist/conventions/conventionMiner.js +696 -0
  127. package/dist/conventions/conventionMiner.js.map +1 -0
  128. package/dist/coverage/coverageService.d.ts +49 -0
  129. package/dist/coverage/coverageService.d.ts.map +1 -0
  130. package/dist/coverage/coverageService.js +117 -0
  131. package/dist/coverage/coverageService.js.map +1 -0
  132. package/dist/coverage/istanbulParser.d.ts +84 -0
  133. package/dist/coverage/istanbulParser.d.ts.map +1 -0
  134. package/dist/coverage/istanbulParser.js +120 -0
  135. package/dist/coverage/istanbulParser.js.map +1 -0
  136. package/dist/coverage/lcovParser.d.ts +40 -0
  137. package/dist/coverage/lcovParser.d.ts.map +1 -0
  138. package/dist/coverage/lcovParser.js +168 -0
  139. package/dist/coverage/lcovParser.js.map +1 -0
  140. package/dist/fingerprint.d.ts +17 -0
  141. package/dist/fingerprint.d.ts.map +1 -1
  142. package/dist/fingerprint.js +42 -0
  143. package/dist/fingerprint.js.map +1 -1
  144. package/dist/grammars/tree-sitter-css.wasm +0 -0
  145. package/dist/graph/blastRadius.d.ts +27 -0
  146. package/dist/graph/blastRadius.d.ts.map +1 -0
  147. package/dist/graph/blastRadius.js +150 -0
  148. package/dist/graph/blastRadius.js.map +1 -0
  149. package/dist/graph/callGraph.d.ts +78 -0
  150. package/dist/graph/callGraph.d.ts.map +1 -0
  151. package/dist/graph/callGraph.js +484 -0
  152. package/dist/graph/callGraph.js.map +1 -0
  153. package/dist/graph/importGraph.d.ts +91 -0
  154. package/dist/graph/importGraph.d.ts.map +1 -0
  155. package/dist/graph/importGraph.js +602 -0
  156. package/dist/graph/importGraph.js.map +1 -0
  157. package/dist/graph/outputFormatter.d.ts +55 -0
  158. package/dist/graph/outputFormatter.d.ts.map +1 -0
  159. package/dist/graph/outputFormatter.js +283 -0
  160. package/dist/graph/outputFormatter.js.map +1 -0
  161. package/dist/hooks/codex.d.ts +36 -1
  162. package/dist/hooks/codex.d.ts.map +1 -1
  163. package/dist/hooks/codex.js +88 -54
  164. package/dist/hooks/codex.js.map +1 -1
  165. package/dist/hotspots/hotspotScorer.d.ts +20 -0
  166. package/dist/hotspots/hotspotScorer.d.ts.map +1 -0
  167. package/dist/hotspots/hotspotScorer.js +202 -0
  168. package/dist/hotspots/hotspotScorer.js.map +1 -0
  169. package/dist/invariants/invariant-rules.schema.json +42 -1
  170. package/dist/invariants/ruleEngine.d.ts +4 -1
  171. package/dist/invariants/ruleEngine.d.ts.map +1 -1
  172. package/dist/invariants/ruleEngine.js +141 -5
  173. package/dist/invariants/ruleEngine.js.map +1 -1
  174. package/dist/invariants/ruleValidator.d.ts.map +1 -1
  175. package/dist/invariants/ruleValidator.js +62 -1
  176. package/dist/invariants/ruleValidator.js.map +1 -1
  177. package/dist/invariants/types.d.ts +23 -2
  178. package/dist/invariants/types.d.ts.map +1 -1
  179. package/dist/languages/UniversalAnalyzer.d.ts +1 -1
  180. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  181. package/dist/languages/UniversalAnalyzer.js +61 -4
  182. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  183. package/dist/languages/index.d.ts +1 -0
  184. package/dist/languages/index.d.ts.map +1 -1
  185. package/dist/languages/index.js +3 -0
  186. package/dist/languages/index.js.map +1 -1
  187. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +59 -0
  188. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -0
  189. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +236 -0
  190. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -0
  191. package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
  192. package/dist/languages/tree-sitter/parser.js +2 -0
  193. package/dist/languages/tree-sitter/parser.js.map +1 -1
  194. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  195. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +61 -10
  196. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  197. package/dist/ledger.d.ts +128 -0
  198. package/dist/ledger.d.ts.map +1 -0
  199. package/dist/ledger.js +345 -0
  200. package/dist/ledger.js.map +1 -0
  201. package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
  202. package/dist/mcp-tools/projectTasks.js +9 -14
  203. package/dist/mcp-tools/projectTasks.js.map +1 -1
  204. package/dist/mcp.js +0 -0
  205. package/dist/mcpAuditJobs.d.ts.map +1 -1
  206. package/dist/mcpAuditJobs.js +3 -0
  207. package/dist/mcpAuditJobs.js.map +1 -1
  208. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  209. package/dist/reporting/csvReportGenerator.js +7 -1
  210. package/dist/reporting/csvReportGenerator.js.map +1 -1
  211. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  212. package/dist/reporting/htmlReportGenerator.js +5 -1
  213. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  214. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  215. package/dist/reporting/jsonReportGenerator.js +10 -2
  216. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  217. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  218. package/dist/reporting/sarifReportGenerator.js +17 -17
  219. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  220. package/dist/scripts/runBench.d.ts +94 -0
  221. package/dist/scripts/runBench.d.ts.map +1 -0
  222. package/dist/scripts/runBench.js +1559 -0
  223. package/dist/scripts/runBench.js.map +1 -0
  224. package/dist/search/QueryParser.d.ts.map +1 -1
  225. package/dist/search/QueryParser.js +51 -4
  226. package/dist/search/QueryParser.js.map +1 -1
  227. package/dist/services/CodeMapGenerator.d.ts +33 -0
  228. package/dist/services/CodeMapGenerator.d.ts.map +1 -1
  229. package/dist/services/CodeMapGenerator.js +404 -0
  230. package/dist/services/CodeMapGenerator.js.map +1 -1
  231. package/dist/styles/normalizer.d.ts +37 -0
  232. package/dist/styles/normalizer.d.ts.map +1 -0
  233. package/dist/styles/normalizer.js +301 -0
  234. package/dist/styles/normalizer.js.map +1 -0
  235. package/dist/styles/styleExtractor.d.ts +38 -0
  236. package/dist/styles/styleExtractor.d.ts.map +1 -0
  237. package/dist/styles/styleExtractor.js +630 -0
  238. package/dist/styles/styleExtractor.js.map +1 -0
  239. package/dist/styles/styleIndexer.d.ts +35 -0
  240. package/dist/styles/styleIndexer.d.ts.map +1 -0
  241. package/dist/styles/styleIndexer.js +266 -0
  242. package/dist/styles/styleIndexer.js.map +1 -0
  243. package/dist/styles/tailwindConfigLoader.d.ts +38 -0
  244. package/dist/styles/tailwindConfigLoader.d.ts.map +1 -0
  245. package/dist/styles/tailwindConfigLoader.js +374 -0
  246. package/dist/styles/tailwindConfigLoader.js.map +1 -0
  247. package/dist/styles/tailwindExpander.d.ts +38 -0
  248. package/dist/styles/tailwindExpander.d.ts.map +1 -0
  249. package/dist/styles/tailwindExpander.js +637 -0
  250. package/dist/styles/tailwindExpander.js.map +1 -0
  251. package/dist/styles/types.d.ts +92 -0
  252. package/dist/styles/types.d.ts.map +1 -0
  253. package/dist/styles/types.js +10 -0
  254. package/dist/styles/types.js.map +1 -0
  255. package/dist/symbols.d.ts +21 -0
  256. package/dist/symbols.d.ts.map +1 -0
  257. package/dist/symbols.js +31 -0
  258. package/dist/symbols.js.map +1 -0
  259. package/dist/types.d.ts +326 -3
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js.map +1 -1
  262. package/package.json +9 -5
  263. package/plugin/.claude-plugin/plugin.json +1 -1
  264. package/plugin/scripts/hook-audit.sh +5 -1
  265. package/plugin/skills/code-auditor/SKILL.md +51 -0
  266. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts +0 -21
  267. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts.map +0 -1
  268. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js +0 -21
  269. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js.map +0 -1
  270. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts +0 -13
  271. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts.map +0 -1
  272. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js +0 -12
  273. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js.map +0 -1
  274. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts +0 -11
  275. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts.map +0 -1
  276. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js +0 -22
  277. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js.map +0 -1
  278. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts +0 -9
  279. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts.map +0 -1
  280. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js +0 -13
  281. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js.map +0 -1
  282. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts +0 -13
  283. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts.map +0 -1
  284. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js +0 -18
  285. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js.map +0 -1
  286. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts +0 -10
  287. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts.map +0 -1
  288. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js +0 -16
  289. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js.map +0 -1
  290. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts +0 -14
  291. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts.map +0 -1
  292. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js +0 -18
  293. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js.map +0 -1
  294. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts +0 -13
  295. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts.map +0 -1
  296. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js +0 -22
  297. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js.map +0 -1
  298. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts +0 -10
  299. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts.map +0 -1
  300. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js +0 -20
  301. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js.map +0 -1
  302. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts +0 -18
  303. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts.map +0 -1
  304. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js +0 -28
  305. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js.map +0 -1
  306. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts +0 -11
  307. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts.map +0 -1
  308. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js +0 -31
  309. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js.map +0 -1
  310. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts +0 -11
  311. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts.map +0 -1
  312. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js +0 -18
  313. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js.map +0 -1
  314. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts +0 -13
  315. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts.map +0 -1
  316. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js +0 -61
  317. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js.map +0 -1
  318. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts +0 -10
  319. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts.map +0 -1
  320. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js +0 -15
  321. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js.map +0 -1
  322. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts +0 -11
  323. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts.map +0 -1
  324. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js +0 -49
  325. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js.map +0 -1
  326. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts +0 -10
  327. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts.map +0 -1
  328. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js +0 -19
  329. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js.map +0 -1
  330. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts +0 -37
  331. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts.map +0 -1
  332. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js +0 -73
  333. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js.map +0 -1
  334. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts +0 -12
  335. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts.map +0 -1
  336. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js +0 -82
  337. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js.map +0 -1
  338. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts +0 -10
  339. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts.map +0 -1
  340. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js +0 -15
  341. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js.map +0 -1
  342. package/dist/analyzers/dataAccessAnalyzer.d.ts +0 -46
  343. package/dist/analyzers/dataAccessAnalyzer.d.ts.map +0 -1
  344. package/dist/analyzers/dataAccessAnalyzer.js +0 -474
  345. package/dist/analyzers/dataAccessAnalyzer.js.map +0 -1
  346. package/dist/analyzers/dryAnalyzer.d.ts +0 -31
  347. package/dist/analyzers/dryAnalyzer.d.ts.map +0 -1
  348. package/dist/analyzers/dryAnalyzer.js +0 -734
  349. package/dist/analyzers/dryAnalyzer.js.map +0 -1
  350. package/dist/analyzers/schemaAnalyzer.d.ts +0 -23
  351. package/dist/analyzers/schemaAnalyzer.d.ts.map +0 -1
  352. package/dist/analyzers/schemaAnalyzer.js +0 -564
  353. package/dist/analyzers/schemaAnalyzer.js.map +0 -1
  354. package/dist/analyzers/solidAnalyzer.d.ts +0 -33
  355. package/dist/analyzers/solidAnalyzer.d.ts.map +0 -1
  356. package/dist/analyzers/solidAnalyzer.js +0 -847
  357. package/dist/analyzers/solidAnalyzer.js.map +0 -1
@@ -0,0 +1,696 @@
1
+ /**
2
+ * Spec 12 — Convention Mining
3
+ *
4
+ * Mines five domains of unwritten codebase conventions from the SQLite function
5
+ * index. All mining runs on the existing `functions` and `function_calls` tables;
6
+ * no new parsing infrastructure required.
7
+ *
8
+ * Domains:
9
+ * 1. usage-pair — calls that always co-occur (from function_calls table)
10
+ * 2. import-form — dominant import style per (source, directory)
11
+ * 3. error-handling — dominant error-handling shape per directory
12
+ * 4. export-shape — dominant export style per directory
13
+ * 5. naming — dominant exported-symbol casing per directory
14
+ */
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import * as crypto from 'crypto';
18
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
19
+ /** Escape regex special characters in a string. */
20
+ export function escapeRegex(s) {
21
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
22
+ }
23
+ /** Detect whether a string contains non-Latin-script characters (Spec 21 R5.4). */
24
+ export function hasNonLatinChars(name) {
25
+ // Latin script covers Basic Latin (U+0000–U+007F), Latin-1 Supplement
26
+ // (U+0080–U+00FF), and Latin Extended-A/B (U+0100–U+024F). Allow digits,
27
+ // underscores, and dollar signs as well.
28
+ for (const ch of name) {
29
+ const cp = ch.codePointAt(0);
30
+ // Digits, underscore, dollar sign
31
+ if (ch >= '0' && ch <= '9')
32
+ continue;
33
+ if (ch === '_' || ch === '$')
34
+ continue;
35
+ // Latin ranges
36
+ if (cp >= 0x0041 && cp <= 0x005A)
37
+ continue; // A–Z
38
+ if (cp >= 0x0061 && cp <= 0x007A)
39
+ continue; // a–z
40
+ if (cp >= 0x00C0 && cp <= 0x024F)
41
+ continue; // Latin-1 Supplement + Extended
42
+ // Any character outside Latin ranges → non-Latin
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+ /** Classify a symbol name into a casing convention. Returns null for
48
+ * non-Latin, unclassifiable, or ambiguous names. */
49
+ export function detectCase(name) {
50
+ if (!name || name.length === 0)
51
+ return null;
52
+ if (hasNonLatinChars(name))
53
+ return null;
54
+ // UPPER_SNAKE: all-uppercase with underscores (e.g. MAX_BUFFER_SIZE, DEBUG)
55
+ if (/^[A-Z][A-Z0-9]*(_[A-Z][A-Z0-9]*)*$/.test(name))
56
+ return 'UPPER_SNAKE';
57
+ // PascalCase: starts uppercase, no underscores or dashes (e.g. UserService)
58
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(name))
59
+ return 'PascalCase';
60
+ // camelCase: starts lowercase, no underscores or dashes (e.g. useState)
61
+ if (/^[a-z][a-zA-Z0-9]*$/.test(name))
62
+ return 'camelCase';
63
+ // snake_case: all-lowercase with underscores (e.g. handle_click)
64
+ if (/^[a-z][a-z0-9]*(_[a-z][a-z0-9]*)*$/.test(name))
65
+ return 'snake_case';
66
+ // kebab-case: all-lowercase with dashes (e.g. user-service)
67
+ if (/^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/.test(name))
68
+ return 'kebab-case';
69
+ return null;
70
+ }
71
+ /**
72
+ * Parse import statements from raw file content.
73
+ *
74
+ * Handles: import default, import named, import namespace, import side-effect,
75
+ * require default, require destructured.
76
+ * Multi-line imports supported via {}-accumulation.
77
+ */
78
+ export function parseFileImports(content) {
79
+ const results = [];
80
+ const lines = content.split('\n');
81
+ let multiLineBuf = null;
82
+ let multiLineStart = 0;
83
+ for (let i = 0; i < lines.length; i++) {
84
+ let line = lines[i].trim();
85
+ const lineNum = i + 1;
86
+ // Skip comments and empty lines
87
+ if (!line || line.startsWith('//') || line.startsWith('/*') || line.startsWith('*'))
88
+ continue;
89
+ // Handle multi-line imports
90
+ if (multiLineBuf !== null) {
91
+ multiLineBuf += ' ' + line;
92
+ if (line.includes('}') || line.includes("'") || line.includes('"')) {
93
+ line = multiLineBuf;
94
+ multiLineBuf = null;
95
+ }
96
+ else {
97
+ continue;
98
+ }
99
+ }
100
+ // Detect multi-line import start: "import {" with no closing "}" and no "from" yet
101
+ if (line.startsWith('import') && line.includes('{') && !line.includes('}') && !line.includes('from')) {
102
+ multiLineBuf = line;
103
+ multiLineStart = lineNum;
104
+ continue;
105
+ }
106
+ const effectiveLine = multiLineStart > 0 ? multiLineStart : lineNum;
107
+ multiLineStart = 0; // reset after use
108
+ // import 'source' or import "source" (side-effect)
109
+ const sideEffectMatch = line.match(/^import\s+['"]([^'"]+)['"]\s*;?\s*$/);
110
+ if (sideEffectMatch) {
111
+ results.push({ source: sideEffectMatch[1], localNames: [], form: 'side-effect', line: effectiveLine });
112
+ continue;
113
+ }
114
+ // import * as Name from 'source' (namespace)
115
+ const nsMatch = line.match(/^import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/);
116
+ if (nsMatch) {
117
+ results.push({ source: nsMatch[2], localNames: [nsMatch[1]], form: 'namespace', line: effectiveLine });
118
+ continue;
119
+ }
120
+ // import DefaultName from 'source' (default)
121
+ // import DefaultName, { ... } from 'source' (default + named) — classify as default
122
+ const defaultMatch = line.match(/^import\s+(\w+)\s*,?\s*(?:\{[^}]*\})?\s*from\s+['"]([^'"]+)['"]/);
123
+ if (defaultMatch) {
124
+ const namedPart = line.match(/\{([^}]+)\}/);
125
+ const namedNames = namedPart
126
+ ? namedPart[1]
127
+ .split(',')
128
+ .map((n) => n.trim().replace(/\s+as\s+\w+\s*$/, '').trim())
129
+ .filter(Boolean)
130
+ : [];
131
+ const localNames = [defaultMatch[1]];
132
+ // Strip "as" aliases (keep local name = what comes after "as", else the imported name)
133
+ if (namedPart) {
134
+ const resolved = namedPart[1].split(',').map((n) => {
135
+ const trimmed = n.trim();
136
+ const asMatch = trimmed.match(/^(\w+)\s+as\s+(\w+)$/);
137
+ return asMatch ? asMatch[2] : trimmed;
138
+ }).filter(Boolean);
139
+ localNames.push(...resolved);
140
+ }
141
+ results.push({ source: defaultMatch[2], localNames, form: 'default', line: effectiveLine });
142
+ continue;
143
+ }
144
+ // import { X, Y } from 'source' (named only)
145
+ const namedMatch = line.match(/^import\s+\{([^}]+)\}\s*from\s+['"]([^'"]+)['"]/);
146
+ if (namedMatch) {
147
+ const names = namedMatch[1]
148
+ .split(',')
149
+ .map((n) => {
150
+ const trimmed = n.trim();
151
+ const asMatch = trimmed.match(/^(\w+)\s+as\s+(\w+)$/);
152
+ return asMatch ? asMatch[2] : trimmed;
153
+ })
154
+ .filter(Boolean);
155
+ results.push({ source: namedMatch[2], localNames: names, form: 'named', line: effectiveLine });
156
+ continue;
157
+ }
158
+ // const X = require('source') (require default)
159
+ const reqDefMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
160
+ if (reqDefMatch) {
161
+ results.push({ source: reqDefMatch[2], localNames: [reqDefMatch[1]], form: 'require', line: effectiveLine });
162
+ continue;
163
+ }
164
+ // const { X, Y } = require('source') (require named)
165
+ const reqNamedMatch = line.match(/(?:const|let|var)\s*\{([^}]+)\}\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
166
+ if (reqNamedMatch) {
167
+ const names = reqNamedMatch[1]
168
+ .split(',')
169
+ .map((n) => {
170
+ const trimmed = n.trim();
171
+ // Handle renaming: { foo: bar } → local name is 'bar'
172
+ const colonMatch = trimmed.match(/^(\w+)\s*:\s*(\w+)$/);
173
+ return colonMatch ? colonMatch[2] : trimmed;
174
+ })
175
+ .filter(Boolean);
176
+ results.push({ source: reqNamedMatch[2], localNames: names, form: 'named', line: effectiveLine });
177
+ continue;
178
+ }
179
+ }
180
+ return results;
181
+ }
182
+ /**
183
+ * Parse the export form for a specific exported function from its source file.
184
+ * Returns null if we can't determine the form.
185
+ */
186
+ export function detectExportForm(filePath, functionName) {
187
+ let content;
188
+ try {
189
+ content = fs.readFileSync(filePath, 'utf-8');
190
+ }
191
+ catch {
192
+ return null;
193
+ }
194
+ const escaped = escapeRegex(functionName);
195
+ // export default function Name / export default class Name / export default Name
196
+ const defaultPatterns = [
197
+ new RegExp(`export\\s+default\\s+(?:function|class|const|let|var)?\\s*${escaped}\\b`),
198
+ new RegExp(`export\\s*\\{\\s*${escaped}\\s+as\\s+default\\s*\\}`),
199
+ new RegExp(`export\\s+default\\s+\\{[^}]*\\b${escaped}\\b`), // export default { Name } (re-export)
200
+ ];
201
+ for (const pat of defaultPatterns) {
202
+ if (pat.test(content))
203
+ return 'default';
204
+ }
205
+ // export function Name / export class Name / export const Name / export let Name / export var Name
206
+ const namedDirect = new RegExp(`export\\s+(?:function|class|const|let|var)\\s+${escaped}\\b`);
207
+ if (namedDirect.test(content))
208
+ return 'named';
209
+ // export { Name } or export { Name as Alias }
210
+ const namedList = new RegExp(`export\\s*\\{[^}]*\\b${escaped}\\b[^}]*\\}`);
211
+ if (namedList.test(content))
212
+ return 'named';
213
+ return null;
214
+ }
215
+ /**
216
+ * Detect the error-handling shape used in a function body.
217
+ * Returns null if no error handling is present (function should not be counted
218
+ * in the corpus for mode computation).
219
+ */
220
+ export function detectErrorHandlingShape(body) {
221
+ if (!body)
222
+ return null;
223
+ if (/\btry\s*\{/.test(body))
224
+ return 'try-catch';
225
+ if (/\.catch\s*\(/.test(body))
226
+ return 'promise-catch';
227
+ if (/\bif\s*\(\s*err/.test(body))
228
+ return 'if-err';
229
+ if (/\b\.success\b/.test(body))
230
+ return 'go-style';
231
+ return null;
232
+ }
233
+ /** Compute MD5 hash for change detection. */
234
+ function computeHash(inputs) {
235
+ return crypto.createHash('md5').update(JSON.stringify(inputs)).digest('hex');
236
+ }
237
+ /** Cap the number of conventions per domain. */
238
+ function capPerDomain(conventions, max) {
239
+ const byDomain = new Map();
240
+ for (const c of conventions) {
241
+ const list = byDomain.get(c.domain) ?? [];
242
+ list.push(c);
243
+ byDomain.set(c.domain, list);
244
+ }
245
+ const result = [];
246
+ for (const [, list] of byDomain) {
247
+ result.push(...list.slice(0, max));
248
+ }
249
+ return result;
250
+ }
251
+ // ─── Domain Miners ───────────────────────────────────────────────────────────
252
+ /**
253
+ * Mine `usage-pair` conventions.
254
+ *
255
+ * For each function F with call-set {A, B, C}, each call A is a potential
256
+ * antecedent. For each antecedent A, find all functions that call A. Among
257
+ * those, compute which other calls X co-occur with confidence ≥ pairConfidence
258
+ * and support ≥ minCorpus.
259
+ */
260
+ function mineUsagePairs(db, config) {
261
+ const conventions = [];
262
+ // Build per-function call sets
263
+ const rows = db
264
+ .prepare(`SELECT f.id, f.name as func_name, f.file_path, f.line_number,
265
+ f.metadata_json
266
+ FROM functions f`)
267
+ .all();
268
+ // callerId -> Set<calleeName>
269
+ const callSets = new Map();
270
+ const callRows = db
271
+ .prepare(`SELECT caller_id, callee_name FROM function_calls`)
272
+ .all();
273
+ for (const cr of callRows) {
274
+ if (!callSets.has(cr.caller_id))
275
+ callSets.set(cr.caller_id, new Set());
276
+ callSets.get(cr.caller_id).add(cr.callee_name);
277
+ }
278
+ // For each unique callee (potential antecedent), find all callers
279
+ // antecedentName -> Set<callerId>
280
+ const antecedentCallers = new Map();
281
+ for (const cr of callRows) {
282
+ if (!antecedentCallers.has(cr.callee_name))
283
+ antecedentCallers.set(cr.callee_name, new Set());
284
+ antecedentCallers.get(cr.callee_name).add(cr.caller_id);
285
+ }
286
+ // For each antecedent A, among A-callers, compute co-occurring calls X
287
+ for (const [antecedent, callerIds] of antecedentCallers) {
288
+ const total = callerIds.size;
289
+ if (total < config.minCorpus)
290
+ continue;
291
+ // Count how many A-callers also call each other function
292
+ const coOccurCounts = new Map();
293
+ for (const cid of callerIds) {
294
+ const callSet = callSets.get(cid);
295
+ if (!callSet)
296
+ continue;
297
+ for (const callee of callSet) {
298
+ if (callee === antecedent)
299
+ continue;
300
+ coOccurCounts.set(callee, (coOccurCounts.get(callee) ?? 0) + 1);
301
+ }
302
+ }
303
+ for (const [consequent, support] of coOccurCounts) {
304
+ const confidence = support / total;
305
+ if (confidence >= config.pairConfidence && support >= config.minCorpus) {
306
+ // Find an exemplar: a function that calls both A and X
307
+ let exemplarFile = null;
308
+ let exemplarLine = null;
309
+ for (const cid of callerIds) {
310
+ const cs = callSets.get(cid);
311
+ if (cs && cs.has(consequent)) {
312
+ const funcRow = rows.find((r) => r.id === cid);
313
+ if (funcRow) {
314
+ exemplarFile = funcRow.file_path;
315
+ exemplarLine = funcRow.line_number;
316
+ break;
317
+ }
318
+ }
319
+ }
320
+ conventions.push({
321
+ domain: 'usage-pair',
322
+ rule_id: 'conventions/usage-pair',
323
+ antecedent,
324
+ consequent,
325
+ pattern: null,
326
+ directory: null,
327
+ file_path: null,
328
+ line: null,
329
+ support,
330
+ total_cases: total,
331
+ confidence: Math.round(confidence * 10000) / 10000,
332
+ exemplar_file: exemplarFile,
333
+ exemplar_line: exemplarLine,
334
+ hash: computeHash([antecedent, consequent, support, total]),
335
+ });
336
+ }
337
+ }
338
+ }
339
+ return conventions;
340
+ }
341
+ /**
342
+ * Mine `import-form` conventions.
343
+ *
344
+ * Per (source, directory) pair, compute the dominant import form. Reads source
345
+ * files from disk to parse import statements, since import form data is not
346
+ * persisted in the functions table.
347
+ */
348
+ function mineImportForm(db, config, projectRoot) {
349
+ const conventions = [];
350
+ // Get unique (file_path, directory) pairs
351
+ const rows = db
352
+ .prepare(`SELECT DISTINCT file_path,
353
+ substr(file_path, 1, length(file_path) - length(replace(file_path, '/', '')) - 1) as dir_part
354
+ FROM functions
355
+ WHERE file_path IS NOT NULL`)
356
+ .all();
357
+ // Deduplicate files
358
+ const seen = new Set();
359
+ // (source, directory) -> Map<form, count>
360
+ const formCounts = new Map();
361
+ // (source, directory) -> exemplar
362
+ const exemplars = new Map();
363
+ for (const row of rows) {
364
+ if (seen.has(row.file_path))
365
+ continue;
366
+ seen.add(row.file_path);
367
+ const fullPath = projectRoot ? path.join(projectRoot, row.file_path) : row.file_path;
368
+ let content;
369
+ try {
370
+ content = fs.readFileSync(fullPath, 'utf-8');
371
+ }
372
+ catch {
373
+ continue;
374
+ }
375
+ const directory = row.dir_part ? path.dirname(row.file_path) : '.';
376
+ const imports = parseFileImports(content);
377
+ for (const imp of imports) {
378
+ const source = imp.source;
379
+ const form = imp.form;
380
+ const key = `${source}::${directory}`;
381
+ if (!formCounts.has(key))
382
+ formCounts.set(key, new Map());
383
+ const fc = formCounts.get(key);
384
+ fc.set(form, (fc.get(form) ?? 0) + 1);
385
+ // Track exemplar (first one wins)
386
+ if (!exemplars.has(key)) {
387
+ exemplars.set(key, { file: row.file_path, line: 0, form });
388
+ }
389
+ }
390
+ }
391
+ // For each (source, directory) pair, find dominant form
392
+ for (const [key, fc] of formCounts) {
393
+ const [source, directory] = key.split('::');
394
+ const total = [...fc.values()].reduce((s, c) => s + c, 0);
395
+ if (total < config.minCorpus)
396
+ continue;
397
+ // Find the mode (most common form)
398
+ let maxCount = 0;
399
+ let dominantForm = '';
400
+ for (const [form, count] of fc) {
401
+ if (count > maxCount) {
402
+ maxCount = count;
403
+ dominantForm = form;
404
+ }
405
+ }
406
+ const modeShare = maxCount / total;
407
+ if (modeShare >= config.modeShare && maxCount >= config.minCorpus) {
408
+ // List the minority forms (for reference)
409
+ const minorityForms = [...fc.entries()]
410
+ .filter(([f]) => f !== dominantForm)
411
+ .map(([f, c]) => `${f}:${c}`)
412
+ .join(',');
413
+ const exemplar = exemplars.get(key);
414
+ conventions.push({
415
+ domain: 'import-form',
416
+ rule_id: 'conventions/import-form',
417
+ antecedent: source,
418
+ consequent: dominantForm,
419
+ pattern: minorityForms || null,
420
+ directory,
421
+ file_path: null,
422
+ line: null,
423
+ support: maxCount,
424
+ total_cases: total,
425
+ confidence: Math.round(modeShare * 10000) / 10000,
426
+ exemplar_file: exemplar?.file ?? null,
427
+ exemplar_line: exemplar?.line ?? null,
428
+ hash: computeHash([source, directory, dominantForm, maxCount, total]),
429
+ });
430
+ }
431
+ }
432
+ return conventions;
433
+ }
434
+ /**
435
+ * Mine `error-handling` conventions.
436
+ *
437
+ * Per directory, compute the dominant error-handling shape among functions
438
+ * that *have* error handling. Functions without any error handling are
439
+ * excluded from the corpus and never flagged as deviants.
440
+ */
441
+ function mineErrorHandling(db, config) {
442
+ const conventions = [];
443
+ const rows = db
444
+ .prepare(`SELECT id, name, file_path, line_number, metadata_json
445
+ FROM functions
446
+ WHERE metadata_json IS NOT NULL`)
447
+ .all();
448
+ // directory -> Map<shape, count>
449
+ const dirShapes = new Map();
450
+ // directory -> exemplar info
451
+ const dirExemplars = new Map();
452
+ for (const row of rows) {
453
+ let metadata;
454
+ try {
455
+ metadata = JSON.parse(row.metadata_json);
456
+ }
457
+ catch {
458
+ continue;
459
+ }
460
+ const body = metadata.body;
461
+ const shape = detectErrorHandlingShape(body);
462
+ if (!shape)
463
+ continue; // no error handling → skip
464
+ const directory = path.dirname(row.file_path) || '.';
465
+ if (!dirShapes.has(directory))
466
+ dirShapes.set(directory, new Map());
467
+ const shapes = dirShapes.get(directory);
468
+ shapes.set(shape, (shapes.get(shape) ?? 0) + 1);
469
+ if (!dirExemplars.has(directory)) {
470
+ dirExemplars.set(directory, {
471
+ file: row.file_path,
472
+ line: row.line_number,
473
+ shape,
474
+ });
475
+ }
476
+ }
477
+ for (const [directory, shapes] of dirShapes) {
478
+ const total = [...shapes.values()].reduce((s, c) => s + c, 0);
479
+ if (total < config.minCorpus)
480
+ continue;
481
+ let maxCount = 0;
482
+ let dominantShape = '';
483
+ for (const [shape, count] of shapes) {
484
+ if (count > maxCount) {
485
+ maxCount = count;
486
+ dominantShape = shape;
487
+ }
488
+ }
489
+ const modeShare = maxCount / total;
490
+ if (modeShare >= config.modeShare && maxCount >= config.minCorpus) {
491
+ const minorityShapes = [...shapes.entries()]
492
+ .filter(([s]) => s !== dominantShape)
493
+ .map(([s, c]) => `${s}:${c}`)
494
+ .join(',');
495
+ const exemplar = dirExemplars.get(directory);
496
+ conventions.push({
497
+ domain: 'error-handling',
498
+ rule_id: 'conventions/error-handling',
499
+ antecedent: null,
500
+ consequent: null,
501
+ pattern: dominantShape,
502
+ directory,
503
+ file_path: null,
504
+ line: null,
505
+ support: maxCount,
506
+ total_cases: total,
507
+ confidence: Math.round(modeShare * 10000) / 10000,
508
+ exemplar_file: exemplar?.file ?? null,
509
+ exemplar_line: exemplar?.line ?? null,
510
+ hash: computeHash([directory, dominantShape, maxCount, total]),
511
+ });
512
+ }
513
+ }
514
+ return conventions;
515
+ }
516
+ /**
517
+ * Mine `export-shape` conventions.
518
+ *
519
+ * Per directory, compute the dominant export style (default vs named) among
520
+ * exported functions. Reads source files from disk to determine the export
521
+ * form, since it is not stored in the functions table metadata.
522
+ */
523
+ function mineExportShape(db, config, projectRoot) {
524
+ const conventions = [];
525
+ const rows = db
526
+ .prepare(`SELECT id, name, file_path, line_number
527
+ FROM functions
528
+ WHERE is_exported = 1`)
529
+ .all();
530
+ // directory -> Map<form, count>
531
+ const dirForms = new Map();
532
+ const dirExemplars = new Map();
533
+ for (const row of rows) {
534
+ const fullPath = projectRoot ? path.join(projectRoot, row.file_path) : row.file_path;
535
+ const form = detectExportForm(fullPath, row.name);
536
+ if (!form)
537
+ continue;
538
+ const directory = path.dirname(row.file_path) || '.';
539
+ if (!dirForms.has(directory))
540
+ dirForms.set(directory, new Map());
541
+ const forms = dirForms.get(directory);
542
+ forms.set(form, (forms.get(form) ?? 0) + 1);
543
+ if (!dirExemplars.has(directory)) {
544
+ dirExemplars.set(directory, {
545
+ file: row.file_path,
546
+ line: row.line_number,
547
+ form,
548
+ });
549
+ }
550
+ }
551
+ for (const [directory, forms] of dirForms) {
552
+ const total = [...forms.values()].reduce((s, c) => s + c, 0);
553
+ if (total < config.minCorpus)
554
+ continue;
555
+ let maxCount = 0;
556
+ let dominantForm = '';
557
+ for (const [form, count] of forms) {
558
+ if (count > maxCount) {
559
+ maxCount = count;
560
+ dominantForm = form;
561
+ }
562
+ }
563
+ const modeShare = maxCount / total;
564
+ if (modeShare >= config.modeShare && maxCount >= config.minCorpus) {
565
+ const minorityForms = [...forms.entries()]
566
+ .filter(([f]) => f !== dominantForm)
567
+ .map(([f, c]) => `${f}:${c}`)
568
+ .join(',');
569
+ const exemplar = dirExemplars.get(directory);
570
+ conventions.push({
571
+ domain: 'export-shape',
572
+ rule_id: 'conventions/export-shape',
573
+ antecedent: null,
574
+ consequent: null,
575
+ pattern: dominantForm,
576
+ directory,
577
+ file_path: null,
578
+ line: null,
579
+ support: maxCount,
580
+ total_cases: total,
581
+ confidence: Math.round(modeShare * 10000) / 10000,
582
+ exemplar_file: exemplar?.file ?? null,
583
+ exemplar_line: exemplar?.line ?? null,
584
+ hash: computeHash([directory, dominantForm, maxCount, total]),
585
+ });
586
+ }
587
+ }
588
+ return conventions;
589
+ }
590
+ /**
591
+ * Mine `naming` conventions.
592
+ *
593
+ * Per directory, compute the dominant casing convention for exported symbols.
594
+ * Non-Latin identifiers are excluded (Spec 21 R5.4).
595
+ */
596
+ function mineNaming(db, config) {
597
+ const conventions = [];
598
+ const rows = db
599
+ .prepare(`SELECT id, name, file_path, line_number
600
+ FROM functions
601
+ WHERE is_exported = 1`)
602
+ .all();
603
+ // directory -> Map<case, count>
604
+ const dirCases = new Map();
605
+ const dirExemplars = new Map();
606
+ for (const row of rows) {
607
+ const casing = detectCase(row.name);
608
+ if (!casing)
609
+ continue; // non-Latin or unclassifiable → skip
610
+ const directory = path.dirname(row.file_path) || '.';
611
+ if (!dirCases.has(directory))
612
+ dirCases.set(directory, new Map());
613
+ const cases = dirCases.get(directory);
614
+ cases.set(casing, (cases.get(casing) ?? 0) + 1);
615
+ if (!dirExemplars.has(directory)) {
616
+ dirExemplars.set(directory, {
617
+ file: row.file_path,
618
+ line: row.line_number,
619
+ casing,
620
+ });
621
+ }
622
+ }
623
+ for (const [directory, cases] of dirCases) {
624
+ const total = [...cases.values()].reduce((s, c) => s + c, 0);
625
+ if (total < config.minCorpus)
626
+ continue;
627
+ let maxCount = 0;
628
+ let dominantCase = '';
629
+ for (const [casing, count] of cases) {
630
+ if (count > maxCount) {
631
+ maxCount = count;
632
+ dominantCase = casing;
633
+ }
634
+ }
635
+ const modeShare = maxCount / total;
636
+ if (modeShare >= config.modeShare && maxCount >= config.minCorpus) {
637
+ const minorityCases = [...cases.entries()]
638
+ .filter(([c]) => c !== dominantCase)
639
+ .map(([c, n]) => `${c}:${n}`)
640
+ .join(',');
641
+ const exemplar = dirExemplars.get(directory);
642
+ conventions.push({
643
+ domain: 'naming',
644
+ rule_id: 'conventions/naming',
645
+ antecedent: null,
646
+ consequent: null,
647
+ pattern: dominantCase,
648
+ directory,
649
+ file_path: null,
650
+ line: null,
651
+ support: maxCount,
652
+ total_cases: total,
653
+ confidence: Math.round(modeShare * 10000) / 10000,
654
+ exemplar_file: exemplar?.file ?? null,
655
+ exemplar_line: exemplar?.line ?? null,
656
+ hash: computeHash([directory, dominantCase, maxCount, total]),
657
+ });
658
+ }
659
+ }
660
+ return conventions;
661
+ }
662
+ // ─── Public API ──────────────────────────────────────────────────────────────
663
+ /**
664
+ * Compute a content hash of the miner inputs for change detection.
665
+ * Callers can store this in the meta table to skip re-mining when unchanged.
666
+ */
667
+ export function computeMinerInputHash(db, config) {
668
+ const funcCount = db.prepare('SELECT COUNT(*) as c FROM functions').get().c;
669
+ const callCount = db.prepare('SELECT COUNT(*) as c FROM function_calls').get().c;
670
+ return computeHash([funcCount, callCount, config]);
671
+ }
672
+ /**
673
+ * Mine all five convention domains from the SQLite index.
674
+ *
675
+ * @param db The better-sqlite3 database instance.
676
+ * @param config Threshold configuration for the miner.
677
+ * @param projectRoot Optional project root — required for import-form and
678
+ * export-shape mining to resolve file paths.
679
+ * @returns Array of mined conventions (uncapped — caller should upsert).
680
+ */
681
+ export function mineConventions(db, config, projectRoot) {
682
+ const conventions = [];
683
+ // 1. Usage Pairs
684
+ conventions.push(...mineUsagePairs(db, config));
685
+ // 2. Import Form (reads files from disk)
686
+ conventions.push(...mineImportForm(db, config, projectRoot));
687
+ // 3. Error Handling
688
+ conventions.push(...mineErrorHandling(db, config));
689
+ // 4. Export Shape (reads files from disk)
690
+ conventions.push(...mineExportShape(db, config, projectRoot));
691
+ // 5. Naming
692
+ conventions.push(...mineNaming(db, config));
693
+ // Cap per domain
694
+ return capPerDomain(conventions, config.maxConventionsPerDomain);
695
+ }
696
+ //# sourceMappingURL=conventionMiner.js.map