code-auditor-mcp 3.1.0 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +665 -1
- package/README.md +85 -11
- package/dist/__test_tmp__/spec18_test.d.ts +2 -0
- package/dist/__test_tmp__/spec18_test.d.ts.map +1 -0
- package/dist/__test_tmp__/spec18_test.js +5 -0
- package/dist/__test_tmp__/spec18_test.js.map +1 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts +6 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts.map +1 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.js +12 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.js.map +1 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts +7 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts.map +1 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.js +13 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.js.map +1 -0
- package/dist/analyzers/analyzerUtils.js +2 -2
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +2 -4
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts +3 -0
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js +3 -5
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +0 -3
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +2 -5
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +83 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +611 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -0
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +8 -4
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.d.ts +41 -0
- package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -0
- package/dist/analyzers/orm/adapterRegistry.js +73 -0
- package/dist/analyzers/orm/adapterRegistry.js.map +1 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts +42 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -0
- package/dist/analyzers/orm/drizzleAdapter.js +166 -0
- package/dist/analyzers/orm/drizzleAdapter.js.map +1 -0
- package/dist/analyzers/orm/index.d.ts +14 -0
- package/dist/analyzers/orm/index.d.ts.map +1 -0
- package/dist/analyzers/orm/index.js +24 -0
- package/dist/analyzers/orm/index.js.map +1 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts +45 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -0
- package/dist/analyzers/orm/prismaAdapter.js +170 -0
- package/dist/analyzers/orm/prismaAdapter.js.map +1 -0
- package/dist/analyzers/orm/types.d.ts +63 -0
- package/dist/analyzers/orm/types.d.ts.map +1 -0
- package/dist/analyzers/orm/types.js +10 -0
- package/dist/analyzers/orm/types.js.map +1 -0
- package/dist/analyzers/provenance.d.ts +133 -0
- package/dist/analyzers/provenance.d.ts.map +1 -0
- package/dist/analyzers/provenance.js +1041 -0
- package/dist/analyzers/provenance.js.map +1 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +117 -1
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +38 -0
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -0
- package/dist/analyzers/ruleRegistry.js +123 -0
- package/dist/analyzers/ruleRegistry.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +58 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +445 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -0
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +52 -0
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +108 -26
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +64 -0
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +259 -103
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +5 -15
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +10 -10
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +25 -0
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +250 -59
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +103 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +750 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +483 -3
- package/dist/auditRunner.js.map +1 -1
- package/dist/baseline.d.ts +98 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +184 -0
- package/dist/baseline.js.map +1 -0
- package/dist/churn/churnExtractor.d.ts +32 -0
- package/dist/churn/churnExtractor.d.ts.map +1 -0
- package/dist/churn/churnExtractor.js +261 -0
- package/dist/churn/churnExtractor.js.map +1 -0
- package/dist/cli.js +1215 -11
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB-enhanced.js +1 -1
- package/dist/codeIndexDB-enhanced.js.map +1 -1
- package/dist/codeIndexDB.d.ts +101 -0
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +661 -5
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +95 -1
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +72 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +146 -15
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/pathProfiles.d.ts +38 -0
- package/dist/config/pathProfiles.d.ts.map +1 -0
- package/dist/config/pathProfiles.js +47 -0
- package/dist/config/pathProfiles.js.map +1 -0
- package/dist/conventions/conventionMiner.d.ts +63 -0
- package/dist/conventions/conventionMiner.d.ts.map +1 -0
- package/dist/conventions/conventionMiner.js +696 -0
- package/dist/conventions/conventionMiner.js.map +1 -0
- package/dist/coverage/coverageService.d.ts +49 -0
- package/dist/coverage/coverageService.d.ts.map +1 -0
- package/dist/coverage/coverageService.js +117 -0
- package/dist/coverage/coverageService.js.map +1 -0
- package/dist/coverage/istanbulParser.d.ts +84 -0
- package/dist/coverage/istanbulParser.d.ts.map +1 -0
- package/dist/coverage/istanbulParser.js +120 -0
- package/dist/coverage/istanbulParser.js.map +1 -0
- package/dist/coverage/lcovParser.d.ts +40 -0
- package/dist/coverage/lcovParser.d.ts.map +1 -0
- package/dist/coverage/lcovParser.js +168 -0
- package/dist/coverage/lcovParser.js.map +1 -0
- package/dist/fingerprint.d.ts +17 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +42 -0
- package/dist/fingerprint.js.map +1 -1
- package/dist/grammars/tree-sitter-css.wasm +0 -0
- package/dist/graph/blastRadius.d.ts +27 -0
- package/dist/graph/blastRadius.d.ts.map +1 -0
- package/dist/graph/blastRadius.js +150 -0
- package/dist/graph/blastRadius.js.map +1 -0
- package/dist/graph/callGraph.d.ts +78 -0
- package/dist/graph/callGraph.d.ts.map +1 -0
- package/dist/graph/callGraph.js +484 -0
- package/dist/graph/callGraph.js.map +1 -0
- package/dist/graph/importGraph.d.ts +91 -0
- package/dist/graph/importGraph.d.ts.map +1 -0
- package/dist/graph/importGraph.js +602 -0
- package/dist/graph/importGraph.js.map +1 -0
- package/dist/graph/outputFormatter.d.ts +55 -0
- package/dist/graph/outputFormatter.d.ts.map +1 -0
- package/dist/graph/outputFormatter.js +283 -0
- package/dist/graph/outputFormatter.js.map +1 -0
- package/dist/hooks/codex.d.ts +36 -1
- package/dist/hooks/codex.d.ts.map +1 -1
- package/dist/hooks/codex.js +88 -54
- package/dist/hooks/codex.js.map +1 -1
- package/dist/hotspots/hotspotScorer.d.ts +20 -0
- package/dist/hotspots/hotspotScorer.d.ts.map +1 -0
- package/dist/hotspots/hotspotScorer.js +202 -0
- package/dist/hotspots/hotspotScorer.js.map +1 -0
- package/dist/invariants/invariant-rules.schema.json +42 -1
- package/dist/invariants/ruleEngine.d.ts +4 -1
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +141 -5
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/invariants/ruleValidator.d.ts.map +1 -1
- package/dist/invariants/ruleValidator.js +62 -1
- package/dist/invariants/ruleValidator.js.map +1 -1
- package/dist/invariants/types.d.ts +23 -2
- package/dist/invariants/types.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +59 -24
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/index.d.ts +1 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +3 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +59 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +236 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -0
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +2 -0
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +61 -10
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/ledger.d.ts +128 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +345 -0
- package/dist/ledger.js.map +1 -0
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +9 -14
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp.js +0 -0
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +3 -0
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +7 -1
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +5 -1
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +10 -2
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +17 -17
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/scripts/runBench.d.ts +94 -0
- package/dist/scripts/runBench.d.ts.map +1 -0
- package/dist/scripts/runBench.js +1559 -0
- package/dist/scripts/runBench.js.map +1 -0
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +51 -4
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts +33 -0
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js +404 -0
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/styles/normalizer.d.ts +37 -0
- package/dist/styles/normalizer.d.ts.map +1 -0
- package/dist/styles/normalizer.js +301 -0
- package/dist/styles/normalizer.js.map +1 -0
- package/dist/styles/styleExtractor.d.ts +38 -0
- package/dist/styles/styleExtractor.d.ts.map +1 -0
- package/dist/styles/styleExtractor.js +630 -0
- package/dist/styles/styleExtractor.js.map +1 -0
- package/dist/styles/styleIndexer.d.ts +35 -0
- package/dist/styles/styleIndexer.d.ts.map +1 -0
- package/dist/styles/styleIndexer.js +266 -0
- package/dist/styles/styleIndexer.js.map +1 -0
- package/dist/styles/tailwindConfigLoader.d.ts +38 -0
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -0
- package/dist/styles/tailwindConfigLoader.js +374 -0
- package/dist/styles/tailwindConfigLoader.js.map +1 -0
- package/dist/styles/tailwindExpander.d.ts +38 -0
- package/dist/styles/tailwindExpander.d.ts.map +1 -0
- package/dist/styles/tailwindExpander.js +637 -0
- package/dist/styles/tailwindExpander.js.map +1 -0
- package/dist/styles/types.d.ts +92 -0
- package/dist/styles/types.d.ts.map +1 -0
- package/dist/styles/types.js +10 -0
- package/dist/styles/types.js.map +1 -0
- package/dist/symbols.d.ts +21 -0
- package/dist/symbols.d.ts.map +1 -0
- package/dist/symbols.js +31 -0
- package/dist/symbols.js.map +1 -0
- package/dist/types.d.ts +326 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +9 -5
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/hook-audit.sh +5 -1
- package/plugin/skills/code-auditor/SKILL.md +51 -0
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts +0 -21
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js +0 -21
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js +0 -12
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js +0 -22
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts +0 -9
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js +0 -16
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts +0 -14
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js +0 -22
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js +0 -20
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js +0 -28
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js +0 -31
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js +0 -61
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js +0 -15
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js +0 -49
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js +0 -19
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts +0 -37
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js +0 -73
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts +0 -12
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js +0 -82
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js +0 -15
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js.map +0 -1
- package/dist/analyzers/dataAccessAnalyzer.d.ts +0 -46
- package/dist/analyzers/dataAccessAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/dataAccessAnalyzer.js +0 -474
- package/dist/analyzers/dataAccessAnalyzer.js.map +0 -1
- package/dist/analyzers/dryAnalyzer.d.ts +0 -31
- package/dist/analyzers/dryAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/dryAnalyzer.js +0 -734
- package/dist/analyzers/dryAnalyzer.js.map +0 -1
- package/dist/analyzers/schemaAnalyzer.d.ts +0 -23
- package/dist/analyzers/schemaAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/schemaAnalyzer.js +0 -564
- package/dist/analyzers/schemaAnalyzer.js.map +0 -1
- package/dist/analyzers/solidAnalyzer.d.ts +0 -33
- package/dist/analyzers/solidAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/solidAnalyzer.js +0 -847
- 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
|