code-auditor-mcp 4.1.1 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +338 -0
- package/README.md +4 -4
- package/configs/hhra-compat.json +0 -8
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts +1 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js +7 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts +2 -8
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js +4 -4
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts +1 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts +2 -2
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts +1 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.js.map +1 -1
- package/dist/analyzers/applicability.d.ts +42 -0
- package/dist/analyzers/applicability.d.ts.map +1 -1
- package/dist/analyzers/applicability.js +74 -0
- package/dist/analyzers/applicability.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +18 -24
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +30 -23
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +113 -76
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +2 -0
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +8 -3
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +210 -148
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +23 -28
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/handleIdentification.d.ts +356 -0
- package/dist/analyzers/handleIdentification.d.ts.map +1 -0
- package/dist/analyzers/handleIdentification.js +424 -0
- package/dist/analyzers/handleIdentification.js.map +1 -0
- package/dist/analyzers/manifestStaleness.d.ts +64 -0
- package/dist/analyzers/manifestStaleness.d.ts.map +1 -0
- package/dist/analyzers/manifestStaleness.js +115 -0
- package/dist/analyzers/manifestStaleness.js.map +1 -0
- package/dist/analyzers/orgFilterTiers.d.ts +73 -1
- package/dist/analyzers/orgFilterTiers.d.ts.map +1 -1
- package/dist/analyzers/orgFilterTiers.js +166 -1
- package/dist/analyzers/orgFilterTiers.js.map +1 -1
- package/dist/analyzers/provenance.d.ts +251 -57
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +1109 -429
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts +2 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +2 -0
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/receiverResolution.d.ts +183 -0
- package/dist/analyzers/receiverResolution.d.ts.map +1 -0
- package/dist/analyzers/receiverResolution.js +540 -0
- package/dist/analyzers/receiverResolution.js.map +1 -0
- package/dist/analyzers/receiverRoot.d.ts +328 -0
- package/dist/analyzers/receiverRoot.d.ts.map +1 -0
- package/dist/analyzers/receiverRoot.js +990 -0
- package/dist/analyzers/receiverRoot.js.map +1 -0
- package/dist/analyzers/ruleRegistry.d.ts +12 -68
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +32 -342
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/ruleTiming.d.ts +10 -0
- package/dist/analyzers/ruleTiming.d.ts.map +1 -1
- package/dist/analyzers/ruleTiming.js +20 -0
- package/dist/analyzers/ruleTiming.js.map +1 -1
- package/dist/analyzers/sqlLiteral.d.ts +76 -0
- package/dist/analyzers/sqlLiteral.d.ts.map +1 -0
- package/dist/analyzers/sqlLiteral.js +419 -0
- package/dist/analyzers/sqlLiteral.js.map +1 -0
- package/dist/analyzers/tsEcosystem.d.ts +115 -0
- package/dist/analyzers/tsEcosystem.d.ts.map +1 -0
- package/dist/analyzers/tsEcosystem.js +231 -0
- package/dist/analyzers/tsEcosystem.js.map +1 -0
- package/dist/analyzers/tsExpressionDescriptor.d.ts +156 -0
- package/dist/analyzers/tsExpressionDescriptor.d.ts.map +1 -0
- package/dist/analyzers/tsExpressionDescriptor.js +374 -0
- package/dist/analyzers/tsExpressionDescriptor.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +6 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +107 -44
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +23 -35
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +287 -49
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +2130 -512
- 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 +2 -18
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +0 -33
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +20 -143
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +4 -3
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +36 -13
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.js +6 -0
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts +15 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js +25 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +21 -32
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +166 -196
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/functionConcerns.d.ts +3 -0
- package/dist/analyzers/universal/functionConcerns.d.ts.map +1 -1
- package/dist/analyzers/universal/functionConcerns.js +4 -1
- package/dist/analyzers/universal/functionConcerns.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +310 -69
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +709 -383
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/config.d.ts +3 -14
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/config.js +3 -16
- package/dist/analyzers/universal/schema/config.js.map +1 -1
- package/dist/analyzers/universal/schema/discovery.d.ts +22 -1
- package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/discovery.js +36 -20
- package/dist/analyzers/universal/schema/discovery.js.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.js +13 -3
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
- package/dist/analyzers/universal/schema/migrations.d.ts +187 -27
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/migrations.js +443 -186
- package/dist/analyzers/universal/schema/migrations.js.map +1 -1
- package/dist/analyzers/universal/schema/types.d.ts +17 -3
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/violations.js +1 -1
- package/dist/analyzers/universal/schema/violations.js.map +1 -1
- package/dist/analyzers/universal/styleScale.d.ts +52 -0
- package/dist/analyzers/universal/styleScale.d.ts.map +1 -0
- package/dist/analyzers/universal/styleScale.js +95 -0
- package/dist/analyzers/universal/styleScale.js.map +1 -0
- package/dist/applyDataDirEnv.js +1 -1
- package/dist/applyDataDirEnv.js.map +1 -1
- package/dist/auditRouter.d.ts +26 -30
- package/dist/auditRouter.d.ts.map +1 -1
- package/dist/auditRouter.js +373 -143
- package/dist/auditRouter.js.map +1 -1
- package/dist/auditRunner.d.ts +64 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +1538 -1033
- package/dist/auditRunner.js.map +1 -1
- package/dist/baseline.d.ts +30 -3
- package/dist/baseline.d.ts.map +1 -1
- package/dist/baseline.js +70 -9
- package/dist/baseline.js.map +1 -1
- package/dist/cli.js +969 -821
- package/dist/cli.js.map +1 -1
- package/dist/codeIndex/analyzerConfig.d.ts +60 -0
- package/dist/codeIndex/analyzerConfig.d.ts.map +1 -0
- package/dist/codeIndex/analyzerConfig.js +98 -0
- package/dist/codeIndex/analyzerConfig.js.map +1 -0
- package/dist/codeIndex/auditResults.d.ts +39 -0
- package/dist/codeIndex/auditResults.d.ts.map +1 -0
- package/dist/codeIndex/auditResults.js +99 -0
- package/dist/codeIndex/auditResults.js.map +1 -0
- package/dist/codeIndex/codeMap.d.ts +59 -0
- package/dist/codeIndex/codeMap.d.ts.map +1 -0
- package/dist/codeIndex/codeMap.js +72 -0
- package/dist/codeIndex/codeMap.js.map +1 -0
- package/dist/codeIndex/conventions.d.ts +33 -0
- package/dist/codeIndex/conventions.d.ts.map +1 -0
- package/dist/codeIndex/conventions.js +84 -0
- package/dist/codeIndex/conventions.js.map +1 -0
- package/dist/codeIndex/coverage.d.ts +78 -0
- package/dist/codeIndex/coverage.d.ts.map +1 -0
- package/dist/codeIndex/coverage.js +164 -0
- package/dist/codeIndex/coverage.js.map +1 -0
- package/dist/codeIndex/dependencyGraph.d.ts +97 -0
- package/dist/codeIndex/dependencyGraph.d.ts.map +1 -0
- package/dist/codeIndex/dependencyGraph.js +241 -0
- package/dist/codeIndex/dependencyGraph.js.map +1 -0
- package/dist/codeIndex/functionIndex.d.ts +111 -0
- package/dist/codeIndex/functionIndex.d.ts.map +1 -0
- package/dist/codeIndex/functionIndex.js +306 -0
- package/dist/codeIndex/functionIndex.js.map +1 -0
- package/dist/codeIndex/inferredReceivers.d.ts +32 -0
- package/dist/codeIndex/inferredReceivers.d.ts.map +1 -0
- package/dist/codeIndex/inferredReceivers.js +41 -0
- package/dist/codeIndex/inferredReceivers.js.map +1 -0
- package/dist/codeIndex/meta.d.ts +30 -0
- package/dist/codeIndex/meta.d.ts.map +1 -0
- package/dist/codeIndex/meta.js +37 -0
- package/dist/codeIndex/meta.js.map +1 -0
- package/dist/codeIndex/migrations.d.ts +96 -0
- package/dist/codeIndex/migrations.d.ts.map +1 -0
- package/dist/codeIndex/migrations.js +1302 -0
- package/dist/codeIndex/migrations.js.map +1 -0
- package/dist/codeIndex/projectTasks.d.ts +54 -0
- package/dist/codeIndex/projectTasks.d.ts.map +1 -0
- package/dist/codeIndex/projectTasks.js +81 -0
- package/dist/codeIndex/projectTasks.js.map +1 -0
- package/dist/codeIndex/provenance.d.ts +47 -0
- package/dist/codeIndex/provenance.d.ts.map +1 -0
- package/dist/codeIndex/provenance.js +48 -0
- package/dist/codeIndex/provenance.js.map +1 -0
- package/dist/codeIndex/rawSql.d.ts +54 -0
- package/dist/codeIndex/rawSql.d.ts.map +1 -0
- package/dist/codeIndex/rawSql.js +71 -0
- package/dist/codeIndex/rawSql.js.map +1 -0
- package/dist/codeIndex/schema.d.ts +99 -0
- package/dist/codeIndex/schema.d.ts.map +1 -0
- package/dist/codeIndex/schema.js +174 -0
- package/dist/codeIndex/schema.js.map +1 -0
- package/dist/codeIndex/search.d.ts +40 -0
- package/dist/codeIndex/search.d.ts.map +1 -0
- package/dist/codeIndex/search.js +316 -0
- package/dist/codeIndex/search.js.map +1 -0
- package/dist/codeIndex/shared.d.ts +27 -0
- package/dist/codeIndex/shared.d.ts.map +1 -0
- package/dist/codeIndex/shared.js +46 -0
- package/dist/codeIndex/shared.js.map +1 -0
- package/dist/codeIndex/sqliteCollection.d.ts +81 -0
- package/dist/codeIndex/sqliteCollection.d.ts.map +1 -0
- package/dist/codeIndex/sqliteCollection.js +250 -0
- package/dist/codeIndex/sqliteCollection.js.map +1 -0
- package/dist/codeIndex/whitelist.d.ts +59 -0
- package/dist/codeIndex/whitelist.d.ts.map +1 -0
- package/dist/codeIndex/whitelist.js +188 -0
- package/dist/codeIndex/whitelist.js.map +1 -0
- package/dist/codeIndexDB.d.ts +132 -271
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +365 -2729
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/codeIndexService.d.ts +87 -12
- package/dist/codeIndexService.d.ts.map +1 -1
- package/dist/codeIndexService.js +110 -34
- package/dist/codeIndexService.js.map +1 -1
- package/dist/componentScanner.d.ts +35 -0
- package/dist/componentScanner.d.ts.map +1 -1
- package/dist/componentScanner.js +114 -80
- package/dist/componentScanner.js.map +1 -1
- package/dist/config/configGeneratePath.d.ts +4 -0
- package/dist/config/configGeneratePath.d.ts.map +1 -1
- package/dist/config/configGeneratePath.js +4 -0
- package/dist/config/configGeneratePath.js.map +1 -1
- package/dist/config/configLoader.d.ts +15 -0
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +15 -16
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +11 -7
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +16 -14
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +17 -2
- package/dist/config/effectiveConfig.d.ts.map +1 -1
- package/dist/config/effectiveConfig.js +32 -25
- package/dist/config/effectiveConfig.js.map +1 -1
- package/dist/config/lintConfigReader.d.ts +6 -0
- package/dist/config/lintConfigReader.d.ts.map +1 -1
- package/dist/config/lintConfigReader.js +6 -0
- package/dist/config/lintConfigReader.js.map +1 -1
- package/dist/config/staticObjectExtract.d.ts +4 -0
- package/dist/config/staticObjectExtract.d.ts.map +1 -1
- package/dist/config/staticObjectExtract.js +7 -3
- package/dist/config/staticObjectExtract.js.map +1 -1
- package/dist/config/thresholdRationales.d.ts +2 -0
- package/dist/config/thresholdRationales.d.ts.map +1 -1
- package/dist/config/thresholdRationales.js +5 -2
- package/dist/config/thresholdRationales.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/conventions/conventionMiner.d.ts +105 -2
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +334 -259
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/coverage/coverageService.js +4 -4
- package/dist/coverage/coverageService.js.map +1 -1
- package/dist/daemon/args.d.ts +3 -0
- package/dist/daemon/args.d.ts.map +1 -1
- package/dist/daemon/args.js +3 -0
- package/dist/daemon/args.js.map +1 -1
- package/dist/daemon/core.d.ts +33 -27
- package/dist/daemon/core.d.ts.map +1 -1
- package/dist/daemon/core.js +198 -162
- package/dist/daemon/core.js.map +1 -1
- package/dist/daemon/lspServer.d.ts +8 -0
- package/dist/daemon/lspServer.d.ts.map +1 -1
- package/dist/daemon/lspServer.js +8 -0
- package/dist/daemon/lspServer.js.map +1 -1
- package/dist/daemon/main.d.ts +2 -0
- package/dist/daemon/main.d.ts.map +1 -1
- package/dist/daemon/main.js +2 -0
- package/dist/daemon/main.js.map +1 -1
- package/dist/daemon/resolve.d.ts +18 -3
- package/dist/daemon/resolve.d.ts.map +1 -1
- package/dist/daemon/resolve.js +25 -4
- package/dist/daemon/resolve.js.map +1 -1
- package/dist/daemon/socketClient.d.ts +10 -1
- package/dist/daemon/socketClient.d.ts.map +1 -1
- package/dist/daemon/socketClient.js +10 -1
- package/dist/daemon/socketClient.js.map +1 -1
- package/dist/daemon/socketServer.d.ts +13 -0
- package/dist/daemon/socketServer.d.ts.map +1 -1
- package/dist/daemon/socketServer.js +13 -0
- package/dist/daemon/socketServer.js.map +1 -1
- package/dist/dataPaths.d.ts +17 -8
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +16 -44
- package/dist/dataPaths.js.map +1 -1
- package/dist/dismissals.d.ts +22 -1
- package/dist/dismissals.d.ts.map +1 -1
- package/dist/dismissals.js +22 -1
- package/dist/dismissals.js.map +1 -1
- package/dist/enforcement/gate.d.ts +1 -0
- package/dist/enforcement/gate.d.ts.map +1 -1
- package/dist/enforcement/gate.js +1 -0
- package/dist/enforcement/gate.js.map +1 -1
- package/dist/fingerprint.d.ts +6 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +46 -1
- package/dist/fingerprint.js.map +1 -1
- package/dist/functionScanner.d.ts +30 -1
- package/dist/functionScanner.d.ts.map +1 -1
- package/dist/functionScanner.js +258 -256
- package/dist/functionScanner.js.map +1 -1
- package/dist/generators/AWSQConfigGenerator.d.ts +23 -0
- package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
- package/dist/generators/AWSQConfigGenerator.js +23 -0
- package/dist/generators/AWSQConfigGenerator.js.map +1 -1
- package/dist/generators/BaseConfigGenerator.d.ts +7 -0
- package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.js +7 -0
- package/dist/generators/BaseConfigGenerator.js.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.d.ts +23 -0
- package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.js +23 -0
- package/dist/generators/ClaudeConfigGenerator.js.map +1 -1
- package/dist/generators/ClineConfigGenerator.d.ts +23 -0
- package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClineConfigGenerator.js +23 -0
- package/dist/generators/ClineConfigGenerator.js.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.d.ts +23 -0
- package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.js +23 -0
- package/dist/generators/CodeiumConfigGenerator.js.map +1 -1
- package/dist/generators/CodexConfigGenerator.d.ts +23 -0
- package/dist/generators/CodexConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodexConfigGenerator.js +23 -0
- package/dist/generators/CodexConfigGenerator.js.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.d.ts +9 -0
- package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.js +9 -0
- package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
- package/dist/generators/ContinueConfigGenerator.d.ts +23 -0
- package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ContinueConfigGenerator.js +23 -0
- package/dist/generators/ContinueConfigGenerator.js.map +1 -1
- package/dist/generators/CopilotConfigGenerator.d.ts +23 -0
- package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CopilotConfigGenerator.js +23 -0
- package/dist/generators/CopilotConfigGenerator.js.map +1 -1
- package/dist/generators/CursorConfigGenerator.d.ts +23 -0
- package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CursorConfigGenerator.js +23 -0
- package/dist/generators/CursorConfigGenerator.js.map +1 -1
- package/dist/generators/GeminiConfigGenerator.d.ts +23 -0
- package/dist/generators/GeminiConfigGenerator.d.ts.map +1 -1
- package/dist/generators/GeminiConfigGenerator.js +23 -0
- package/dist/generators/GeminiConfigGenerator.js.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.d.ts +23 -0
- package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.js +23 -0
- package/dist/generators/VSCodeConfigGenerator.js.map +1 -1
- package/dist/generators/ZCodeConfigGenerator.d.ts +23 -0
- package/dist/generators/ZCodeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ZCodeConfigGenerator.js +23 -0
- package/dist/generators/ZCodeConfigGenerator.js.map +1 -1
- package/dist/graph/blastRadius.d.ts.map +1 -1
- package/dist/graph/blastRadius.js +12 -7
- package/dist/graph/blastRadius.js.map +1 -1
- package/dist/graph/callGraph.d.ts +31 -0
- package/dist/graph/callGraph.d.ts.map +1 -1
- package/dist/graph/callGraph.js +62 -32
- package/dist/graph/callGraph.js.map +1 -1
- package/dist/graph/importClassification.d.ts +28 -0
- package/dist/graph/importClassification.d.ts.map +1 -1
- package/dist/graph/importClassification.js +57 -0
- package/dist/graph/importClassification.js.map +1 -1
- package/dist/graph/importGraph.d.ts +38 -0
- package/dist/graph/importGraph.d.ts.map +1 -1
- package/dist/graph/importGraph.js +38 -0
- package/dist/graph/importGraph.js.map +1 -1
- package/dist/graph/outputFormatter.d.ts +12 -4
- package/dist/graph/outputFormatter.d.ts.map +1 -1
- package/dist/graph/outputFormatter.js +12 -62
- package/dist/graph/outputFormatter.js.map +1 -1
- package/dist/hooks/codex.d.ts +8 -1
- package/dist/hooks/codex.d.ts.map +1 -1
- package/dist/hooks/codex.js +7 -0
- package/dist/hooks/codex.js.map +1 -1
- package/dist/hooks/core.d.ts +6 -0
- package/dist/hooks/core.d.ts.map +1 -1
- package/dist/hooks/core.js +6 -1
- package/dist/hooks/core.js.map +1 -1
- package/dist/hooks/cursor.d.ts +11 -1
- package/dist/hooks/cursor.d.ts.map +1 -1
- package/dist/hooks/cursor.js +10 -0
- package/dist/hooks/cursor.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/installConfig.d.ts +23 -5
- package/dist/installConfig.d.ts.map +1 -1
- package/dist/installConfig.js +23 -5
- package/dist/installConfig.js.map +1 -1
- package/dist/installer.d.ts +6 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +6 -0
- package/dist/installer.js.map +1 -1
- package/dist/invariants/ruleEngine.d.ts +3 -0
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +156 -119
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/invariants/ruleValidator.d.ts +3 -0
- package/dist/invariants/ruleValidator.d.ts.map +1 -1
- package/dist/invariants/ruleValidator.js +185 -178
- package/dist/invariants/ruleValidator.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +4 -1
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +8 -19
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +8 -0
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +55 -5
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +1 -1
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +4 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +37 -3
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +11 -14
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/go/analyzer-darwin-amd64 +0 -0
- package/dist/languages/go/analyzer-darwin-arm64 +0 -0
- package/dist/languages/go/analyzer-linux-amd64 +0 -0
- package/dist/languages/go/analyzer-linux-arm64 +0 -0
- package/dist/languages/go/analyzer-src/analyzer.go +2 -0
- package/dist/languages/go/analyzer-src/dataaccess.go +388 -0
- package/dist/languages/go/analyzer-src/types.go +8 -0
- package/dist/languages/go/analyzer-windows-amd64.exe +0 -0
- package/dist/languages/go/database-packages.json +8 -0
- package/dist/languages/go/goResolution.d.ts +229 -0
- package/dist/languages/go/goResolution.d.ts.map +1 -0
- package/dist/languages/go/goResolution.js +799 -0
- package/dist/languages/go/goResolution.js.map +1 -0
- package/dist/languages/go/stdlib-packages.json +353 -0
- package/dist/languages/go/tenantInputs.d.ts +37 -0
- package/dist/languages/go/tenantInputs.d.ts.map +1 -0
- package/dist/languages/go/tenantInputs.js +121 -0
- package/dist/languages/go/tenantInputs.js.map +1 -0
- package/dist/languages/index.d.ts +1 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +5 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/json/JsonAdapter.d.ts +177 -0
- package/dist/languages/json/JsonAdapter.d.ts.map +1 -0
- package/dist/languages/json/JsonAdapter.js +531 -0
- package/dist/languages/json/JsonAdapter.js.map +1 -0
- package/dist/languages/locationIndex.d.ts +41 -0
- package/dist/languages/locationIndex.d.ts.map +1 -0
- package/dist/languages/locationIndex.js +42 -0
- package/dist/languages/locationIndex.js.map +1 -0
- package/dist/languages/sql/dialectDetection.d.ts +57 -0
- package/dist/languages/sql/dialectDetection.d.ts.map +1 -0
- package/dist/languages/sql/dialectDetection.js +135 -0
- package/dist/languages/sql/dialectDetection.js.map +1 -0
- package/dist/languages/sql/sqlAst.d.ts +494 -0
- package/dist/languages/sql/sqlAst.d.ts.map +1 -0
- package/dist/languages/sql/sqlAst.js +1817 -0
- package/dist/languages/sql/sqlAst.js.map +1 -0
- package/dist/languages/testConventions.d.ts +12 -1
- package/dist/languages/testConventions.d.ts.map +1 -1
- package/dist/languages/testConventions.js +13 -1
- package/dist/languages/testConventions.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/converter.js +2 -2
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/languages/tree-sitter/parser.d.ts +3 -4
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +10 -4
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/tree-sitter/rawNode.d.ts +3 -0
- package/dist/languages/tree-sitter/rawNode.d.ts.map +1 -1
- package/dist/languages/tree-sitter/rawNode.js +3 -0
- package/dist/languages/tree-sitter/rawNode.js.map +1 -1
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +36 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +168 -45
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/languages/typescript/database-packages.json +19 -0
- package/dist/ledger.d.ts +118 -2
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +167 -45
- package/dist/ledger.js.map +1 -1
- package/dist/legacyIndexNotice.d.ts +32 -0
- package/dist/legacyIndexNotice.d.ts.map +1 -0
- package/dist/legacyIndexNotice.js +89 -0
- package/dist/legacyIndexNotice.js.map +1 -0
- package/dist/mcp-tools/discoveryQueries.d.ts +55 -0
- package/dist/mcp-tools/discoveryQueries.d.ts.map +1 -0
- package/dist/mcp-tools/discoveryQueries.js +157 -0
- package/dist/mcp-tools/discoveryQueries.js.map +1 -0
- package/dist/mcp-tools/projectTasks.d.ts +9 -123
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +180 -170
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp-tools/whitelistTools.d.ts +20 -0
- package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
- package/dist/mcp-tools/whitelistTools.js +20 -0
- package/dist/mcp-tools/whitelistTools.js.map +1 -1
- package/dist/mcp-tools/workflowGuide.d.ts +9 -0
- package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
- package/dist/mcp-tools/workflowGuide.js +13 -5
- package/dist/mcp-tools/workflowGuide.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts +78 -0
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js +203 -340
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp-ui-simple.d.ts +2 -0
- package/dist/mcp-ui-simple.d.ts.map +1 -1
- package/dist/mcp-ui-simple.js +12 -5
- package/dist/mcp-ui-simple.js.map +1 -1
- package/dist/mcp.d.ts +6 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +827 -850
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts +29 -33
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +102 -791
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/mcpAutoIndex.d.ts +5 -0
- package/dist/mcpAutoIndex.d.ts.map +1 -1
- package/dist/mcpAutoIndex.js +7 -2
- package/dist/mcpAutoIndex.js.map +1 -1
- package/dist/mcpDiagnostics.d.ts +7 -0
- package/dist/mcpDiagnostics.d.ts.map +1 -1
- package/dist/mcpDiagnostics.js +34 -9
- package/dist/mcpDiagnostics.js.map +1 -1
- package/dist/mcpToolErrors.d.ts +16 -0
- package/dist/mcpToolErrors.d.ts.map +1 -1
- package/dist/mcpToolErrors.js +16 -0
- package/dist/mcpToolErrors.js.map +1 -1
- package/dist/nextFile.d.ts +6 -0
- package/dist/nextFile.d.ts.map +1 -1
- package/dist/nextFile.js +6 -0
- package/dist/nextFile.js.map +1 -1
- package/dist/nextFileIncremental.d.ts +44 -4
- package/dist/nextFileIncremental.d.ts.map +1 -1
- package/dist/nextFileIncremental.js +47 -7
- package/dist/nextFileIncremental.js.map +1 -1
- package/dist/phase/batchFunctions.d.ts +38 -0
- package/dist/phase/batchFunctions.d.ts.map +1 -0
- package/dist/phase/batchFunctions.js +64 -0
- package/dist/phase/batchFunctions.js.map +1 -0
- package/dist/phase/checks.d.ts +39 -0
- package/dist/phase/checks.d.ts.map +1 -0
- package/dist/phase/checks.js +48 -0
- package/dist/phase/checks.js.map +1 -0
- package/dist/phase/codeBlocks.d.ts +25 -0
- package/dist/phase/codeBlocks.d.ts.map +1 -0
- package/dist/phase/codeBlocks.js +268 -0
- package/dist/phase/codeBlocks.js.map +1 -0
- package/dist/phase/colorMath.d.ts +41 -0
- package/dist/phase/colorMath.d.ts.map +1 -0
- package/dist/phase/colorMath.js +109 -0
- package/dist/phase/colorMath.js.map +1 -0
- package/dist/phase/config.d.ts +29 -0
- package/dist/phase/config.d.ts.map +1 -0
- package/dist/phase/config.js +54 -0
- package/dist/phase/config.js.map +1 -0
- package/dist/phase/consumed.d.ts +47 -0
- package/dist/phase/consumed.d.ts.map +1 -0
- package/dist/phase/consumed.js +22 -0
- package/dist/phase/consumed.js.map +1 -0
- package/dist/phase/conventionMining.d.ts +59 -0
- package/dist/phase/conventionMining.d.ts.map +1 -0
- package/dist/phase/conventionMining.js +152 -0
- package/dist/phase/conventionMining.js.map +1 -0
- package/dist/phase/coverage.d.ts +65 -0
- package/dist/phase/coverage.d.ts.map +1 -0
- package/dist/phase/coverage.js +121 -0
- package/dist/phase/coverage.js.map +1 -0
- package/dist/phase/dag.d.ts +49 -0
- package/dist/phase/dag.d.ts.map +1 -0
- package/dist/phase/dag.js +120 -0
- package/dist/phase/dag.js.map +1 -0
- package/dist/phase/dataAccessCalls.d.ts +33 -0
- package/dist/phase/dataAccessCalls.d.ts.map +1 -0
- package/dist/phase/dataAccessCalls.js +42 -0
- package/dist/phase/dataAccessCalls.js.map +1 -0
- package/dist/phase/dataAccessCallsCandidates.d.ts +25 -0
- package/dist/phase/dataAccessCallsCandidates.d.ts.map +1 -0
- package/dist/phase/dataAccessCallsCandidates.js +27 -0
- package/dist/phase/dataAccessCallsCandidates.js.map +1 -0
- package/dist/phase/dynamicSql.d.ts +18 -0
- package/dist/phase/dynamicSql.d.ts.map +1 -0
- package/dist/phase/dynamicSql.js +20 -0
- package/dist/phase/dynamicSql.js.map +1 -0
- package/dist/phase/exportForm.d.ts +23 -0
- package/dist/phase/exportForm.d.ts.map +1 -0
- package/dist/phase/exportForm.js +29 -0
- package/dist/phase/exportForm.js.map +1 -0
- package/dist/phase/exportSymbols.d.ts +21 -0
- package/dist/phase/exportSymbols.d.ts.map +1 -0
- package/dist/phase/exportSymbols.js +28 -0
- package/dist/phase/exportSymbols.js.map +1 -0
- package/dist/phase/fileHeader.d.ts +15 -0
- package/dist/phase/fileHeader.d.ts.map +1 -0
- package/dist/phase/fileHeader.js +33 -0
- package/dist/phase/fileHeader.js.map +1 -0
- package/dist/phase/fileImports.d.ts +39 -0
- package/dist/phase/fileImports.d.ts.map +1 -0
- package/dist/phase/fileImports.js +138 -0
- package/dist/phase/fileImports.js.map +1 -0
- package/dist/phase/fileSymbols.d.ts +25 -0
- package/dist/phase/fileSymbols.d.ts.map +1 -0
- package/dist/phase/fileSymbols.js +302 -0
- package/dist/phase/fileSymbols.js.map +1 -0
- package/dist/phase/functionBodies.d.ts +28 -0
- package/dist/phase/functionBodies.d.ts.map +1 -0
- package/dist/phase/functionBodies.js +67 -0
- package/dist/phase/functionBodies.js.map +1 -0
- package/dist/phase/functionIndex.d.ts +28 -0
- package/dist/phase/functionIndex.d.ts.map +1 -0
- package/dist/phase/functionIndex.js +152 -0
- package/dist/phase/functionIndex.js.map +1 -0
- package/dist/phase/goFunctionAnalysis.d.ts +47 -0
- package/dist/phase/goFunctionAnalysis.d.ts.map +1 -0
- package/dist/phase/goFunctionAnalysis.js +291 -0
- package/dist/phase/goFunctionAnalysis.js.map +1 -0
- package/dist/phase/goFunctions.d.ts +38 -0
- package/dist/phase/goFunctions.d.ts.map +1 -0
- package/dist/phase/goFunctions.js +154 -0
- package/dist/phase/goFunctions.js.map +1 -0
- package/dist/phase/goImports.d.ts +29 -0
- package/dist/phase/goImports.d.ts.map +1 -0
- package/dist/phase/goImports.js +68 -0
- package/dist/phase/goImports.js.map +1 -0
- package/dist/phase/goPackageBindings.d.ts +22 -0
- package/dist/phase/goPackageBindings.d.ts.map +1 -0
- package/dist/phase/goPackageBindings.js +30 -0
- package/dist/phase/goPackageBindings.js.map +1 -0
- package/dist/phase/goSwitches.d.ts +28 -0
- package/dist/phase/goSwitches.d.ts.map +1 -0
- package/dist/phase/goSwitches.js +52 -0
- package/dist/phase/goSwitches.js.map +1 -0
- package/dist/phase/importForm.d.ts +20 -0
- package/dist/phase/importForm.d.ts.map +1 -0
- package/dist/phase/importForm.js +27 -0
- package/dist/phase/importForm.js.map +1 -0
- package/dist/phase/importSpecifiers.d.ts +20 -0
- package/dist/phase/importSpecifiers.d.ts.map +1 -0
- package/dist/phase/importSpecifiers.js +30 -0
- package/dist/phase/importSpecifiers.js.map +1 -0
- package/dist/phase/imports.d.ts +18 -0
- package/dist/phase/imports.d.ts.map +1 -0
- package/dist/phase/imports.js +25 -0
- package/dist/phase/imports.js.map +1 -0
- package/dist/phase/jsonDocument.d.ts +26 -0
- package/dist/phase/jsonDocument.d.ts.map +1 -0
- package/dist/phase/jsonDocument.js +36 -0
- package/dist/phase/jsonDocument.js.map +1 -0
- package/dist/phase/localBinding.d.ts +33 -0
- package/dist/phase/localBinding.d.ts.map +1 -0
- package/dist/phase/localBinding.js +208 -0
- package/dist/phase/localBinding.js.map +1 -0
- package/dist/phase/loopQueries.d.ts +31 -0
- package/dist/phase/loopQueries.d.ts.map +1 -0
- package/dist/phase/loopQueries.js +44 -0
- package/dist/phase/loopQueries.js.map +1 -0
- package/dist/phase/loopQueryCandidates.d.ts +26 -0
- package/dist/phase/loopQueryCandidates.d.ts.map +1 -0
- package/dist/phase/loopQueryCandidates.js +28 -0
- package/dist/phase/loopQueryCandidates.js.map +1 -0
- package/dist/phase/oracles.d.ts +216 -0
- package/dist/phase/oracles.d.ts.map +1 -0
- package/dist/phase/oracles.js +484 -0
- package/dist/phase/oracles.js.map +1 -0
- package/dist/phase/phaseModel.d.ts +190 -0
- package/dist/phase/phaseModel.d.ts.map +1 -0
- package/dist/phase/phaseModel.js +468 -0
- package/dist/phase/phaseModel.js.map +1 -0
- package/dist/phase/producers.d.ts +450 -0
- package/dist/phase/producers.d.ts.map +1 -0
- package/dist/phase/producers.js +1027 -0
- package/dist/phase/producers.js.map +1 -0
- package/dist/phase/querySiteCandidates.d.ts +27 -0
- package/dist/phase/querySiteCandidates.d.ts.map +1 -0
- package/dist/phase/querySiteCandidates.js +66 -0
- package/dist/phase/querySiteCandidates.js.map +1 -0
- package/dist/phase/querySites.d.ts +30 -0
- package/dist/phase/querySites.d.ts.map +1 -0
- package/dist/phase/querySites.js +93 -0
- package/dist/phase/querySites.js.map +1 -0
- package/dist/phase/reachability.d.ts +61 -0
- package/dist/phase/reachability.d.ts.map +1 -0
- package/dist/phase/reachability.js +104 -0
- package/dist/phase/reachability.js.map +1 -0
- package/dist/phase/reactComponents.d.ts +33 -0
- package/dist/phase/reactComponents.d.ts.map +1 -0
- package/dist/phase/reactComponents.js +47 -0
- package/dist/phase/reactComponents.js.map +1 -0
- package/dist/phase/receiverActivity.d.ts +35 -0
- package/dist/phase/receiverActivity.d.ts.map +1 -0
- package/dist/phase/receiverActivity.js +66 -0
- package/dist/phase/receiverActivity.js.map +1 -0
- package/dist/phase/receiverConsumers.d.ts +200 -0
- package/dist/phase/receiverConsumers.d.ts.map +1 -0
- package/dist/phase/receiverConsumers.js +1025 -0
- package/dist/phase/receiverConsumers.js.map +1 -0
- package/dist/phase/receiverProvenance.d.ts +97 -0
- package/dist/phase/receiverProvenance.d.ts.map +1 -0
- package/dist/phase/receiverProvenance.js +337 -0
- package/dist/phase/receiverProvenance.js.map +1 -0
- package/dist/phase/routing.d.ts +47 -0
- package/dist/phase/routing.d.ts.map +1 -0
- package/dist/phase/routing.js +62 -0
- package/dist/phase/routing.js.map +1 -0
- package/dist/phase/rules/conventions.d.ts +61 -0
- package/dist/phase/rules/conventions.d.ts.map +1 -0
- package/dist/phase/rules/conventions.js +403 -0
- package/dist/phase/rules/conventions.js.map +1 -0
- package/dist/phase/rules/crossDomain.d.ts +102 -0
- package/dist/phase/rules/crossDomain.d.ts.map +1 -0
- package/dist/phase/rules/crossDomain.js +918 -0
- package/dist/phase/rules/crossDomain.js.map +1 -0
- package/dist/phase/rules/dataAccess.d.ts +62 -0
- package/dist/phase/rules/dataAccess.d.ts.map +1 -0
- package/dist/phase/rules/dataAccess.js +480 -0
- package/dist/phase/rules/dataAccess.js.map +1 -0
- package/dist/phase/rules/dependencyGraph.d.ts +44 -0
- package/dist/phase/rules/dependencyGraph.d.ts.map +1 -0
- package/dist/phase/rules/dependencyGraph.js +260 -0
- package/dist/phase/rules/dependencyGraph.js.map +1 -0
- package/dist/phase/rules/documentation.d.ts +45 -0
- package/dist/phase/rules/documentation.d.ts.map +1 -0
- package/dist/phase/rules/documentation.js +340 -0
- package/dist/phase/rules/documentation.js.map +1 -0
- package/dist/phase/rules/dry.d.ts +109 -0
- package/dist/phase/rules/dry.d.ts.map +1 -0
- package/dist/phase/rules/dry.js +803 -0
- package/dist/phase/rules/dry.js.map +1 -0
- package/dist/phase/rules/fileDocumentation.d.ts +36 -0
- package/dist/phase/rules/fileDocumentation.d.ts.map +1 -0
- package/dist/phase/rules/fileDocumentation.js +134 -0
- package/dist/phase/rules/fileDocumentation.js.map +1 -0
- package/dist/phase/rules/functionBodies.d.ts +33 -0
- package/dist/phase/rules/functionBodies.d.ts.map +1 -0
- package/dist/phase/rules/functionBodies.js +71 -0
- package/dist/phase/rules/functionBodies.js.map +1 -0
- package/dist/phase/rules/goRules.d.ts +49 -0
- package/dist/phase/rules/goRules.d.ts.map +1 -0
- package/dist/phase/rules/goRules.js +209 -0
- package/dist/phase/rules/goRules.js.map +1 -0
- package/dist/phase/rules/querySites.d.ts +29 -0
- package/dist/phase/rules/querySites.d.ts.map +1 -0
- package/dist/phase/rules/querySites.js +94 -0
- package/dist/phase/rules/querySites.js.map +1 -0
- package/dist/phase/rules/react.d.ts +38 -0
- package/dist/phase/rules/react.d.ts.map +1 -0
- package/dist/phase/rules/react.js +202 -0
- package/dist/phase/rules/react.js.map +1 -0
- package/dist/phase/rules/registry.d.ts +170 -0
- package/dist/phase/rules/registry.d.ts.map +1 -0
- package/dist/phase/rules/registry.js +103 -0
- package/dist/phase/rules/registry.js.map +1 -0
- package/dist/phase/rules/schema.d.ts +79 -0
- package/dist/phase/rules/schema.d.ts.map +1 -0
- package/dist/phase/rules/schema.js +273 -0
- package/dist/phase/rules/schema.js.map +1 -0
- package/dist/phase/rules/schemaJson.d.ts +32 -0
- package/dist/phase/rules/schemaJson.d.ts.map +1 -0
- package/dist/phase/rules/schemaJson.js +81 -0
- package/dist/phase/rules/schemaJson.js.map +1 -0
- package/dist/phase/rules/schemaValidator.d.ts +38 -0
- package/dist/phase/rules/schemaValidator.d.ts.map +1 -0
- package/dist/phase/rules/schemaValidator.js +107 -0
- package/dist/phase/rules/schemaValidator.js.map +1 -0
- package/dist/phase/rules/secrets.d.ts +34 -0
- package/dist/phase/rules/secrets.d.ts.map +1 -0
- package/dist/phase/rules/secrets.js +208 -0
- package/dist/phase/rules/secrets.js.map +1 -0
- package/dist/phase/rules/security.d.ts +30 -0
- package/dist/phase/rules/security.d.ts.map +1 -0
- package/dist/phase/rules/security.js +85 -0
- package/dist/phase/rules/security.js.map +1 -0
- package/dist/phase/rules/securityDefects.d.ts +37 -0
- package/dist/phase/rules/securityDefects.d.ts.map +1 -0
- package/dist/phase/rules/securityDefects.js +167 -0
- package/dist/phase/rules/securityDefects.js.map +1 -0
- package/dist/phase/rules/solid.d.ts +61 -0
- package/dist/phase/rules/solid.d.ts.map +1 -0
- package/dist/phase/rules/solid.js +562 -0
- package/dist/phase/rules/solid.js.map +1 -0
- package/dist/phase/rules/styles.d.ts +77 -0
- package/dist/phase/rules/styles.d.ts.map +1 -0
- package/dist/phase/rules/styles.js +868 -0
- package/dist/phase/rules/styles.js.map +1 -0
- package/dist/phase/rules/unreferencedModule.d.ts +26 -0
- package/dist/phase/rules/unreferencedModule.d.ts.map +1 -0
- package/dist/phase/rules/unreferencedModule.js +61 -0
- package/dist/phase/rules/unreferencedModule.js.map +1 -0
- package/dist/phase/runner.d.ts +549 -0
- package/dist/phase/runner.d.ts.map +1 -0
- package/dist/phase/runner.js +1098 -0
- package/dist/phase/runner.js.map +1 -0
- package/dist/phase/schemaCode.d.ts +34 -0
- package/dist/phase/schemaCode.d.ts.map +1 -0
- package/dist/phase/schemaCode.js +53 -0
- package/dist/phase/schemaCode.js.map +1 -0
- package/dist/phase/schemaObjects.d.ts +37 -0
- package/dist/phase/schemaObjects.d.ts.map +1 -0
- package/dist/phase/schemaObjects.js +139 -0
- package/dist/phase/schemaObjects.js.map +1 -0
- package/dist/phase/schemaUsage.d.ts +30 -0
- package/dist/phase/schemaUsage.d.ts.map +1 -0
- package/dist/phase/schemaUsage.js +99 -0
- package/dist/phase/schemaUsage.js.map +1 -0
- package/dist/phase/schemaUsageCandidates.d.ts +32 -0
- package/dist/phase/schemaUsageCandidates.d.ts.map +1 -0
- package/dist/phase/schemaUsageCandidates.js +165 -0
- package/dist/phase/schemaUsageCandidates.js.map +1 -0
- package/dist/phase/schemaValidations.d.ts +34 -0
- package/dist/phase/schemaValidations.d.ts.map +1 -0
- package/dist/phase/schemaValidations.js +53 -0
- package/dist/phase/schemaValidations.js.map +1 -0
- package/dist/phase/secretCandidates.d.ts +28 -0
- package/dist/phase/secretCandidates.d.ts.map +1 -0
- package/dist/phase/secretCandidates.js +132 -0
- package/dist/phase/secretCandidates.js.map +1 -0
- package/dist/phase/securityCandidates.d.ts +29 -0
- package/dist/phase/securityCandidates.d.ts.map +1 -0
- package/dist/phase/securityCandidates.js +469 -0
- package/dist/phase/securityCandidates.js.map +1 -0
- package/dist/phase/seeded-defects.d.ts +18 -0
- package/dist/phase/seeded-defects.d.ts.map +1 -0
- package/dist/phase/seeded-defects.js +61 -0
- package/dist/phase/seeded-defects.js.map +1 -0
- package/dist/phase/stringLiterals.d.ts +24 -0
- package/dist/phase/stringLiterals.d.ts.map +1 -0
- package/dist/phase/stringLiterals.js +35 -0
- package/dist/phase/stringLiterals.js.map +1 -0
- package/dist/phase/stylesCss.d.ts +25 -0
- package/dist/phase/stylesCss.d.ts.map +1 -0
- package/dist/phase/stylesCss.js +34 -0
- package/dist/phase/stylesCss.js.map +1 -0
- package/dist/phase/stylesMarkup.d.ts +31 -0
- package/dist/phase/stylesMarkup.d.ts.map +1 -0
- package/dist/phase/stylesMarkup.js +48 -0
- package/dist/phase/stylesMarkup.js.map +1 -0
- package/dist/phase/stylesSource.d.ts +36 -0
- package/dist/phase/stylesSource.d.ts.map +1 -0
- package/dist/phase/stylesSource.js +50 -0
- package/dist/phase/stylesSource.js.map +1 -0
- package/dist/phase/typeDeclarations.d.ts +33 -0
- package/dist/phase/typeDeclarations.d.ts.map +1 -0
- package/dist/phase/typeDeclarations.js +76 -0
- package/dist/phase/typeDeclarations.js.map +1 -0
- package/dist/phase/types.d.ts +2191 -0
- package/dist/phase/types.d.ts.map +1 -0
- package/dist/phase/types.js +27 -0
- package/dist/phase/types.js.map +1 -0
- package/dist/phase/withinFileProvenance.d.ts +50 -0
- package/dist/phase/withinFileProvenance.d.ts.map +1 -0
- package/dist/phase/withinFileProvenance.js +112 -0
- package/dist/phase/withinFileProvenance.js.map +1 -0
- package/dist/pipeline.d.ts +37 -30
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +131 -326
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +104 -21
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +1144 -984
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/pipelineTypes.d.ts +21 -0
- package/dist/pipelineTypes.d.ts.map +1 -1
- package/dist/pipelineTypes.js +21 -10
- package/dist/pipelineTypes.js.map +1 -1
- package/dist/presets/presets.d.ts +9 -2
- package/dist/presets/presets.d.ts.map +1 -1
- package/dist/presets/presets.js +15 -26
- package/dist/presets/presets.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts +14 -3
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +14 -3
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts +5 -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 +9 -2
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +19 -5
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/reportGenerator.d.ts +9 -2
- package/dist/reporting/reportGenerator.d.ts.map +1 -1
- package/dist/reporting/reportGenerator.js +9 -2
- package/dist/reporting/reportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts +14 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +14 -0
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/reporting/sizeDistribution.d.ts +19 -2
- package/dist/reporting/sizeDistribution.d.ts.map +1 -1
- package/dist/reporting/sizeDistribution.js +23 -5
- package/dist/reporting/sizeDistribution.js.map +1 -1
- package/dist/ruleAliases.d.ts +6 -8
- package/dist/ruleAliases.d.ts.map +1 -1
- package/dist/ruleAliases.js +29 -4
- package/dist/ruleAliases.js.map +1 -1
- package/dist/search/QueryParser.d.ts +1 -0
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +32 -91
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts +19 -20
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js +214 -194
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/services/ProjectTaskRepository.d.ts +65 -3
- package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
- package/dist/services/ProjectTaskRepository.js +94 -25
- package/dist/services/ProjectTaskRepository.js.map +1 -1
- package/dist/services/auditJobService.d.ts +17 -1
- package/dist/services/auditJobService.d.ts.map +1 -1
- package/dist/services/auditJobService.js +16 -0
- package/dist/services/auditJobService.js.map +1 -1
- package/dist/services/fileAccounting.d.ts +43 -5
- package/dist/services/fileAccounting.d.ts.map +1 -1
- package/dist/services/fileAccounting.js +48 -9
- package/dist/services/fileAccounting.js.map +1 -1
- package/dist/services/projectTaskHelpers.d.ts +45 -0
- package/dist/services/projectTaskHelpers.d.ts.map +1 -1
- package/dist/services/projectTaskHelpers.js +45 -0
- package/dist/services/projectTaskHelpers.js.map +1 -1
- package/dist/services/whitelistService.d.ts +40 -5
- package/dist/services/whitelistService.d.ts.map +1 -1
- package/dist/services/whitelistService.js +87 -46
- package/dist/services/whitelistService.js.map +1 -1
- package/dist/sqlite/betterSqlite3.d.ts +35 -1
- package/dist/sqlite/betterSqlite3.d.ts.map +1 -1
- package/dist/sqlite/betterSqlite3.js +35 -1
- package/dist/sqlite/betterSqlite3.js.map +1 -1
- package/dist/sqlite/driver.d.ts +23 -0
- package/dist/sqlite/driver.d.ts.map +1 -1
- package/dist/sqlite/driver.js +23 -0
- package/dist/sqlite/driver.js.map +1 -1
- package/dist/sqlite/nodeSqlite.d.ts +35 -1
- package/dist/sqlite/nodeSqlite.d.ts.map +1 -1
- package/dist/sqlite/nodeSqlite.js +35 -1
- package/dist/sqlite/nodeSqlite.js.map +1 -1
- package/dist/structuralSignature.d.ts +9 -0
- package/dist/structuralSignature.d.ts.map +1 -1
- package/dist/structuralSignature.js +9 -0
- package/dist/structuralSignature.js.map +1 -1
- package/dist/styles/cssAstExtractor.d.ts +15 -0
- package/dist/styles/cssAstExtractor.d.ts.map +1 -1
- package/dist/styles/cssAstExtractor.js +15 -0
- package/dist/styles/cssAstExtractor.js.map +1 -1
- package/dist/styles/normalizer.d.ts +6 -0
- package/dist/styles/normalizer.d.ts.map +1 -1
- package/dist/styles/normalizer.js +6 -0
- package/dist/styles/normalizer.js.map +1 -1
- package/dist/styles/styleExtractor.d.ts +29 -2
- package/dist/styles/styleExtractor.d.ts.map +1 -1
- package/dist/styles/styleExtractor.js +67 -48
- package/dist/styles/styleExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.d.ts +8 -0
- package/dist/styles/styleIndexer.d.ts.map +1 -1
- package/dist/styles/styleIndexer.js +135 -138
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +12 -0
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +12 -0
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/styles/tailwindExpander.d.ts +3 -0
- package/dist/styles/tailwindExpander.d.ts.map +1 -1
- package/dist/styles/tailwindExpander.js +8 -4
- package/dist/styles/tailwindExpander.js.map +1 -1
- package/dist/styles/tailwindProbe.d.ts +9 -0
- package/dist/styles/tailwindProbe.d.ts.map +1 -1
- package/dist/styles/tailwindProbe.js +181 -166
- package/dist/styles/tailwindProbe.js.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts +13 -0
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +13 -0
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/symbols.d.ts +13 -11
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +14 -21
- package/dist/symbols.js.map +1 -1
- package/dist/telemetry.d.ts +31 -3
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +31 -3
- package/dist/telemetry.js.map +1 -1
- package/dist/tool-registry.d.ts +37 -1
- package/dist/tool-registry.d.ts.map +1 -1
- package/dist/tool-registry.js +37 -1
- package/dist/tool-registry.js.map +1 -1
- package/dist/types.d.ts +101 -71
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -16
- package/dist/types.js.map +1 -1
- package/dist/ui/InteractivePrompts.d.ts +12 -0
- package/dist/ui/InteractivePrompts.d.ts.map +1 -1
- package/dist/ui/InteractivePrompts.js +12 -0
- package/dist/ui/InteractivePrompts.js.map +1 -1
- package/dist/utils/astParser.d.ts +15 -0
- package/dist/utils/astParser.d.ts.map +1 -1
- package/dist/utils/astParser.js +15 -0
- package/dist/utils/astParser.js.map +1 -1
- package/dist/utils/astUtils.d.ts +60 -0
- package/dist/utils/astUtils.d.ts.map +1 -1
- package/dist/utils/astUtils.js +350 -362
- package/dist/utils/astUtils.js.map +1 -1
- package/dist/utils/contentHash.d.ts +22 -0
- package/dist/utils/contentHash.d.ts.map +1 -0
- package/dist/utils/contentHash.js +26 -0
- package/dist/utils/contentHash.js.map +1 -0
- package/dist/utils/dependencyExtractor.d.ts +28 -0
- package/dist/utils/dependencyExtractor.d.ts.map +1 -1
- package/dist/utils/dependencyExtractor.js +28 -1
- package/dist/utils/dependencyExtractor.js.map +1 -1
- package/dist/utils/errorMessage.d.ts +10 -0
- package/dist/utils/errorMessage.d.ts.map +1 -0
- package/dist/utils/errorMessage.js +12 -0
- package/dist/utils/errorMessage.js.map +1 -0
- package/dist/utils/fileDiscovery.d.ts +61 -2
- package/dist/utils/fileDiscovery.d.ts.map +1 -1
- package/dist/utils/fileDiscovery.js +131 -23
- package/dist/utils/fileDiscovery.js.map +1 -1
- package/dist/utils/reactDetection.d.ts +34 -5
- package/dist/utils/reactDetection.d.ts.map +1 -1
- package/dist/utils/reactDetection.js +73 -52
- package/dist/utils/reactDetection.js.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/workers/auditWorker.js +7 -3
- package/dist/workers/auditWorker.js.map +1 -1
- package/examples/.auditrc.example.json +0 -8
- package/examples/.auditrc.json +0 -11
- package/examples/.code-auditor.json +0 -2
- package/examples/nextjs.auditrc.json +0 -8
- package/examples/node-api.auditrc.json +0 -7
- package/examples/react.auditrc.json +0 -6
- package/examples/tolerant-solid.auditrc.json +0 -5
- package/package.json +11 -5
- package/plugin/.claude-plugin/manifest.json +16 -0
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/hooks.json +6 -2
- package/plugin/scripts/hook-audit.sh +41 -14
- package/plugin/scripts/hook-common.sh +50 -5
- package/plugin/scripts/hook-drift-check.sh +72 -0
- package/plugin/scripts/hook-self-audit.sh +17 -12
- package/plugin/skills/code-auditor/SKILL.md +1 -1
- package/server.json +2 -2
package/dist/pipelineAdapters.js
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Key patterns:
|
|
9
9
|
* - Per-file (solid, data-access, doc, schema): wrap analyzeAST()
|
|
10
|
-
* - DRY: wraps analyzeAST(), accumulates dryPairs internally →
|
|
11
|
-
* factory returns { visitor, getDryPairs } for post-pipeline extraction
|
|
12
10
|
* - React: wraps scanFile() per file, accumulates scan results →
|
|
13
11
|
* factory returns { visitor, finalizeCrossComponent }
|
|
14
12
|
* - DB-based (styles, conventions, cross-domain): reducers call
|
|
@@ -17,21 +15,21 @@
|
|
|
17
15
|
*/
|
|
18
16
|
import { createHash } from 'crypto';
|
|
19
17
|
import path from 'path';
|
|
20
|
-
import {
|
|
18
|
+
import { MIGRATED_RULES } from './phase/rules/registry.js';
|
|
21
19
|
import { TYPESCRIPT_EXTENSIONS, JAVASCRIPT_EXTENSIONS, getLanguageFromPath } from './utils/fileDiscovery.js';
|
|
20
|
+
import { computeContentHash } from './utils/contentHash.js';
|
|
22
21
|
import { walkAST, isExported, getNodeText, getFieldNode, getLineAndColumn, calculateComplexity, getFunctionBody, } from './languages/adapterBridge.js';
|
|
23
22
|
import { buildImportMap, extractFunctionCalls, } from './utils/dependencyExtractor.js';
|
|
24
23
|
import { classifyImportSpecifier, DEFAULT_VIRTUAL_MODULES } from './graph/importClassification.js';
|
|
25
24
|
import { isReactComponent, detectComponentType, getComponentName, } from './utils/reactDetection.js';
|
|
26
|
-
import { findTableReferences, checkNamingConventions, checkQueryPatterns, checkSQLInjection,
|
|
25
|
+
import { findTableReferences, checkNamingConventions, checkQueryPatterns, checkSQLInjection, getNearestTableSuggestions, } from './analyzers/universal/schema/codeAnalysis.js';
|
|
27
26
|
import { passesFileGate, extractTablesFromRegistry, } from './analyzers/universal/schema/discovery.js';
|
|
27
|
+
import { buildBindingEnv } from './analyzers/receiverRoot.js';
|
|
28
28
|
import { applyMigrationOps, stripIdentifier } from './analyzers/universal/schema/migrations.js';
|
|
29
29
|
import { buildOrgFilterTierSet, tableRequiresOrgFilter, } from './analyzers/orgFilterTiers.js';
|
|
30
30
|
// ── Rule ID helpers ──────────────────────────────────────────────────────────
|
|
31
31
|
function getRuleIdsFor(name) {
|
|
32
|
-
return
|
|
33
|
-
.filter(([, entry]) => entry.analyzer === name)
|
|
34
|
-
.map(([id]) => id);
|
|
32
|
+
return MIGRATED_RULES.filter((r) => r.analyzer === name).map((r) => r.id);
|
|
35
33
|
}
|
|
36
34
|
/** Join a list of names as prose: "a", "a and b", "a, b and c". */
|
|
37
35
|
function joinEnglish(names) {
|
|
@@ -50,91 +48,62 @@ function lazySingleton(loader) {
|
|
|
50
48
|
return promise;
|
|
51
49
|
};
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSOLIDAnalyzer.js').then((m) => new m.UniversalSOLIDAnalyzer()));
|
|
55
|
-
const visitor = {
|
|
56
|
-
name: 'solid',
|
|
57
|
-
stage: 'visitor',
|
|
58
|
-
getRuleIds: () => getRuleIdsFor('solid'),
|
|
59
|
-
async visit(ast, adapter, context, sourceCode) {
|
|
60
|
-
const a = await getAnalyzer();
|
|
61
|
-
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
62
|
-
return {
|
|
63
|
-
violations,
|
|
64
|
-
facts: {},
|
|
65
|
-
};
|
|
66
|
-
},
|
|
67
|
-
defaultConfig: {},
|
|
68
|
-
description: 'Detects violations of SOLID principles',
|
|
69
|
-
category: 'architecture',
|
|
70
|
-
};
|
|
51
|
+
function createAnalyzerAstVisitor(spec) {
|
|
71
52
|
return {
|
|
72
|
-
|
|
73
|
-
getSizeSamples: async () => {
|
|
74
|
-
try {
|
|
75
|
-
const a = await getAnalyzer();
|
|
76
|
-
return a.sizeSamples ?? [];
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
export function createDryVisitor() {
|
|
85
|
-
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalDRYAnalyzer.js').then((m) => new m.UniversalDRYAnalyzer()));
|
|
86
|
-
const visitor = {
|
|
87
|
-
name: 'dry',
|
|
53
|
+
name: spec.name,
|
|
88
54
|
stage: 'visitor',
|
|
89
|
-
getRuleIds: () => getRuleIdsFor(
|
|
55
|
+
getRuleIds: () => getRuleIdsFor(spec.name),
|
|
90
56
|
async visit(ast, adapter, context, sourceCode) {
|
|
91
|
-
const a = await getAnalyzer();
|
|
57
|
+
const a = await spec.getAnalyzer();
|
|
92
58
|
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
93
59
|
return { violations, facts: {} };
|
|
94
60
|
},
|
|
95
61
|
defaultConfig: {},
|
|
96
|
-
description:
|
|
97
|
-
category:
|
|
98
|
-
};
|
|
99
|
-
return {
|
|
100
|
-
visitor,
|
|
101
|
-
getDryPairs: async () => {
|
|
102
|
-
try {
|
|
103
|
-
const a = await getAnalyzer();
|
|
104
|
-
return a.dryPairs ?? [];
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
return [];
|
|
108
|
-
}
|
|
109
|
-
},
|
|
62
|
+
description: spec.description,
|
|
63
|
+
category: spec.category,
|
|
110
64
|
};
|
|
111
65
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
66
|
+
/** Read the analyzer's accumulated samples/pairs, swallowing a load failure. */
|
|
67
|
+
async function readAccumulated(getAnalyzer, pick) {
|
|
68
|
+
try {
|
|
69
|
+
const a = await getAnalyzer();
|
|
70
|
+
return pick(a) ?? [];
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** A bundle visitor: an analyzeAST visitor plus a getter for the analyzer's
|
|
77
|
+
* accumulated samples/pairs. The bundle creators (SOLID, DRY) share one lazy
|
|
78
|
+
* analyzer instance between the visitor and the getter. */
|
|
79
|
+
function createAnalyzerBundle(spec) {
|
|
80
|
+
const getAnalyzer = lazySingleton(spec.loader);
|
|
115
81
|
return {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// The violations returned here exclude missing-org-filter (moved to the
|
|
124
|
-
// derived reducer); the calls carry resolved table + filter presence +
|
|
125
|
-
// DB provenance + the query-site location.
|
|
126
|
-
const { violations, calls } = await a.analyzeWithFacts(ast, adapter, context.config, sourceCode);
|
|
127
|
-
const facts = {};
|
|
128
|
-
if (calls.length > 0) {
|
|
129
|
-
facts[context.filePath] = { calls };
|
|
130
|
-
}
|
|
131
|
-
return { violations, facts };
|
|
132
|
-
},
|
|
133
|
-
defaultConfig: {},
|
|
134
|
-
description: 'Analyzes database access patterns and data layer interactions',
|
|
135
|
-
category: 'security',
|
|
82
|
+
visitor: createAnalyzerAstVisitor({
|
|
83
|
+
name: spec.name,
|
|
84
|
+
getAnalyzer,
|
|
85
|
+
description: spec.description,
|
|
86
|
+
category: spec.category,
|
|
87
|
+
}),
|
|
88
|
+
getAccumulated: () => readAccumulated(getAnalyzer, spec.pick),
|
|
136
89
|
};
|
|
137
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Create the SOLID stage-2 visitor, which lazily loads the SOLID analyzer and
|
|
93
|
+
* also exposes its accumulated size samples.
|
|
94
|
+
*
|
|
95
|
+
* @returns The SOLID visitor bundle.
|
|
96
|
+
*/
|
|
97
|
+
export function createSolidVisitor() {
|
|
98
|
+
const { visitor, getAccumulated } = createAnalyzerBundle({
|
|
99
|
+
name: 'solid',
|
|
100
|
+
loader: () => import('./analyzers/universal/UniversalSOLIDAnalyzer.js').then((m) => new m.UniversalSOLIDAnalyzer()),
|
|
101
|
+
description: 'Detects violations of SOLID principles',
|
|
102
|
+
category: 'architecture',
|
|
103
|
+
pick: (a) => a.sizeSamples,
|
|
104
|
+
});
|
|
105
|
+
return { visitor, getSizeSamples: getAccumulated };
|
|
106
|
+
}
|
|
138
107
|
// ── Data-Access org-filter reducer (Spec 62 Amendment B) ─────────────────────
|
|
139
108
|
/**
|
|
140
109
|
* Stage-4 derived reducer for `missing-org-filter`. It joins the data-access
|
|
@@ -148,6 +117,8 @@ export function createDataAccessVisitor() {
|
|
|
148
117
|
*
|
|
149
118
|
* Firing and applicability both derive from {@link buildOrgFilterTierSet}, so
|
|
150
119
|
* they read the identical tier set (Tier 1 config / Tier 2 schema / Tier 3 DDL).
|
|
120
|
+
*
|
|
121
|
+
* @returns The data-access org-filter stage-4 reducer.
|
|
151
122
|
*/
|
|
152
123
|
export function createOrgFilterReducer() {
|
|
153
124
|
return {
|
|
@@ -155,9 +126,8 @@ export function createOrgFilterReducer() {
|
|
|
155
126
|
stage: 'derivedReducer',
|
|
156
127
|
// Hard dependency on the data-access visitor's per-query facts. The schema
|
|
157
128
|
// table catalog (`allFacts['schema'].tableColumns`) is read opportunistically
|
|
158
|
-
// so
|
|
159
|
-
//
|
|
160
|
-
consumes: ['data-access'],
|
|
129
|
+
// so a run without `schema` facts degrades to Tier 1–2 (config-only) instead
|
|
130
|
+
// of throwing — see buildOrgFilterTierSet.
|
|
161
131
|
getRuleIds: () => getRuleIdsFor('data-access-org-filter'),
|
|
162
132
|
async reduce(allFacts, context) {
|
|
163
133
|
const violations = [];
|
|
@@ -192,7 +162,7 @@ export function createOrgFilterReducer() {
|
|
|
192
162
|
},
|
|
193
163
|
rule: 'missing-org-filter',
|
|
194
164
|
analyzer: 'data-access',
|
|
195
|
-
|
|
165
|
+
symbol,
|
|
196
166
|
});
|
|
197
167
|
}
|
|
198
168
|
}
|
|
@@ -203,41 +173,28 @@ export function createOrgFilterReducer() {
|
|
|
203
173
|
category: 'security',
|
|
204
174
|
};
|
|
205
175
|
}
|
|
206
|
-
// ── Documentation visitor ────────────────────────────────────────────────────
|
|
207
|
-
export function createDocumentationVisitor() {
|
|
208
|
-
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalDocumentationAnalyzer.js').then((m) => new m.UniversalDocumentationAnalyzer()));
|
|
209
|
-
return {
|
|
210
|
-
name: 'documentation',
|
|
211
|
-
stage: 'visitor',
|
|
212
|
-
getRuleIds: () => getRuleIdsFor('documentation'),
|
|
213
|
-
async visit(ast, adapter, context, sourceCode) {
|
|
214
|
-
const a = await getAnalyzer();
|
|
215
|
-
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
216
|
-
return { violations, facts: {} };
|
|
217
|
-
},
|
|
218
|
-
defaultConfig: {},
|
|
219
|
-
description: 'Checks documentation completeness',
|
|
220
|
-
category: 'style',
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
176
|
// ── Secrets visitor ──────────────────────────────────────────────────────────
|
|
177
|
+
/**
|
|
178
|
+
* Create the secrets stage-2 visitor, which detects hardcoded credentials,
|
|
179
|
+
* API keys, and tokens.
|
|
180
|
+
*
|
|
181
|
+
* @returns The secrets stage-2 visitor.
|
|
182
|
+
*/
|
|
224
183
|
export function createSecretsVisitor() {
|
|
225
|
-
|
|
226
|
-
return {
|
|
184
|
+
return createAnalyzerAstVisitor({
|
|
227
185
|
name: 'secrets',
|
|
228
|
-
|
|
229
|
-
getRuleIds: () => getRuleIdsFor('secrets'),
|
|
230
|
-
async visit(ast, adapter, context, sourceCode) {
|
|
231
|
-
const a = await getAnalyzer();
|
|
232
|
-
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
233
|
-
return { violations, facts: {} };
|
|
234
|
-
},
|
|
235
|
-
defaultConfig: {},
|
|
186
|
+
getAnalyzer: lazySingleton(() => import('./analyzers/universal/UniversalSecretsAnalyzer.js').then((m) => new m.UniversalSecretsAnalyzer())),
|
|
236
187
|
description: 'Detects hardcoded credentials, API keys, and tokens',
|
|
237
188
|
category: 'security',
|
|
238
|
-
};
|
|
189
|
+
});
|
|
239
190
|
}
|
|
240
191
|
// ── Security visitor (Spec 61 R6) ────────────────────────────────────────────
|
|
192
|
+
/**
|
|
193
|
+
* Create the security stage-2 visitor, which detects command injection,
|
|
194
|
+
* dynamic require of project paths, and unescaped HTML interpolation.
|
|
195
|
+
*
|
|
196
|
+
* @returns The security stage-2 visitor.
|
|
197
|
+
*/
|
|
241
198
|
export function createSecurityVisitor() {
|
|
242
199
|
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSecurityAnalyzer.js').then((m) => new m.UniversalSecurityAnalyzer()));
|
|
243
200
|
return {
|
|
@@ -247,23 +204,276 @@ export function createSecurityVisitor() {
|
|
|
247
204
|
async visit(ast, adapter, context, sourceCode) {
|
|
248
205
|
const a = await getAnalyzer();
|
|
249
206
|
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
250
|
-
|
|
207
|
+
// Emit a fact per trigger construct the file contained. These fold
|
|
208
|
+
// corpus-wide (only keys seen in ≥1 file survive the spread-merge) and
|
|
209
|
+
// feed securityInputApplicability, so a rule whose construct is absent
|
|
210
|
+
// reads `notApplicable` instead of `clean`.
|
|
211
|
+
const saw = a.inputPresence;
|
|
212
|
+
const facts = {};
|
|
213
|
+
if (saw.shellProcess)
|
|
214
|
+
facts.shellProcessSeen = true;
|
|
215
|
+
if (saw.dynamicRequire)
|
|
216
|
+
facts.dynamicRequireSeen = true;
|
|
217
|
+
if (saw.htmlSink)
|
|
218
|
+
facts.htmlSinkSeen = true;
|
|
219
|
+
return { violations, facts };
|
|
251
220
|
},
|
|
252
221
|
defaultConfig: {},
|
|
253
222
|
description: 'Detects command injection, dynamic require of project paths, and unescaped HTML interpolation',
|
|
254
223
|
category: 'security',
|
|
255
224
|
};
|
|
256
225
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
function
|
|
264
|
-
const
|
|
265
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Collect function/method/component entries from an AST in three passes:
|
|
228
|
+
* (1) named function declarations and methods, (2) arrow functions assigned
|
|
229
|
+
* to variables, (3) React component detection. Returns the deduplicated list
|
|
230
|
+
* of `FunctionIndexEntry` rows destined for the `functions` index table.
|
|
231
|
+
*/
|
|
232
|
+
function collectFunctionEntries(root, sourceCode, importMap, filePath) {
|
|
233
|
+
const fnEntries = [];
|
|
234
|
+
// Pass 1 — named function declarations and methods
|
|
235
|
+
walkAST(root, (node) => {
|
|
236
|
+
if (node.type === 'function_declaration') {
|
|
237
|
+
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
238
|
+
if (!nameNode)
|
|
239
|
+
return;
|
|
240
|
+
const name = getNodeText(nameNode, sourceCode);
|
|
241
|
+
if (!name)
|
|
242
|
+
return;
|
|
243
|
+
const { line } = getLineAndColumn(node);
|
|
244
|
+
const endLine = node.location.end.line;
|
|
245
|
+
const body = getFunctionBody(node, sourceCode);
|
|
246
|
+
const calls = extractFunctionCalls(node, sourceCode, importMap);
|
|
247
|
+
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
248
|
+
fnEntries.push({
|
|
249
|
+
name,
|
|
250
|
+
line,
|
|
251
|
+
endLine,
|
|
252
|
+
entityType: 'function',
|
|
253
|
+
componentType: null,
|
|
254
|
+
isExported: isExported(node),
|
|
255
|
+
complexity: calculateComplexity(node),
|
|
256
|
+
body,
|
|
257
|
+
functionCalls: callNames,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
// Class methods
|
|
261
|
+
if (node.type === 'method_definition') {
|
|
262
|
+
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
263
|
+
if (!nameNode)
|
|
264
|
+
return;
|
|
265
|
+
const methodName = getNodeText(nameNode, sourceCode);
|
|
266
|
+
if (!methodName)
|
|
267
|
+
return;
|
|
268
|
+
// Walk up to find class name
|
|
269
|
+
let parent = node.parent;
|
|
270
|
+
let className = 'AnonymousClass';
|
|
271
|
+
while (parent) {
|
|
272
|
+
if (parent.type === 'class_declaration') {
|
|
273
|
+
const cn = parent.children?.find((c) => c.type === 'identifier');
|
|
274
|
+
if (cn)
|
|
275
|
+
className = getNodeText(cn, sourceCode);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
parent = parent.parent;
|
|
279
|
+
}
|
|
280
|
+
const { line } = getLineAndColumn(node);
|
|
281
|
+
const endLine = node.location.end.line;
|
|
282
|
+
const body = getFunctionBody(node, sourceCode);
|
|
283
|
+
const calls = extractFunctionCalls(node, sourceCode, importMap);
|
|
284
|
+
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
285
|
+
fnEntries.push({
|
|
286
|
+
name: `${className}.${methodName}`,
|
|
287
|
+
line,
|
|
288
|
+
endLine,
|
|
289
|
+
entityType: 'method',
|
|
290
|
+
componentType: null,
|
|
291
|
+
isExported: isExported(node),
|
|
292
|
+
complexity: calculateComplexity(node),
|
|
293
|
+
body,
|
|
294
|
+
functionCalls: callNames,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
// Pass 2 — arrow functions assigned to variables
|
|
299
|
+
walkAST(root, (node) => {
|
|
300
|
+
if (node.type !== 'variable_declarator')
|
|
301
|
+
return;
|
|
302
|
+
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
303
|
+
const arrowFunc = node.children?.find((c) => c.type === 'arrow_function');
|
|
304
|
+
if (!nameNode || !arrowFunc)
|
|
305
|
+
return;
|
|
306
|
+
const name = getNodeText(nameNode, sourceCode);
|
|
307
|
+
if (!name)
|
|
308
|
+
return;
|
|
309
|
+
// Don't duplicate if already covered as function_declaration
|
|
310
|
+
// (shouldn't happen — function_declaration is a different node type)
|
|
311
|
+
const { line } = getLineAndColumn(arrowFunc);
|
|
312
|
+
const endLine = arrowFunc.location.end.line;
|
|
313
|
+
const body = getFunctionBody(arrowFunc, sourceCode);
|
|
314
|
+
const calls = extractFunctionCalls(arrowFunc, sourceCode, importMap);
|
|
315
|
+
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
316
|
+
fnEntries.push({
|
|
317
|
+
name,
|
|
318
|
+
line,
|
|
319
|
+
endLine,
|
|
320
|
+
entityType: 'function',
|
|
321
|
+
componentType: null,
|
|
322
|
+
isExported: isExported(node),
|
|
323
|
+
complexity: calculateComplexity(arrowFunc),
|
|
324
|
+
body,
|
|
325
|
+
functionCalls: callNames,
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
// Pass 3 — React component detection
|
|
329
|
+
// Detect JSX-returning functions to set entity_type='component' and
|
|
330
|
+
// component_type, matching what functionScanner's deepSync produces.
|
|
331
|
+
// The convention miner's classifyExportKind() uses these columns to
|
|
332
|
+
// partition naming conventions.
|
|
333
|
+
const hasReactImport = [...importMap.values()].some((v) => v.modulePath === 'react');
|
|
334
|
+
if (filePath.endsWith('.tsx') ||
|
|
335
|
+
filePath.endsWith('.jsx') ||
|
|
336
|
+
(filePath.endsWith('.js') && hasReactImport)) {
|
|
337
|
+
walkAST(root, (node) => {
|
|
338
|
+
if (!isReactComponent(node, sourceCode))
|
|
339
|
+
return;
|
|
340
|
+
const ct = detectComponentType(node, sourceCode);
|
|
341
|
+
if (!ct)
|
|
342
|
+
return;
|
|
343
|
+
const cName = getComponentName(node, sourceCode);
|
|
344
|
+
if (!cName || cName === 'AnonymousComponent')
|
|
345
|
+
return;
|
|
346
|
+
const existing = fnEntries.find((f) => f.name === cName);
|
|
347
|
+
if (existing) {
|
|
348
|
+
// Upgrade existing function_declaration or arrow-function entry
|
|
349
|
+
existing.entityType = 'component';
|
|
350
|
+
existing.componentType = ct;
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
// New entry — class component, function_expression, or memo/forwardRef
|
|
354
|
+
// wrapper not already captured by passes 1 or 2.
|
|
355
|
+
const { line } = getLineAndColumn(node);
|
|
356
|
+
const endLine = node.location.end.line;
|
|
357
|
+
const body = getFunctionBody(node, sourceCode);
|
|
358
|
+
fnEntries.push({
|
|
359
|
+
name: cName,
|
|
360
|
+
line,
|
|
361
|
+
endLine,
|
|
362
|
+
entityType: 'component',
|
|
363
|
+
componentType: ct,
|
|
364
|
+
isExported: isExported(node),
|
|
365
|
+
complexity: calculateComplexity(node),
|
|
366
|
+
body,
|
|
367
|
+
functionCalls: [],
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
return fnEntries;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Extract static/dynamic imports and exports for a single file, emitting
|
|
376
|
+
* classified `import_specifiers` rows (Spec 60.1) into `indexFacts`. Returns
|
|
377
|
+
* the import/export facts for the file's stage-2 fact map.
|
|
378
|
+
*/
|
|
379
|
+
function collectImportExportFacts(opts) {
|
|
380
|
+
const { ast, adapter, sourceCode, filePath, context, isTsJs, indexFacts } = opts;
|
|
381
|
+
const langAdapter = adapter;
|
|
382
|
+
const langAst = ast;
|
|
383
|
+
// Static imports via the adapter's canonical extractImports()
|
|
384
|
+
const staticImportInfos = langAdapter.extractImports(langAst);
|
|
385
|
+
const staticImports = staticImportInfos.map((imp) => ({
|
|
386
|
+
moduleSpecifier: imp.source,
|
|
387
|
+
isStatic: true,
|
|
388
|
+
isDynamic: false,
|
|
389
|
+
isRequire: false,
|
|
390
|
+
line: imp.location.start.line,
|
|
391
|
+
}));
|
|
392
|
+
// Spec 60.1 — classify each static import specifier and emit one row per
|
|
393
|
+
// occurrence to `import_specifiers`. The corpus file set is the stage-1
|
|
394
|
+
// discovery list, threaded here as `_infra.files` (merged into
|
|
395
|
+
// `context.config` by pipeline.ts). Existence is checked against that
|
|
396
|
+
// in-memory set, never `fs.existsSync`. Virtual-module list + tsconfig
|
|
397
|
+
// `paths` patterns are threaded via `_infra` and passed to the classifier.
|
|
398
|
+
if (isTsJs) {
|
|
399
|
+
// Spec 60.1 Correction 1 — corpus set is the unfiltered discovery list
|
|
400
|
+
// (`_infra.corpusFiles`), not the narrowed `_infra.files`. The latter is
|
|
401
|
+
// the audit's analysis list (already pruned by includePaths/excludePaths),
|
|
402
|
+
// which drops `.json`/other non-analyzed extensions and would mis-classify
|
|
403
|
+
// a real `./invariant-rules.schema.json` as `internal-broken`.
|
|
404
|
+
const corpusFiles = new Set((context.config.corpusFiles) ??
|
|
405
|
+
(context.config.files) ??
|
|
406
|
+
[]);
|
|
407
|
+
const virtualModules = context.config.importVirtualModules
|
|
408
|
+
?? DEFAULT_VIRTUAL_MODULES;
|
|
409
|
+
const tsconfigAliases = context.config.tsconfigAliases;
|
|
410
|
+
for (const imp of staticImportInfos) {
|
|
411
|
+
const { classification, resolvedPath } = classifyImportSpecifier(imp.source, filePath, corpusFiles, {
|
|
412
|
+
virtualModules,
|
|
413
|
+
aliasPatterns: tsconfigAliases?.pathPatterns ?? [],
|
|
414
|
+
pathMappings: tsconfigAliases?.paths ?? {},
|
|
415
|
+
baseUrl: tsconfigAliases?.baseUrl,
|
|
416
|
+
projectRoot: context.projectRoot,
|
|
417
|
+
});
|
|
418
|
+
indexFacts.push({
|
|
419
|
+
table: 'import_specifiers',
|
|
420
|
+
data: {
|
|
421
|
+
file_path: filePath,
|
|
422
|
+
specifier: imp.source,
|
|
423
|
+
classification,
|
|
424
|
+
resolved_path: resolvedPath ?? null,
|
|
425
|
+
line: imp.location.start.line,
|
|
426
|
+
},
|
|
427
|
+
conflictKey: 'file_path, specifier, line',
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Dynamic import() and require() — first attempt with pure NodePattern
|
|
432
|
+
// Plan note: import keyword is an anonymous tree-sitter node, so
|
|
433
|
+
// hasChild cannot see it. We use `custom` with raw node access.
|
|
434
|
+
const dynamicCallNodes = langAdapter.findNodes(langAst, {
|
|
435
|
+
type: 'call_expression',
|
|
436
|
+
custom: (node) => {
|
|
437
|
+
const fn = node.children?.[0];
|
|
438
|
+
return (fn?.type === 'import') ||
|
|
439
|
+
(fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
const dynamicImports = [];
|
|
443
|
+
for (const node of dynamicCallNodes) {
|
|
444
|
+
const fn = node.children?.[0];
|
|
445
|
+
const isImport = fn?.type === 'import';
|
|
446
|
+
const isRequire = !isImport && (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
|
|
447
|
+
// Walk the AST children to find the string argument
|
|
448
|
+
const argsNode = node.children?.find((c) => c.type === 'arguments');
|
|
449
|
+
const stringNode = argsNode?.children?.find((c) => c.type === 'string');
|
|
450
|
+
if (stringNode) {
|
|
451
|
+
const text = getNodeText(stringNode, sourceCode);
|
|
452
|
+
if (text.length >= 2) {
|
|
453
|
+
dynamicImports.push({
|
|
454
|
+
moduleSpecifier: text.slice(1, -1), // strip quotes
|
|
455
|
+
isStatic: false,
|
|
456
|
+
isDynamic: isImport,
|
|
457
|
+
isRequire,
|
|
458
|
+
line: node.location.start.line,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// Exports via the adapter's canonical extractExports()
|
|
464
|
+
// Returns ExportInfo[] with isDefault — used by both invariants and conventions (B2)
|
|
465
|
+
const exportInfos = langAdapter.extractExports(langAst);
|
|
466
|
+
return {
|
|
467
|
+
imports: [...staticImports, ...dynamicImports],
|
|
468
|
+
exports: exportInfos,
|
|
469
|
+
};
|
|
266
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* Create the function-index stage-2 visitor, which populates the `functions`
|
|
473
|
+
* and `import_specifiers` index tables for the downstream reducers.
|
|
474
|
+
*
|
|
475
|
+
* @returns The function-index stage-2 visitor.
|
|
476
|
+
*/
|
|
267
477
|
export function createFunctionIndexVisitor() {
|
|
268
478
|
return {
|
|
269
479
|
name: 'function-index',
|
|
@@ -275,9 +485,12 @@ export function createFunctionIndexVisitor() {
|
|
|
275
485
|
if (!filePath)
|
|
276
486
|
return { violations: [], facts: {} };
|
|
277
487
|
const indexFacts = [];
|
|
278
|
-
// Only index TS/JS files
|
|
488
|
+
// Only index TS/JS files. The registry now maps `.json`/`.css` to their
|
|
489
|
+
// adapter language ids rather than `unknown`, so gate on the two languages
|
|
490
|
+
// this visitor indexes instead of on `unknown` (which would newly admit
|
|
491
|
+
// JSON/CSS ASTs to `buildImportMap`/`collectFunctionEntries`).
|
|
279
492
|
const lang = getLanguageFromPath(filePath);
|
|
280
|
-
if (lang
|
|
493
|
+
if (lang !== 'typescript' && lang !== 'javascript')
|
|
281
494
|
return { violations: [], facts: {} };
|
|
282
495
|
// Clear existing entries for this file
|
|
283
496
|
indexFacts.push({
|
|
@@ -296,148 +509,12 @@ export function createFunctionIndexVisitor() {
|
|
|
296
509
|
}
|
|
297
510
|
// Build import map once per file for resolving call targets
|
|
298
511
|
const importMap = buildImportMap(root, sourceCode);
|
|
299
|
-
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
if (node.type === 'function_declaration') {
|
|
306
|
-
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
307
|
-
if (!nameNode)
|
|
308
|
-
return;
|
|
309
|
-
const name = getNodeText(nameNode, sourceCode);
|
|
310
|
-
if (!name)
|
|
311
|
-
return;
|
|
312
|
-
const { line } = getLineAndColumn(node);
|
|
313
|
-
const endLine = node.location.end.line;
|
|
314
|
-
const body = getFunctionBody(node, sourceCode);
|
|
315
|
-
const calls = extractFunctionCalls(node, sourceCode, importMap);
|
|
316
|
-
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
317
|
-
fnEntries.push({
|
|
318
|
-
name,
|
|
319
|
-
line,
|
|
320
|
-
endLine,
|
|
321
|
-
entityType: 'function',
|
|
322
|
-
componentType: null,
|
|
323
|
-
isExported: isExported(node),
|
|
324
|
-
complexity: calculateComplexity(node),
|
|
325
|
-
body,
|
|
326
|
-
functionCalls: callNames,
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
// Class methods
|
|
330
|
-
if (node.type === 'method_definition') {
|
|
331
|
-
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
332
|
-
if (!nameNode)
|
|
333
|
-
return;
|
|
334
|
-
const methodName = getNodeText(nameNode, sourceCode);
|
|
335
|
-
if (!methodName)
|
|
336
|
-
return;
|
|
337
|
-
// Walk up to find class name
|
|
338
|
-
let parent = node.parent;
|
|
339
|
-
let className = 'AnonymousClass';
|
|
340
|
-
while (parent) {
|
|
341
|
-
if (parent.type === 'class_declaration') {
|
|
342
|
-
const cn = parent.children?.find((c) => c.type === 'identifier');
|
|
343
|
-
if (cn)
|
|
344
|
-
className = getNodeText(cn, sourceCode);
|
|
345
|
-
break;
|
|
346
|
-
}
|
|
347
|
-
parent = parent.parent;
|
|
348
|
-
}
|
|
349
|
-
const { line } = getLineAndColumn(node);
|
|
350
|
-
const endLine = node.location.end.line;
|
|
351
|
-
const body = getFunctionBody(node, sourceCode);
|
|
352
|
-
const calls = extractFunctionCalls(node, sourceCode, importMap);
|
|
353
|
-
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
354
|
-
fnEntries.push({
|
|
355
|
-
name: `${className}.${methodName}`,
|
|
356
|
-
line,
|
|
357
|
-
endLine,
|
|
358
|
-
entityType: 'method',
|
|
359
|
-
componentType: null,
|
|
360
|
-
isExported: isExported(node),
|
|
361
|
-
complexity: calculateComplexity(node),
|
|
362
|
-
body,
|
|
363
|
-
functionCalls: callNames,
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
// Pass 2 — arrow functions assigned to variables
|
|
368
|
-
walkAST(root, (node) => {
|
|
369
|
-
if (node.type !== 'variable_declarator')
|
|
370
|
-
return;
|
|
371
|
-
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
372
|
-
const arrowFunc = node.children?.find((c) => c.type === 'arrow_function');
|
|
373
|
-
if (!nameNode || !arrowFunc)
|
|
374
|
-
return;
|
|
375
|
-
const name = getNodeText(nameNode, sourceCode);
|
|
376
|
-
if (!name)
|
|
377
|
-
return;
|
|
378
|
-
// Don't duplicate if already covered as function_declaration
|
|
379
|
-
// (shouldn't happen — function_declaration is a different node type)
|
|
380
|
-
const { line } = getLineAndColumn(arrowFunc);
|
|
381
|
-
const endLine = arrowFunc.location.end.line;
|
|
382
|
-
const body = getFunctionBody(arrowFunc, sourceCode);
|
|
383
|
-
const calls = extractFunctionCalls(arrowFunc, sourceCode, importMap);
|
|
384
|
-
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
385
|
-
fnEntries.push({
|
|
386
|
-
name,
|
|
387
|
-
line,
|
|
388
|
-
endLine,
|
|
389
|
-
entityType: 'function',
|
|
390
|
-
componentType: null,
|
|
391
|
-
isExported: isExported(node),
|
|
392
|
-
complexity: calculateComplexity(arrowFunc),
|
|
393
|
-
body,
|
|
394
|
-
functionCalls: callNames,
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
// Pass 3 — React component detection
|
|
398
|
-
// Detect JSX-returning functions to set entity_type='component' and
|
|
399
|
-
// component_type, matching what functionScanner's deepSync produces.
|
|
400
|
-
// The convention miner's classifyExportKind() uses these columns to
|
|
401
|
-
// partition naming conventions.
|
|
402
|
-
const hasReactImport = [...importMap.values()].some((v) => v.modulePath === 'react');
|
|
403
|
-
if (filePath.endsWith('.tsx') ||
|
|
404
|
-
filePath.endsWith('.jsx') ||
|
|
405
|
-
(filePath.endsWith('.js') && hasReactImport)) {
|
|
406
|
-
walkAST(root, (node) => {
|
|
407
|
-
if (!isReactComponent(node, sourceCode))
|
|
408
|
-
return;
|
|
409
|
-
const ct = detectComponentType(node, sourceCode);
|
|
410
|
-
if (!ct)
|
|
411
|
-
return;
|
|
412
|
-
const cName = getComponentName(node, sourceCode);
|
|
413
|
-
if (!cName || cName === 'AnonymousComponent')
|
|
414
|
-
return;
|
|
415
|
-
const existing = fnEntries.find((f) => f.name === cName);
|
|
416
|
-
if (existing) {
|
|
417
|
-
// Upgrade existing function_declaration or arrow-function entry
|
|
418
|
-
existing.entityType = 'component';
|
|
419
|
-
existing.componentType = ct;
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
// New entry — class component, function_expression, or memo/forwardRef
|
|
423
|
-
// wrapper not already captured by passes 1 or 2.
|
|
424
|
-
const { line } = getLineAndColumn(node);
|
|
425
|
-
const endLine = node.location.end.line;
|
|
426
|
-
const body = getFunctionBody(node, sourceCode);
|
|
427
|
-
fnEntries.push({
|
|
428
|
-
name: cName,
|
|
429
|
-
line,
|
|
430
|
-
endLine,
|
|
431
|
-
entityType: 'component',
|
|
432
|
-
componentType: ct,
|
|
433
|
-
isExported: isExported(node),
|
|
434
|
-
complexity: calculateComplexity(node),
|
|
435
|
-
body,
|
|
436
|
-
functionCalls: [],
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
}
|
|
512
|
+
const fnEntries = collectFunctionEntries(root, sourceCode, importMap, filePath);
|
|
513
|
+
// File-level content hash, shared by every function row this file emits.
|
|
514
|
+
// detectChangedFunctions stores the same value (from its own read of the
|
|
515
|
+
// file) so a later `changed` run can skip the re-scan when the file's
|
|
516
|
+
// whole content is unchanged.
|
|
517
|
+
const fileHash = computeContentHash(sourceCode);
|
|
441
518
|
// Build IndexFactsEntry for each function
|
|
442
519
|
for (const fn of fnEntries) {
|
|
443
520
|
const metadata = {
|
|
@@ -446,7 +523,7 @@ export function createFunctionIndexVisitor() {
|
|
|
446
523
|
complexity: fn.complexity,
|
|
447
524
|
functionCalls: fn.functionCalls,
|
|
448
525
|
};
|
|
449
|
-
const contentHash = computeContentHash(fn.body
|
|
526
|
+
const contentHash = computeContentHash(fn.body);
|
|
450
527
|
const now = new Date().toISOString();
|
|
451
528
|
indexFacts.push({
|
|
452
529
|
table: 'functions',
|
|
@@ -459,7 +536,6 @@ export function createFunctionIndexVisitor() {
|
|
|
459
536
|
language: lang,
|
|
460
537
|
entity_type: fn.entityType,
|
|
461
538
|
component_type: fn.componentType,
|
|
462
|
-
signature: '',
|
|
463
539
|
return_type: null,
|
|
464
540
|
complexity: fn.complexity,
|
|
465
541
|
is_exported: fn.isExported ? 1 : 0,
|
|
@@ -468,105 +544,27 @@ export function createFunctionIndexVisitor() {
|
|
|
468
544
|
parameters: null,
|
|
469
545
|
body: fn.body ?? null,
|
|
470
546
|
content_hash: contentHash,
|
|
547
|
+
file_hash: fileHash,
|
|
471
548
|
last_modified: now,
|
|
472
549
|
metadata_json: JSON.stringify(metadata),
|
|
473
550
|
},
|
|
474
551
|
conflictKey: 'name, file_path, line_number',
|
|
475
552
|
});
|
|
476
553
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
moduleSpecifier: imp.source,
|
|
486
|
-
isStatic: true,
|
|
487
|
-
isDynamic: false,
|
|
488
|
-
isRequire: false,
|
|
489
|
-
line: imp.location.start.line,
|
|
490
|
-
}));
|
|
491
|
-
// Spec 60.1 — classify each static import specifier and emit one row per
|
|
492
|
-
// occurrence to `import_specifiers`. The corpus file set is the stage-1
|
|
493
|
-
// discovery list, threaded here as `_infra.files` (merged into
|
|
494
|
-
// `context.config` by pipeline.ts). Existence is checked against that
|
|
495
|
-
// in-memory set, never `fs.existsSync`. Virtual-module list + tsconfig
|
|
496
|
-
// `paths` patterns are threaded via `_infra` and passed to the classifier.
|
|
497
|
-
if (isTsJs) {
|
|
498
|
-
// Spec 60.1 Correction 1 — corpus set is the unfiltered discovery list
|
|
499
|
-
// (`_infra.corpusFiles`), not the narrowed `_infra.files`. The latter is
|
|
500
|
-
// the audit's analysis list (already pruned by includePaths/excludePaths),
|
|
501
|
-
// which drops `.json`/other non-analyzed extensions and would mis-classify
|
|
502
|
-
// a real `./invariant-rules.schema.json` as `internal-broken`.
|
|
503
|
-
const corpusFiles = new Set((context.config.corpusFiles) ??
|
|
504
|
-
(context.config.files) ??
|
|
505
|
-
[]);
|
|
506
|
-
const virtualModules = context.config.importVirtualModules
|
|
507
|
-
?? DEFAULT_VIRTUAL_MODULES;
|
|
508
|
-
const tsconfigAliases = context.config.tsconfigAliases;
|
|
509
|
-
for (const imp of staticImportInfos) {
|
|
510
|
-
const { classification, resolvedPath } = classifyImportSpecifier(imp.source, filePath, corpusFiles, {
|
|
511
|
-
virtualModules,
|
|
512
|
-
aliasPatterns: tsconfigAliases?.pathPatterns ?? [],
|
|
513
|
-
pathMappings: tsconfigAliases?.paths ?? {},
|
|
514
|
-
baseUrl: tsconfigAliases?.baseUrl,
|
|
515
|
-
projectRoot: context.projectRoot,
|
|
516
|
-
});
|
|
517
|
-
indexFacts.push({
|
|
518
|
-
table: 'import_specifiers',
|
|
519
|
-
data: {
|
|
520
|
-
file_path: filePath,
|
|
521
|
-
specifier: imp.source,
|
|
522
|
-
classification,
|
|
523
|
-
resolved_path: resolvedPath ?? null,
|
|
524
|
-
line: imp.location.start.line,
|
|
525
|
-
},
|
|
526
|
-
conflictKey: 'file_path, specifier, line',
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
// Dynamic import() and require() — first attempt with pure NodePattern
|
|
531
|
-
// Plan note: import keyword is an anonymous tree-sitter node, so
|
|
532
|
-
// hasChild cannot see it. We use `custom` with raw node access.
|
|
533
|
-
const dynamicCallNodes = langAdapter.findNodes(langAst, {
|
|
534
|
-
type: 'call_expression',
|
|
535
|
-
custom: (node) => {
|
|
536
|
-
const fn = node.children?.[0];
|
|
537
|
-
return (fn?.type === 'import') ||
|
|
538
|
-
(fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
|
|
539
|
-
},
|
|
554
|
+
const { imports: importFacts, exports: exportInfos } = collectImportExportFacts({
|
|
555
|
+
ast: ast,
|
|
556
|
+
adapter: _adapter,
|
|
557
|
+
sourceCode,
|
|
558
|
+
filePath,
|
|
559
|
+
context,
|
|
560
|
+
isTsJs,
|
|
561
|
+
indexFacts,
|
|
540
562
|
});
|
|
541
|
-
const dynamicImports = [];
|
|
542
|
-
for (const node of dynamicCallNodes) {
|
|
543
|
-
const fn = node.children?.[0];
|
|
544
|
-
const isImport = fn?.type === 'import';
|
|
545
|
-
const isRequire = !isImport && (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
|
|
546
|
-
// Walk the AST children to find the string argument
|
|
547
|
-
const argsNode = node.children?.find((c) => c.type === 'arguments');
|
|
548
|
-
const stringNode = argsNode?.children?.find((c) => c.type === 'string');
|
|
549
|
-
if (stringNode) {
|
|
550
|
-
const text = getNodeText(stringNode, sourceCode);
|
|
551
|
-
if (text.length >= 2) {
|
|
552
|
-
dynamicImports.push({
|
|
553
|
-
moduleSpecifier: text.slice(1, -1), // strip quotes
|
|
554
|
-
isStatic: false,
|
|
555
|
-
isDynamic: isImport,
|
|
556
|
-
isRequire,
|
|
557
|
-
line: node.location.start.line,
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
// Exports via the adapter's canonical extractExports()
|
|
563
|
-
// Returns ExportInfo[] with isDefault — used by both invariants and conventions (B2)
|
|
564
|
-
const exportInfos = langAdapter.extractExports(langAst);
|
|
565
563
|
return {
|
|
566
564
|
violations: [],
|
|
567
565
|
facts: {
|
|
568
566
|
[filePath]: {
|
|
569
|
-
imports:
|
|
567
|
+
imports: importFacts,
|
|
570
568
|
exports: exportInfos,
|
|
571
569
|
},
|
|
572
570
|
},
|
|
@@ -578,6 +576,12 @@ export function createFunctionIndexVisitor() {
|
|
|
578
576
|
category: 'infrastructure',
|
|
579
577
|
};
|
|
580
578
|
}
|
|
579
|
+
/**
|
|
580
|
+
* Create the React stage-2 visitor, which scans React components and exposes
|
|
581
|
+
* cross-component checks to run after stage 2 completes.
|
|
582
|
+
*
|
|
583
|
+
* @returns The React visitor bundle.
|
|
584
|
+
*/
|
|
581
585
|
export function createReactVisitor() {
|
|
582
586
|
const scanResults = [];
|
|
583
587
|
const visitor = {
|
|
@@ -659,6 +663,8 @@ export function createReactVisitor() {
|
|
|
659
663
|
* Styles CSS visitor — extracts declarations, tokens, and class usage from
|
|
660
664
|
* tree-sitter-css and tree-sitter-scss parsed ASTs. Replaces the regex-based
|
|
661
665
|
* CSS/SCSS extraction in styleExtractor.ts for .css and .scss files.
|
|
666
|
+
*
|
|
667
|
+
* @returns The styles-css stage-2 visitor.
|
|
662
668
|
*/
|
|
663
669
|
export function createStylesCssVisitor() {
|
|
664
670
|
return {
|
|
@@ -699,6 +705,8 @@ export function createStylesCssVisitor() {
|
|
|
699
705
|
* emitting a fact (with a content hash) even when it yields no declarations.
|
|
700
706
|
* The reducer uses that fact to (a) drop stale rows for files that *lost* their
|
|
701
707
|
* styles and (b) skip re-writing files whose stored rows are already current.
|
|
708
|
+
*
|
|
709
|
+
* @returns The styles-source stage-2 visitor.
|
|
702
710
|
*/
|
|
703
711
|
export function createStylesSourceVisitor() {
|
|
704
712
|
// Spec 61 R3.4 — a project tailwind config that exists but cannot be read
|
|
@@ -744,6 +752,44 @@ export function createStylesSourceVisitor() {
|
|
|
744
752
|
category: 'style',
|
|
745
753
|
};
|
|
746
754
|
}
|
|
755
|
+
// Style-table SQL, hoisted so the two inserters (source CSS-in-JS facts and
|
|
756
|
+
// compiled-CSS facts) and each one's prepare-once fast path + `indexHandle.run`
|
|
757
|
+
// fallback cannot drift on a statement string.
|
|
758
|
+
const SQL_STYLE_GET_HASH = 'SELECT content_hash FROM style_declarations WHERE file_path = ? LIMIT 1';
|
|
759
|
+
const SQL_STYLE_DEL_DECL = 'DELETE FROM style_declarations WHERE file_path = ?';
|
|
760
|
+
const SQL_STYLE_DEL_USAGE = 'DELETE FROM style_class_usage WHERE file_path = ?';
|
|
761
|
+
const SQL_STYLE_DEL_TOKEN = 'DELETE FROM style_tokens WHERE file_path = ?';
|
|
762
|
+
const SQL_STYLE_DEL_CLASS = 'DELETE FROM style_defined_classes WHERE file_path = ?';
|
|
763
|
+
const SQL_STYLE_INS_DECL = 'INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
|
764
|
+
const SQL_STYLE_INS_CLASS = 'INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)';
|
|
765
|
+
const SQL_STYLE_INS_USAGE = 'INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)';
|
|
766
|
+
const SQL_STYLE_INS_TOKEN = 'INSERT INTO style_tokens (name, value, file_path, mechanism) VALUES (?, ?, ?, ?)';
|
|
767
|
+
/** Run a style write through its prepared statement. The index handle always
|
|
768
|
+
* exposes a raw better-sqlite3 DB (`pipelineIndexHandle` in auditRunner is the
|
|
769
|
+
* only construction and always sets `rawDb`), so the prepared statements are
|
|
770
|
+
* always available — the former `else indexHandle.run(sql, params)` per-call
|
|
771
|
+
* fallback was unreachable and has been removed. */
|
|
772
|
+
function runStyleWrite(stmts, stmtKey, params) {
|
|
773
|
+
stmts[stmtKey].run(...params);
|
|
774
|
+
}
|
|
775
|
+
/** Read a file's stored style content hash through its prepared statement. */
|
|
776
|
+
function getStyleHash(stmts, filePath) {
|
|
777
|
+
return stmts.getHash.get(filePath);
|
|
778
|
+
}
|
|
779
|
+
/** Prepare the statements shared by both style inserters (source CSS-in-JS and
|
|
780
|
+
* compiled CSS). Each site layers its own extras (`getHash` for the source path,
|
|
781
|
+
* `insTok` for the compiled-CSS path) on top of this common set. */
|
|
782
|
+
function prepareCommonStyleStatements(rawDb) {
|
|
783
|
+
return {
|
|
784
|
+
delDecl: rawDb.prepare(SQL_STYLE_DEL_DECL),
|
|
785
|
+
delUsage: rawDb.prepare(SQL_STYLE_DEL_USAGE),
|
|
786
|
+
delTok: rawDb.prepare(SQL_STYLE_DEL_TOKEN),
|
|
787
|
+
delClass: rawDb.prepare(SQL_STYLE_DEL_CLASS),
|
|
788
|
+
insDecl: rawDb.prepare(SQL_STYLE_INS_DECL),
|
|
789
|
+
insClass: rawDb.prepare(SQL_STYLE_INS_CLASS),
|
|
790
|
+
insUsage: rawDb.prepare(SQL_STYLE_INS_USAGE),
|
|
791
|
+
};
|
|
792
|
+
}
|
|
747
793
|
/**
|
|
748
794
|
* Insert TS/JS CSS-in-JS facts (from the styles-source visitor) into the
|
|
749
795
|
* style_* tables. Mirrors the TS/JS branch the style indexer used to run
|
|
@@ -764,19 +810,10 @@ function insertSourceStyleFacts(indexHandle, sourceFacts, isScoped) {
|
|
|
764
810
|
if (entries.length === 0)
|
|
765
811
|
return false;
|
|
766
812
|
const rawDb = indexHandle.rawDb;
|
|
767
|
-
const stmts =
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
delUsage: rawDb.prepare('DELETE FROM style_class_usage WHERE file_path = ?'),
|
|
772
|
-
delTok: rawDb.prepare('DELETE FROM style_tokens WHERE file_path = ?'),
|
|
773
|
-
delUnread: rawDb.prepare('DELETE FROM style_unread_sources WHERE file_path = ?'),
|
|
774
|
-
delClass: rawDb.prepare('DELETE FROM style_defined_classes WHERE file_path = ?'),
|
|
775
|
-
insDecl: rawDb.prepare('INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'),
|
|
776
|
-
insClass: rawDb.prepare('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)'),
|
|
777
|
-
insUsage: rawDb.prepare('INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)'),
|
|
778
|
-
}
|
|
779
|
-
: null;
|
|
813
|
+
const stmts = {
|
|
814
|
+
...prepareCommonStyleStatements(rawDb),
|
|
815
|
+
getHash: rawDb.prepare(SQL_STYLE_GET_HASH),
|
|
816
|
+
};
|
|
780
817
|
let contributed = false;
|
|
781
818
|
const insertOne = (filePath, facts) => {
|
|
782
819
|
if (facts.declarations.length > 0 || facts.classUsage.length > 0)
|
|
@@ -784,59 +821,32 @@ function insertSourceStyleFacts(indexHandle, sourceFacts, isScoped) {
|
|
|
784
821
|
// Full-run content-hash skip: leave already-current rows untouched. Scoped
|
|
785
822
|
// runs always re-write, matching the old style indexer's `if (!scoped)` guard.
|
|
786
823
|
if (!isScoped) {
|
|
787
|
-
const stored = stmts
|
|
788
|
-
? stmts.getHash.get(filePath)
|
|
789
|
-
: indexHandle.query('SELECT content_hash FROM style_declarations WHERE file_path = ? LIMIT 1', [filePath])[0];
|
|
824
|
+
const stored = getStyleHash(stmts, filePath);
|
|
790
825
|
if (stored?.content_hash === facts.contentHash)
|
|
791
826
|
return;
|
|
792
827
|
}
|
|
793
|
-
// Delete stale rows — all
|
|
794
|
-
// so a file that *lost* its styles also loses its stale
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
stmts.delUnread.run(filePath);
|
|
800
|
-
stmts.delClass.run(filePath);
|
|
801
|
-
}
|
|
802
|
-
else {
|
|
803
|
-
indexHandle.run('DELETE FROM style_declarations WHERE file_path = ?', [filePath]);
|
|
804
|
-
indexHandle.run('DELETE FROM style_class_usage WHERE file_path = ?', [filePath]);
|
|
805
|
-
indexHandle.run('DELETE FROM style_tokens WHERE file_path = ?', [filePath]);
|
|
806
|
-
indexHandle.run('DELETE FROM style_unread_sources WHERE file_path = ?', [filePath]);
|
|
807
|
-
indexHandle.run('DELETE FROM style_defined_classes WHERE file_path = ?', [filePath]);
|
|
808
|
-
}
|
|
828
|
+
// Delete stale rows — all four tables, mirroring the old deleteFileEntries
|
|
829
|
+
// so a file that *lost* its styles also loses its stale rows.
|
|
830
|
+
runStyleWrite(stmts, 'delDecl', [filePath]);
|
|
831
|
+
runStyleWrite(stmts, 'delUsage', [filePath]);
|
|
832
|
+
runStyleWrite(stmts, 'delTok', [filePath]);
|
|
833
|
+
runStyleWrite(stmts, 'delClass', [filePath]);
|
|
809
834
|
// Insert declarations (and their defined-class catalog entries).
|
|
810
835
|
for (const decl of facts.declarations) {
|
|
811
|
-
|
|
812
|
-
stmts.insDecl.run(decl.property, decl.rawValue, decl.normalizedValue ? JSON.stringify(decl.normalizedValue) : null, decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, facts.contentHash);
|
|
813
|
-
}
|
|
814
|
-
else {
|
|
815
|
-
indexHandle.run('INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [decl.property, decl.rawValue, decl.normalizedValue ? JSON.stringify(decl.normalizedValue) : null, decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, facts.contentHash]);
|
|
816
|
-
}
|
|
836
|
+
runStyleWrite(stmts, 'insDecl', [decl.property, decl.rawValue, decl.normalizedValue ? JSON.stringify(decl.normalizedValue) : null, decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, facts.contentHash]);
|
|
817
837
|
if (decl.context) {
|
|
818
838
|
for (const m of decl.context.matchAll(/\.([a-zA-Z0-9_-]+)/g)) {
|
|
819
|
-
|
|
820
|
-
stmts.insClass.run(m[1], decl.filePath);
|
|
821
|
-
else
|
|
822
|
-
indexHandle.run('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)', [m[1], decl.filePath]);
|
|
839
|
+
runStyleWrite(stmts, 'insClass', [m[1], decl.filePath]);
|
|
823
840
|
}
|
|
824
841
|
}
|
|
825
842
|
}
|
|
826
843
|
// Insert class usage.
|
|
827
844
|
for (const cu of facts.classUsage) {
|
|
828
|
-
|
|
829
|
-
stmts.insUsage.run(cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0);
|
|
830
|
-
else
|
|
831
|
-
indexHandle.run('INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)', [cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0]);
|
|
845
|
+
runStyleWrite(stmts, 'insUsage', [cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0]);
|
|
832
846
|
}
|
|
833
847
|
};
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
insertOne(fp, f); })();
|
|
837
|
-
else
|
|
838
|
-
for (const [fp, f] of entries)
|
|
839
|
-
insertOne(fp, f);
|
|
848
|
+
rawDb.transaction(() => { for (const [fp, f] of entries)
|
|
849
|
+
insertOne(fp, f); })();
|
|
840
850
|
return contributed;
|
|
841
851
|
}
|
|
842
852
|
/**
|
|
@@ -847,6 +857,8 @@ function insertSourceStyleFacts(indexHandle, sourceFacts, isScoped) {
|
|
|
847
857
|
* Spec 26 Phase 2: CSS facts from the styles-css visitor are inserted into
|
|
848
858
|
* the DB before the analyzer runs. For .css files, the AST extraction replaces
|
|
849
859
|
* the regex-based styleIndexer path (which now skips .css files).
|
|
860
|
+
*
|
|
861
|
+
* @returns The styles stage-3 reducer.
|
|
850
862
|
*/
|
|
851
863
|
export function createStylesReducer() {
|
|
852
864
|
return {
|
|
@@ -888,77 +900,44 @@ export function createStylesReducer() {
|
|
|
888
900
|
// statement — measured at 345ms for 5 files / ~13k declarations on
|
|
889
901
|
// recall-protocol. Prepare-once + one transaction collapses that.
|
|
890
902
|
const rawDb = context.indexHandle.rawDb;
|
|
891
|
-
const stmts =
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
delUsage: rawDb.prepare('DELETE FROM style_class_usage WHERE file_path = ?'),
|
|
896
|
-
delClass: rawDb.prepare('DELETE FROM style_defined_classes WHERE file_path = ?'),
|
|
897
|
-
insDecl: rawDb.prepare('INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'),
|
|
898
|
-
insClass: rawDb.prepare('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)'),
|
|
899
|
-
insTok: rawDb.prepare('INSERT INTO style_tokens (name, value, file_path, mechanism) VALUES (?, ?, ?, ?)'),
|
|
900
|
-
insUsage: rawDb.prepare('INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)'),
|
|
901
|
-
}
|
|
902
|
-
: null;
|
|
903
|
+
const stmts = {
|
|
904
|
+
...prepareCommonStyleStatements(rawDb),
|
|
905
|
+
insTok: rawDb.prepare(SQL_STYLE_INS_TOKEN),
|
|
906
|
+
};
|
|
903
907
|
const insertAll = () => {
|
|
904
908
|
for (const [filePath, facts] of Object.entries(cssFacts)) {
|
|
905
909
|
// Delete old entries for this file (replaces the styleIndexer path)
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
stmts.delClass.run(filePath);
|
|
911
|
-
}
|
|
912
|
-
else {
|
|
913
|
-
indexHandle.run('DELETE FROM style_declarations WHERE file_path = ?', [filePath]);
|
|
914
|
-
indexHandle.run('DELETE FROM style_tokens WHERE file_path = ?', [filePath]);
|
|
915
|
-
indexHandle.run('DELETE FROM style_class_usage WHERE file_path = ?', [filePath]);
|
|
916
|
-
indexHandle.run('DELETE FROM style_defined_classes WHERE file_path = ?', [filePath]);
|
|
917
|
-
}
|
|
910
|
+
runStyleWrite(stmts, 'delDecl', [filePath]);
|
|
911
|
+
runStyleWrite(stmts, 'delTok', [filePath]);
|
|
912
|
+
runStyleWrite(stmts, 'delUsage', [filePath]);
|
|
913
|
+
runStyleWrite(stmts, 'delClass', [filePath]);
|
|
918
914
|
// Compute content hash for the file
|
|
919
915
|
const contentStr = JSON.stringify({ declarations: facts.declarations.length, tokens: facts.tokens.length, classUsage: facts.classUsage.length });
|
|
920
916
|
const contentHash = createHash('sha256').update(contentStr).digest('hex').slice(0, 16);
|
|
921
917
|
// Insert declarations
|
|
922
918
|
for (const decl of facts.declarations) {
|
|
923
|
-
|
|
924
|
-
stmts.insDecl.run(decl.property, decl.rawValue, JSON.stringify(decl.normalizedValue), decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, contentHash);
|
|
925
|
-
}
|
|
926
|
-
else {
|
|
927
|
-
indexHandle.run('INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [decl.property, decl.rawValue, JSON.stringify(decl.normalizedValue), decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, contentHash]);
|
|
928
|
-
}
|
|
919
|
+
runStyleWrite(stmts, 'insDecl', [decl.property, decl.rawValue, JSON.stringify(decl.normalizedValue), decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, contentHash]);
|
|
929
920
|
// Populate the defined-class catalog (Spec 45) from any class
|
|
930
921
|
// selectors in the rule's context so styles/undefined-class can
|
|
931
922
|
// resolve names via `style_defined_classes` instead of a
|
|
932
923
|
// full-corpus regex scan over every declaration.
|
|
933
924
|
if (decl.context) {
|
|
934
925
|
for (const m of decl.context.matchAll(/\.([a-zA-Z0-9_-]+)/g)) {
|
|
935
|
-
|
|
936
|
-
stmts.insClass.run(m[1], decl.filePath);
|
|
937
|
-
else
|
|
938
|
-
indexHandle.run('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)', [m[1], decl.filePath]);
|
|
926
|
+
runStyleWrite(stmts, 'insClass', [m[1], decl.filePath]);
|
|
939
927
|
}
|
|
940
928
|
}
|
|
941
929
|
}
|
|
942
930
|
// Insert tokens
|
|
943
931
|
for (const tok of facts.tokens) {
|
|
944
|
-
|
|
945
|
-
stmts.insTok.run(tok.name, tok.value, tok.filePath, tok.mechanism);
|
|
946
|
-
else
|
|
947
|
-
indexHandle.run('INSERT INTO style_tokens (name, value, file_path, mechanism) VALUES (?, ?, ?, ?)', [tok.name, tok.value, tok.filePath, tok.mechanism]);
|
|
932
|
+
runStyleWrite(stmts, 'insTok', [tok.name, tok.value, tok.filePath, tok.mechanism]);
|
|
948
933
|
}
|
|
949
934
|
// Insert class usage
|
|
950
935
|
for (const cu of facts.classUsage) {
|
|
951
|
-
|
|
952
|
-
stmts.insUsage.run(cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0);
|
|
953
|
-
else
|
|
954
|
-
indexHandle.run('INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)', [cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0]);
|
|
936
|
+
runStyleWrite(stmts, 'insUsage', [cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0]);
|
|
955
937
|
}
|
|
956
938
|
}
|
|
957
939
|
};
|
|
958
|
-
|
|
959
|
-
rawDb.transaction(insertAll)();
|
|
960
|
-
else
|
|
961
|
-
insertAll();
|
|
940
|
+
rawDb.transaction(insertAll)();
|
|
962
941
|
}
|
|
963
942
|
// Short-circuit on non-style-bearing scoped runs — the source and CSS
|
|
964
943
|
// facts above are already written (so stale rows are dropped); only the
|
|
@@ -990,10 +969,32 @@ export function createStylesReducer() {
|
|
|
990
969
|
};
|
|
991
970
|
}
|
|
992
971
|
// ── Conventions reducer (stage 3) ────────────────────────────────────────────
|
|
972
|
+
/**
|
|
973
|
+
* Builds the shared analyzer-config bag for reducers that query the code
|
|
974
|
+
* index: the reducer's own namespaced config spread over the index handle,
|
|
975
|
+
* project root, in-scope file set, and scope flag. Reducers that need extra
|
|
976
|
+
* inputs (`readSource`, `exportsMap`) spread this and layer them on top.
|
|
977
|
+
*
|
|
978
|
+
* Scoped/diff audits thread `_infra.files` (absolute) as `scopedFiles` so the
|
|
979
|
+
* analyzer scopes its function/file queries to only the changed files;
|
|
980
|
+
* `functions.file_path` is also absolute, so the IN clause matches directly.
|
|
981
|
+
*/
|
|
982
|
+
function makeIndexAnalyzerConfig(context) {
|
|
983
|
+
const scopedFiles = context.isScoped ? (context.config.files ?? []) : undefined;
|
|
984
|
+
return {
|
|
985
|
+
...context.config,
|
|
986
|
+
indexHandle: context.indexHandle,
|
|
987
|
+
projectRoot: context.projectRoot,
|
|
988
|
+
scopedFiles,
|
|
989
|
+
isScoped: context.isScoped,
|
|
990
|
+
};
|
|
991
|
+
}
|
|
993
992
|
/**
|
|
994
993
|
* Conventions reducer — queries conventions table and flags deviations
|
|
995
994
|
* (usage-pair, import-form, error-handling, export-shape, naming).
|
|
996
995
|
* Uses analyzer.analyze([]) with rawDb injected.
|
|
996
|
+
*
|
|
997
|
+
* @returns The conventions stage-3 reducer.
|
|
997
998
|
*/
|
|
998
999
|
export function createConventionsReducer() {
|
|
999
1000
|
return {
|
|
@@ -1014,12 +1015,7 @@ export function createConventionsReducer() {
|
|
|
1014
1015
|
.filter(([, data]) => data?.exports?.length)
|
|
1015
1016
|
.map(([filePath, data]) => [filePath, data.exports]))
|
|
1016
1017
|
: undefined;
|
|
1017
|
-
|
|
1018
|
-
// its function/file queries to only the changed files. `_infra.files` are
|
|
1019
|
-
// absolute; `functions.file_path` is also absolute (function-index visitor
|
|
1020
|
-
// stores `context.filePath`), so the IN clause matches directly.
|
|
1021
|
-
const scopedFiles = context.isScoped ? (context.config.files ?? []) : undefined;
|
|
1022
|
-
const config = { ...context.config, indexHandle: context.indexHandle, projectRoot: context.projectRoot, readSource: context.readSource, exportsMap, scopedFiles, isScoped: context.isScoped };
|
|
1018
|
+
const config = { ...makeIndexAnalyzerConfig(context), readSource: context.readSource, exportsMap };
|
|
1023
1019
|
const result = await analyzer.analyze([], config);
|
|
1024
1020
|
// Count the INPUT the conventions analyzer reads (the function index the
|
|
1025
1021
|
// function-index visitor populated), not the derived `conventions` table it
|
|
@@ -1028,7 +1024,12 @@ export function createConventionsReducer() {
|
|
|
1028
1024
|
// analyzer — counting it as "facts consumed" misreports a present input as
|
|
1029
1025
|
// notApplicable. (Spec 39 R2/R3 bug B)
|
|
1030
1026
|
const factsConsumed = context.indexHandle.count('functions');
|
|
1031
|
-
return {
|
|
1027
|
+
return {
|
|
1028
|
+
violations: result.violations ?? [],
|
|
1029
|
+
facts: {},
|
|
1030
|
+
factsConsumed,
|
|
1031
|
+
...(result.diagnostics && result.diagnostics.length > 0 && { diagnostics: result.diagnostics }),
|
|
1032
|
+
};
|
|
1032
1033
|
}
|
|
1033
1034
|
catch {
|
|
1034
1035
|
return { violations: [], facts: {} };
|
|
@@ -1044,12 +1045,13 @@ export function createConventionsReducer() {
|
|
|
1044
1045
|
* Cross-domain reducer — runs written-never-read, read-never-written,
|
|
1045
1046
|
* transaction-boundary risk, no-validator-reachable, and uncovered-risk detectors.
|
|
1046
1047
|
* Uses analyzer.analyze([]) with rawDb injected.
|
|
1048
|
+
*
|
|
1049
|
+
* @returns The cross-domain stage-4 reducer.
|
|
1047
1050
|
*/
|
|
1048
1051
|
export function createCrossDomainReducer() {
|
|
1049
1052
|
return {
|
|
1050
1053
|
name: 'cross-domain',
|
|
1051
1054
|
stage: 'derivedReducer',
|
|
1052
|
-
consumes: [],
|
|
1053
1055
|
getRuleIds: () => getRuleIdsFor('cross-domain'),
|
|
1054
1056
|
async reduce(_allFacts, context) {
|
|
1055
1057
|
if (!context.indexHandle)
|
|
@@ -1057,11 +1059,7 @@ export function createCrossDomainReducer() {
|
|
|
1057
1059
|
try {
|
|
1058
1060
|
const { CrossDomainAnalyzer } = await import('./analyzers/crossDomain/CrossDomainAnalyzer.js');
|
|
1059
1061
|
const analyzer = new CrossDomainAnalyzer();
|
|
1060
|
-
|
|
1061
|
-
// anchor to changed files on diff audits; full audits keep the root-LIKE
|
|
1062
|
-
// scope (resolveFileScope falls back when isScoped is false).
|
|
1063
|
-
const scopedFiles = context.isScoped ? (context.config.files ?? []) : undefined;
|
|
1064
|
-
const config = { ...context.config, indexHandle: context.indexHandle, projectRoot: context.projectRoot, scopedFiles, isScoped: context.isScoped };
|
|
1062
|
+
const config = makeIndexAnalyzerConfig(context);
|
|
1065
1063
|
const result = await analyzer.analyze([], config);
|
|
1066
1064
|
// Count DB rows consumed across primary cross-domain tables
|
|
1067
1065
|
let factsConsumed = 0;
|
|
@@ -1095,7 +1093,7 @@ export function createCrossDomainReducer() {
|
|
|
1095
1093
|
// DependencyGraphBuilder) are corpus-wide: they compare schemas/endpoints/cycles
|
|
1096
1094
|
// across files and languages. They run as Stage-4 derived reducers over a single
|
|
1097
1095
|
// shared entity-extraction visitor, so each analyzer's rule IDs map to its own
|
|
1098
|
-
// result row (coverage resolves by
|
|
1096
|
+
// result row (coverage resolves by each rule's analyzer — one reducer per
|
|
1099
1097
|
// analyzer, not one reducer fanning out to three names).
|
|
1100
1098
|
//
|
|
1101
1099
|
// Scope-awareness ("Stage 4, and measure the gate"): the corpus-wide graph/cycle
|
|
@@ -1180,10 +1178,11 @@ function clCollectFileReferences(node, sourceCode) {
|
|
|
1180
1178
|
}
|
|
1181
1179
|
else if (t === 'identifier') {
|
|
1182
1180
|
// A bare identifier used as a value — an argument to a call
|
|
1183
|
-
// (`arr.map(singularize)`)
|
|
1184
|
-
//
|
|
1185
|
-
//
|
|
1186
|
-
// captured
|
|
1181
|
+
// (`arr.map(singularize)`), an array element (`[missedClose, …]`), or an
|
|
1182
|
+
// object-literal value (`{ hostname: isValidHostname }`) — is a function
|
|
1183
|
+
// reference. Declaration names (`name` fields) and member accesses
|
|
1184
|
+
// (`property_identifier`) are separate node types and are not captured
|
|
1185
|
+
// here.
|
|
1187
1186
|
const parent = n.parent;
|
|
1188
1187
|
if (parent && (parent.type === 'arguments' || parent.type === 'array')) {
|
|
1189
1188
|
const text = getNodeText(n, sourceCode).trim();
|
|
@@ -1191,13 +1190,34 @@ function clCollectFileReferences(node, sourceCode) {
|
|
|
1191
1190
|
names.add(text);
|
|
1192
1191
|
}
|
|
1193
1192
|
}
|
|
1193
|
+
else if (t === 'shorthand_property_identifier') {
|
|
1194
|
+
// `{ isValidHostname }` — the shorthand key doubles as a reference to the
|
|
1195
|
+
// in-scope function of the same name. A function wired up only through an
|
|
1196
|
+
// object literal (`FORMAT_VALIDATORS`) has no call edge, so without this
|
|
1197
|
+
// it reads orphaned even though it is genuinely referenced.
|
|
1198
|
+
const text = getNodeText(n, sourceCode).trim();
|
|
1199
|
+
if (text && /^[A-Za-z_$][\w$]*$/.test(text))
|
|
1200
|
+
names.add(text);
|
|
1201
|
+
}
|
|
1202
|
+
else if (t === 'pair') {
|
|
1203
|
+
// `{ key: value }` — the *value* may be a bare identifier referencing a
|
|
1204
|
+
// function (`{ hostname: isValidHostname }`); the key is a property name,
|
|
1205
|
+
// not a reference, and must not be added.
|
|
1206
|
+
const value = getFieldNode(n, 'value');
|
|
1207
|
+
if (value?.type === 'identifier') {
|
|
1208
|
+
const text = getNodeText(value, sourceCode).trim();
|
|
1209
|
+
if (text && /^[A-Za-z_$][\w$]*$/.test(text))
|
|
1210
|
+
names.add(text);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1194
1213
|
for (const child of n.children ?? [])
|
|
1195
1214
|
walk(child);
|
|
1196
1215
|
};
|
|
1197
1216
|
walk(node);
|
|
1198
1217
|
return [...names];
|
|
1199
1218
|
}
|
|
1200
|
-
function clMakeFunction(
|
|
1219
|
+
function clMakeFunction(opts) {
|
|
1220
|
+
const { filePath, lang, name, line, signature, purpose, params, callees, exported, complexity, isMethod = false } = opts;
|
|
1201
1221
|
return {
|
|
1202
1222
|
id: clEntityId(filePath, 'function', name, line),
|
|
1203
1223
|
name,
|
|
@@ -1245,7 +1265,7 @@ function clDynamicImport(node, sourceCode) {
|
|
|
1245
1265
|
// not a computed specifier — `import(\`@babel/plugin-syntax-jsx\`)` resolves
|
|
1246
1266
|
// to an edge just like `import('@babel/plugin-syntax-jsx')`. Only an
|
|
1247
1267
|
// interpolated template (`` import(`./${name}.js`) ``) is genuinely computed.
|
|
1248
|
-
const templateNode = args?.children?.find((c) => c.type === 'template_string'
|
|
1268
|
+
const templateNode = args?.children?.find((c) => c.type === 'template_string');
|
|
1249
1269
|
if (templateNode) {
|
|
1250
1270
|
const hasSubstitution = (templateNode.children ?? []).some((c) => c.type === 'template_substitution');
|
|
1251
1271
|
if (!hasSubstitution) {
|
|
@@ -1375,7 +1395,16 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
|
|
|
1375
1395
|
}
|
|
1376
1396
|
}
|
|
1377
1397
|
const line = node.location.start.line;
|
|
1378
|
-
const entity = clMakeFunction(
|
|
1398
|
+
const entity = clMakeFunction({
|
|
1399
|
+
filePath, lang, name: displayName, line,
|
|
1400
|
+
signature: clSignatureText(node, sourceCode) || getNodeText(node, sourceCode),
|
|
1401
|
+
purpose: clBodyText(node, sourceCode),
|
|
1402
|
+
params: clExtractTSParams(node, sourceCode),
|
|
1403
|
+
callees: clCollectCallees(node, sourceCode),
|
|
1404
|
+
exported: isExported(node),
|
|
1405
|
+
complexity: calculateComplexity(node),
|
|
1406
|
+
isMethod: node.type === 'method_definition',
|
|
1407
|
+
});
|
|
1379
1408
|
entity.metadata.fileReferences = fileReferences;
|
|
1380
1409
|
out.push(entity);
|
|
1381
1410
|
return;
|
|
@@ -1389,7 +1418,15 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
|
|
|
1389
1418
|
if (!name)
|
|
1390
1419
|
return;
|
|
1391
1420
|
const line = node.location.start.line;
|
|
1392
|
-
const entity = clMakeFunction(
|
|
1421
|
+
const entity = clMakeFunction({
|
|
1422
|
+
filePath, lang, name, line,
|
|
1423
|
+
signature: clSignatureText(arrow, sourceCode) || getNodeText(arrow, sourceCode),
|
|
1424
|
+
purpose: clBodyText(arrow, sourceCode),
|
|
1425
|
+
params: [],
|
|
1426
|
+
callees: clCollectCallees(arrow, sourceCode),
|
|
1427
|
+
exported: isExported(node),
|
|
1428
|
+
complexity: calculateComplexity(node),
|
|
1429
|
+
});
|
|
1393
1430
|
entity.metadata.fileReferences = fileReferences;
|
|
1394
1431
|
out.push(entity);
|
|
1395
1432
|
return;
|
|
@@ -1449,18 +1486,24 @@ function clExtractGoParams(node, sourceCode) {
|
|
|
1449
1486
|
}
|
|
1450
1487
|
function clExtractGoStructFields(structNode, sourceCode) {
|
|
1451
1488
|
const fields = [];
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1489
|
+
// A struct's fields are not direct children of `struct_type` — tree-sitter-go
|
|
1490
|
+
// wraps them in a `field_declaration_list`. Iterate one level in so the
|
|
1491
|
+
// per-field name/type/tag extraction actually sees them.
|
|
1492
|
+
for (const child of structNode.children ?? []) {
|
|
1493
|
+
const decls = child.type === 'field_declaration_list' ? (child.children ?? []) : [child];
|
|
1494
|
+
for (const f of decls) {
|
|
1495
|
+
if (f.type !== 'field_declaration')
|
|
1496
|
+
continue;
|
|
1497
|
+
const nameNode = getFieldNode(f, 'name');
|
|
1498
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1499
|
+
if (!name)
|
|
1500
|
+
continue;
|
|
1501
|
+
const typeNode = getFieldNode(f, 'type');
|
|
1502
|
+
const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
|
|
1503
|
+
const tagNode = getFieldNode(f, 'tag');
|
|
1504
|
+
const tag = tagNode ? getNodeText(tagNode, sourceCode) : undefined;
|
|
1505
|
+
fields.push({ name, type, isExported: clIsExportedGo(name), tag });
|
|
1506
|
+
}
|
|
1464
1507
|
}
|
|
1465
1508
|
return fields;
|
|
1466
1509
|
}
|
|
@@ -1475,7 +1518,15 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
|
1475
1518
|
const receiverType = receiver ? clGoReceiverType(receiver, sourceCode) : undefined;
|
|
1476
1519
|
const displayName = receiverType ? `${receiverType}.${name}` : name;
|
|
1477
1520
|
const line = node.location.start.line;
|
|
1478
|
-
const entity = clMakeFunction(
|
|
1521
|
+
const entity = clMakeFunction({
|
|
1522
|
+
filePath, lang: 'go', name: displayName, line,
|
|
1523
|
+
signature: clSignatureText(node, sourceCode) || getNodeText(node, sourceCode),
|
|
1524
|
+
purpose: clBodyText(node, sourceCode),
|
|
1525
|
+
params: clExtractGoParams(node, sourceCode),
|
|
1526
|
+
callees: clCollectCallees(node, sourceCode),
|
|
1527
|
+
exported: clIsExportedGo(name),
|
|
1528
|
+
complexity: calculateComplexity(node),
|
|
1529
|
+
});
|
|
1479
1530
|
entity.metadata.fileReferences = fileReferences;
|
|
1480
1531
|
out.push(entity);
|
|
1481
1532
|
return;
|
|
@@ -1534,6 +1585,38 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
|
1534
1585
|
});
|
|
1535
1586
|
}
|
|
1536
1587
|
// ── Shared entity visitor (Stage 2) ──────────────────────────────────────────
|
|
1588
|
+
/**
|
|
1589
|
+
* Spec 68 §3.2 — the `cross-language-entities` FileProcessor extraction.
|
|
1590
|
+
*
|
|
1591
|
+
* The entity half of {@link createCrossLanguageEntityVisitor}, factored out so
|
|
1592
|
+
* the phase-model producer can call it directly without the Stage-2 `visit`
|
|
1593
|
+
* plumbing (violations/facts/indexFacts). Emits the same `CrossLanguageEntity[]`
|
|
1594
|
+
* the visitor feeds the cross-language analyzers; the per-file imports/exports
|
|
1595
|
+
* half of the visitor is the reachability/coverage concern (§8), not this fact.
|
|
1596
|
+
*
|
|
1597
|
+
* @param ast - The parsed file to extract entities from.
|
|
1598
|
+
* @param filePath - The file path stamping each extracted entity.
|
|
1599
|
+
* @param sourceCode - Source text used for names and signatures.
|
|
1600
|
+
* @param lang - The resolved language (`go`, `typescript`, or `javascript`).
|
|
1601
|
+
* @returns The cross-language entities extracted from the file.
|
|
1602
|
+
*/
|
|
1603
|
+
export function extractCrossLanguageEntities(ast, filePath, sourceCode, lang) {
|
|
1604
|
+
const root = ast.root;
|
|
1605
|
+
const entities = [];
|
|
1606
|
+
if (lang === 'go') {
|
|
1607
|
+
clExtractGoEntities(root, filePath, sourceCode, entities);
|
|
1608
|
+
}
|
|
1609
|
+
else {
|
|
1610
|
+
clExtractTSEntities(root, filePath, sourceCode, lang, entities);
|
|
1611
|
+
}
|
|
1612
|
+
return entities;
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Create the cross-language-entities stage-2 visitor, which extracts entities
|
|
1616
|
+
* and import/export info for the cross-language analyzers.
|
|
1617
|
+
*
|
|
1618
|
+
* @returns The cross-language-entities stage-2 visitor.
|
|
1619
|
+
*/
|
|
1537
1620
|
export function createCrossLanguageEntityVisitor() {
|
|
1538
1621
|
return {
|
|
1539
1622
|
name: 'cross-language-entities',
|
|
@@ -1545,7 +1628,7 @@ export function createCrossLanguageEntityVisitor() {
|
|
|
1545
1628
|
if (!filePath)
|
|
1546
1629
|
return { violations: [], facts: {} };
|
|
1547
1630
|
const lang = getLanguageFromPath(filePath);
|
|
1548
|
-
if (lang
|
|
1631
|
+
if (lang !== 'typescript' && lang !== 'javascript' && lang !== 'go')
|
|
1549
1632
|
return { violations: [], facts: {} };
|
|
1550
1633
|
const root = ast.root;
|
|
1551
1634
|
const entities = [];
|
|
@@ -1686,6 +1769,11 @@ function clComputeReachability(fileFacts, options) {
|
|
|
1686
1769
|
* nothing *and* not a framework entry point. `deadPreException` is the
|
|
1687
1770
|
* empty-importers count before the entry-point exception, so the drop
|
|
1688
1771
|
* (`deadPreException - untestedDead`) attributes the entry points exempted.
|
|
1772
|
+
*
|
|
1773
|
+
* @param fileFacts - Per-file import/export info keyed by project-relative path.
|
|
1774
|
+
* @param importersOf - Map of file to the set of files that import it.
|
|
1775
|
+
* @param packageEntrySet - Framework/package entry points exempted from the dead bucket.
|
|
1776
|
+
* @returns The test-coverage classification report.
|
|
1689
1777
|
*/
|
|
1690
1778
|
export function classifyTestCoverage(fileFacts, importersOf, packageEntrySet) {
|
|
1691
1779
|
const result = {
|
|
@@ -1750,6 +1838,9 @@ export function classifyTestCoverage(fileFacts, importersOf, packageEntrySet) {
|
|
|
1750
1838
|
* the same file name appearing in several sibling directories (e.g. two
|
|
1751
1839
|
* `ErrorState.tsx`, three `services.ts` under separate `reports` folders). Only
|
|
1752
1840
|
* basenames with ≥2 files become clusters; a singleton dead file is not a duplicate.
|
|
1841
|
+
*
|
|
1842
|
+
* @param deadFiles - Unreferenced module paths to cluster by basename.
|
|
1843
|
+
* @returns Dead-module clusters (only basenames with two or more files).
|
|
1753
1844
|
*/
|
|
1754
1845
|
export function clusterDeadModules(deadFiles) {
|
|
1755
1846
|
const byBasename = new Map();
|
|
@@ -1789,6 +1880,15 @@ function clBuildReferences(entities) {
|
|
|
1789
1880
|
const eDir = e.file.split('/').slice(0, -1).join('/');
|
|
1790
1881
|
for (const callee of callees) {
|
|
1791
1882
|
const name = (callee.split('.').pop() ?? callee).toLowerCase();
|
|
1883
|
+
// A free function that calls itself by bare name is recursing. Resolving
|
|
1884
|
+
// that name to a *different* same-named entity (a second `dfs` helper in
|
|
1885
|
+
// the same file) fabricates a spurious mutual-recursion cycle between two
|
|
1886
|
+
// unrelated functions and reports it as a circular dependency. Recursion
|
|
1887
|
+
// is a self-reference, not a module cycle — drop it rather than resolve
|
|
1888
|
+
// it sideways. (Methods are named `Class.method`, so a bare callee never
|
|
1889
|
+
// matches their own name and this only affects free functions.)
|
|
1890
|
+
if (name === e.name.toLowerCase())
|
|
1891
|
+
continue;
|
|
1792
1892
|
const others = (byName.get(name) ?? []).filter((t) => t.id !== e.id);
|
|
1793
1893
|
if (others.length === 0)
|
|
1794
1894
|
continue;
|
|
@@ -1825,11 +1925,16 @@ function clBuildReferences(entities) {
|
|
|
1825
1925
|
return refs;
|
|
1826
1926
|
}
|
|
1827
1927
|
// ── schema-validator reducer (Stage 4) ───────────────────────────────────────
|
|
1928
|
+
/**
|
|
1929
|
+
* Create the schema-validator stage-4 reducer, which compares schemas across
|
|
1930
|
+
* language boundaries (TS interfaces vs Go structs).
|
|
1931
|
+
*
|
|
1932
|
+
* @returns The schema-validator stage-4 reducer.
|
|
1933
|
+
*/
|
|
1828
1934
|
export function createSchemaValidatorReducer() {
|
|
1829
1935
|
return {
|
|
1830
1936
|
name: 'schema-validator',
|
|
1831
1937
|
stage: 'derivedReducer',
|
|
1832
|
-
consumes: ['cross-language-entities'],
|
|
1833
1938
|
getRuleIds: () => getRuleIdsFor('schema-validator'),
|
|
1834
1939
|
async reduce(allFacts, context) {
|
|
1835
1940
|
if (context.isScoped) {
|
|
@@ -1881,11 +1986,16 @@ export function createSchemaValidatorReducer() {
|
|
|
1881
1986
|
};
|
|
1882
1987
|
}
|
|
1883
1988
|
// ── api-contract reducer (Stage 4) ───────────────────────────────────────────
|
|
1989
|
+
/**
|
|
1990
|
+
* Create the api-contract stage-4 reducer, which compares frontend API calls
|
|
1991
|
+
* against backend endpoints across languages.
|
|
1992
|
+
*
|
|
1993
|
+
* @returns The api-contract stage-4 reducer.
|
|
1994
|
+
*/
|
|
1884
1995
|
export function createAPIContractReducer() {
|
|
1885
1996
|
return {
|
|
1886
1997
|
name: 'api-contract',
|
|
1887
1998
|
stage: 'derivedReducer',
|
|
1888
|
-
consumes: ['cross-language-entities'],
|
|
1889
1999
|
getRuleIds: () => getRuleIdsFor('api-contract'),
|
|
1890
2000
|
async reduce(allFacts, context) {
|
|
1891
2001
|
if (context.isScoped) {
|
|
@@ -1917,11 +2027,39 @@ export function createAPIContractReducer() {
|
|
|
1917
2027
|
};
|
|
1918
2028
|
}
|
|
1919
2029
|
// ── dependency-graph reducer (Stage 4) ───────────────────────────────────────
|
|
2030
|
+
/**
|
|
2031
|
+
* Persist a `graph_cache` edge set in one transaction: delete the graph's
|
|
2032
|
+
* existing rows, then insert `rows` as (node_key, neighbor_key, weight).
|
|
2033
|
+
* Advisory — a failure here only degrades the post-pipeline hotspot ranking,
|
|
2034
|
+
* so it is swallowed rather than surfaced as an audit error.
|
|
2035
|
+
*/
|
|
2036
|
+
function persistGraphEdges(rawDb, graphType, rows) {
|
|
2037
|
+
try {
|
|
2038
|
+
const db = rawDb;
|
|
2039
|
+
const del = db.prepare(`DELETE FROM graph_cache WHERE graph_type = '${graphType}'`);
|
|
2040
|
+
const ins = db.prepare(`INSERT OR REPLACE INTO graph_cache (graph_type, node_key, neighbor_key, weight) VALUES ('${graphType}', ?, ?, ?)`);
|
|
2041
|
+
const tx = db.transaction(() => {
|
|
2042
|
+
del.run();
|
|
2043
|
+
for (const [nodeKey, neighborKey, weight] of rows) {
|
|
2044
|
+
ins.run(nodeKey, neighborKey, weight);
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
tx();
|
|
2048
|
+
}
|
|
2049
|
+
catch {
|
|
2050
|
+
// Advisory persistence — ranking falls back to a neutral default without it.
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Create the dependency-graph stage-4 reducer, which detects dependency cycles,
|
|
2055
|
+
* hubs, and orphaned nodes.
|
|
2056
|
+
*
|
|
2057
|
+
* @returns The dependency-graph stage-4 reducer.
|
|
2058
|
+
*/
|
|
1920
2059
|
export function createDependencyGraphReducer() {
|
|
1921
2060
|
return {
|
|
1922
2061
|
name: 'dependency-graph',
|
|
1923
2062
|
stage: 'derivedReducer',
|
|
1924
|
-
consumes: ['cross-language-entities'],
|
|
1925
2063
|
getRuleIds: () => getRuleIdsFor('dependency-graph'),
|
|
1926
2064
|
async reduce(allFacts, context) {
|
|
1927
2065
|
if (context.isScoped) {
|
|
@@ -1962,7 +2100,7 @@ export function createDependencyGraphReducer() {
|
|
|
1962
2100
|
type: issue.type, // dependency-graph rules match on field: 'type'
|
|
1963
2101
|
analyzer: 'dependency-graph',
|
|
1964
2102
|
category: 'cross-language-dependency',
|
|
1965
|
-
|
|
2103
|
+
symbol: orphan.name,
|
|
1966
2104
|
details: { orphanId: id },
|
|
1967
2105
|
});
|
|
1968
2106
|
}
|
|
@@ -2001,45 +2139,21 @@ export function createDependencyGraphReducer() {
|
|
|
2001
2139
|
// Persist reachability to graph_cache so the post-pipeline reorder
|
|
2002
2140
|
// (auditRunner hotspot block) can rank every finding by live/dead.
|
|
2003
2141
|
if (context.indexHandle?.rawDb) {
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
const del = db.prepare("DELETE FROM graph_cache WHERE graph_type = 'reachability'");
|
|
2007
|
-
const ins = db.prepare("INSERT OR REPLACE INTO graph_cache (graph_type, node_key, neighbor_key, weight) VALUES ('reachability', ?, '', ?)");
|
|
2008
|
-
const tx = db.transaction(() => {
|
|
2009
|
-
del.run();
|
|
2010
|
-
for (const [fp, score] of reachability) {
|
|
2011
|
-
ins.run(fp, score);
|
|
2012
|
-
}
|
|
2013
|
-
});
|
|
2014
|
-
tx();
|
|
2015
|
-
}
|
|
2016
|
-
catch {
|
|
2017
|
-
// Reachability persistence is advisory — ranking falls back to a
|
|
2018
|
-
// neutral default without it.
|
|
2019
|
-
}
|
|
2142
|
+
const reachabilityRows = [...reachability].map(([fp, score]) => [fp, '', score]);
|
|
2143
|
+
persistGraphEdges(context.indexHandle.rawDb, 'reachability', reachabilityRows);
|
|
2020
2144
|
}
|
|
2021
2145
|
// Spec 60 R1 — persist the file-level import edges (reverse adjacency:
|
|
2022
2146
|
// module → its direct importers) so "what tests exercise this module"
|
|
2023
2147
|
// is answerable downstream from `importersOf`, not the symbol-level
|
|
2024
2148
|
// `function_dependencies` table.
|
|
2025
2149
|
if (context.indexHandle?.rawDb) {
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
del.run();
|
|
2032
|
-
for (const [fp, importers] of importersOf) {
|
|
2033
|
-
for (const importer of importers) {
|
|
2034
|
-
ins.run(fp, importer);
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
});
|
|
2038
|
-
tx();
|
|
2039
|
-
}
|
|
2040
|
-
catch {
|
|
2041
|
-
// Importer-edge persistence is advisory, like reachability.
|
|
2150
|
+
const importerRows = [];
|
|
2151
|
+
for (const [fp, importers] of importersOf) {
|
|
2152
|
+
for (const importer of importers) {
|
|
2153
|
+
importerRows.push([fp, importer, 1]);
|
|
2154
|
+
}
|
|
2042
2155
|
}
|
|
2156
|
+
persistGraphEdges(context.indexHandle.rawDb, 'importers', importerRows);
|
|
2043
2157
|
}
|
|
2044
2158
|
// Flag files that export symbols yet are imported by nothing and are
|
|
2045
2159
|
// not framework entry points — a dead module. This is the signal the
|
|
@@ -2097,7 +2211,6 @@ export function createDependencyGraphReducer() {
|
|
|
2097
2211
|
const HEALTH_SEVERITY = {
|
|
2098
2212
|
'break-cycles': 'severe',
|
|
2099
2213
|
'reduce-coupling': 'high',
|
|
2100
|
-
'split-responsibilities': 'high',
|
|
2101
2214
|
'review-orphans': 'severe',
|
|
2102
2215
|
};
|
|
2103
2216
|
for (const s of health.suggestions) {
|
|
@@ -2135,6 +2248,8 @@ export function createDependencyGraphReducer() {
|
|
|
2135
2248
|
* Invariants reducer — enforces user-defined invariant rules from .codeauditor.json.
|
|
2136
2249
|
* Receives the full file list via context.files and runs the rule engine across
|
|
2137
2250
|
* all files at once (call-constraint rules inherently need cross-file scope).
|
|
2251
|
+
*
|
|
2252
|
+
* @returns The invariants stage-3 reducer.
|
|
2138
2253
|
*/
|
|
2139
2254
|
export function createInvariantsReducer() {
|
|
2140
2255
|
return {
|
|
@@ -2218,6 +2333,8 @@ async function _getProvenanceModule() {
|
|
|
2218
2333
|
* Schema SQL visitor (.sql files) — extracts ordered DDL operations for the
|
|
2219
2334
|
* Stage 3 reducer to replay into the known-tables catalog. Emits the parsed
|
|
2220
2335
|
* ops, not raw source, so the reducer retains only state transitions.
|
|
2336
|
+
*
|
|
2337
|
+
* @returns The schema-sql stage-2 visitor.
|
|
2221
2338
|
*/
|
|
2222
2339
|
export function createSchemaSqlVisitor() {
|
|
2223
2340
|
const getMigrationExtractor = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js'));
|
|
@@ -2228,7 +2345,8 @@ export function createSchemaSqlVisitor() {
|
|
|
2228
2345
|
getRuleIds: () => [],
|
|
2229
2346
|
async visit(_ast, _adapter, context, sourceCode) {
|
|
2230
2347
|
const { extractMigrationOpsFromFile } = await getMigrationExtractor();
|
|
2231
|
-
const
|
|
2348
|
+
const dialect = context.config.sqlDialect;
|
|
2349
|
+
const { ops, columns, tableColumns, skipped, bytes } = await extractMigrationOpsFromFile(context.filePath, sourceCode, dialect ?? null);
|
|
2232
2350
|
return {
|
|
2233
2351
|
violations: [],
|
|
2234
2352
|
facts: {
|
|
@@ -2246,181 +2364,177 @@ export function createSchemaSqlVisitor() {
|
|
|
2246
2364
|
category: 'database',
|
|
2247
2365
|
};
|
|
2248
2366
|
}
|
|
2367
|
+
/** The shared lazy `UniversalSchemaAnalyzer` for the schema-code visitor. Hoisted
|
|
2368
|
+
* to module scope so the factory below stays a thin descriptor and the analyzer
|
|
2369
|
+
* is constructed once, not per factory call. */
|
|
2370
|
+
const getSchemaCodeAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js').then((m) => ({
|
|
2371
|
+
analyzer: new m.UniversalSchemaAnalyzer(),
|
|
2372
|
+
defaults: m.DEFAULT_SCHEMA_CONFIG,
|
|
2373
|
+
parseMigrationOps: m.parseMigrationOps,
|
|
2374
|
+
extractDdlColumnNames: m.extractDdlColumnNames,
|
|
2375
|
+
extractDdlTableColumns: m.extractDdlTableColumns,
|
|
2376
|
+
})));
|
|
2377
|
+
/** The schema-code visitor's per-file `visit` body, extracted from
|
|
2378
|
+
* `createSchemaCodeVisitor` (which exceeded the 200-line `function-length`
|
|
2379
|
+
* budget) so the factory stays a thin descriptor over the shared analyzer. */
|
|
2380
|
+
async function runSchemaCodeVisit(ast, adapter, context, sourceCode) {
|
|
2381
|
+
const { defaults, parseMigrationOps, extractDdlColumnNames, extractDdlTableColumns } = await getSchemaCodeAnalyzer();
|
|
2382
|
+
const pm = await _getProvenanceModule();
|
|
2383
|
+
const violations = [];
|
|
2384
|
+
// Pipeline config for this analyzer (moved before table extraction, needed
|
|
2385
|
+
// by the table-source registry and provenance context).
|
|
2386
|
+
const schemaConfig = (context.config ?? {});
|
|
2387
|
+
// Spec 70 R1 — the corpus's named dialect (or null) threads to the DDL
|
|
2388
|
+
// extractors so DO-local DDL parses instead of regex. A null dialect is
|
|
2389
|
+
// cannot-fire (no DDL facts).
|
|
2390
|
+
const dialect = schemaConfig.sqlDialect;
|
|
2391
|
+
// Spec 29 R2: Extract ORM table names via declarative table-source registry.
|
|
2392
|
+
// Adding an ORM is now a config entry, not code. Falls back to Drizzle
|
|
2393
|
+
// entries when no user-specified tableSources are configured.
|
|
2394
|
+
const ormTables = [];
|
|
2395
|
+
const tableProvenances = [];
|
|
2396
|
+
const tableSources = schemaConfig.tableSources ?? [
|
|
2397
|
+
{ kind: 'callee', name: 'pgTable', arg: 0, description: 'Drizzle PostgreSQL table' },
|
|
2398
|
+
{ kind: 'callee', name: 'mysqlTable', arg: 0, description: 'Drizzle MySQL table' },
|
|
2399
|
+
{ kind: 'callee', name: 'sqliteTable', arg: 0, description: 'Drizzle SQLite table' },
|
|
2400
|
+
];
|
|
2401
|
+
if (tableSources.length > 0) {
|
|
2402
|
+
const registered = extractTablesFromRegistry(tableSources, {
|
|
2403
|
+
ast: ast,
|
|
2404
|
+
adapter: adapter,
|
|
2405
|
+
sourceCode,
|
|
2406
|
+
filePath: context.filePath,
|
|
2407
|
+
});
|
|
2408
|
+
for (const { table, source } of registered) {
|
|
2409
|
+
ormTables.push(table);
|
|
2410
|
+
tableProvenances.push({ table, source });
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
// Extract DDL from sql.exec(...) string literals inside Durable Object classes.
|
|
2414
|
+
// These are CREATE TABLE / DROP TABLE statements at runtime that migration
|
|
2415
|
+
// discovery never sees. Extracting them completes the authoritative catalog
|
|
2416
|
+
// so unknown-table doesn't false-positive on DO-local tables.
|
|
2417
|
+
const doDDL = [];
|
|
2418
|
+
const doTemplateDDL = /`([^`]*(?:CREATE|DROP|ALTER)\s+(?:TABLE|VIRTUAL\s+TABLE)\s+[^`]+)`/gis;
|
|
2419
|
+
const doStringDDL = /(["'])((?:\s*(?:CREATE|DROP|ALTER)\s+(?:TABLE|VIRTUAL\s+TABLE)\s+[^"']+))\1/gis;
|
|
2420
|
+
let ddlMatch;
|
|
2421
|
+
while ((ddlMatch = doTemplateDDL.exec(sourceCode)) !== null) {
|
|
2422
|
+
const sql = ddlMatch[1].trim();
|
|
2423
|
+
if (sql)
|
|
2424
|
+
doDDL.push(sql);
|
|
2425
|
+
}
|
|
2426
|
+
while ((ddlMatch = doStringDDL.exec(sourceCode)) !== null) {
|
|
2427
|
+
const sql = ddlMatch[2].trim();
|
|
2428
|
+
if (sql)
|
|
2429
|
+
doDDL.push(sql);
|
|
2430
|
+
}
|
|
2431
|
+
const doDDLSql = doDDL.length > 0 ? doDDL.join(';\n') : null;
|
|
2432
|
+
const doDDLColumns = doDDLSql ? extractDdlColumnNames(doDDLSql, dialect ?? null) : [];
|
|
2433
|
+
const doDDLTableColumns = doDDLSql ? extractDdlTableColumns(doDDLSql, dialect ?? null) : {};
|
|
2434
|
+
// Build provenance context for this file — defaults from DEFAULT_SCHEMA_CONFIG
|
|
2435
|
+
const detectionMode = schemaConfig.detection?.mode ?? 'hybrid';
|
|
2436
|
+
const provenanceContext = pm.buildProvenanceContext(ast, adapter, sourceCode, {
|
|
2437
|
+
mode: detectionMode,
|
|
2438
|
+
dbBindingNames: schemaConfig.dbBindingNames ?? defaults.dbBindingNames,
|
|
2439
|
+
dbWrapperNames: schemaConfig.dbWrapperNames ?? defaults.dbWrapperNames,
|
|
2440
|
+
// Spec 70 criterion 8 — thread the corpus's named dialect so R3 can prove
|
|
2441
|
+
// a receiver by its parsed SQL argument (the criterion-9 type-annotation
|
|
2442
|
+
// tests are gone). Mirrors buildSchemaProvenanceContext in the analyzer.
|
|
2443
|
+
sqlDialect: dialect ?? null,
|
|
2444
|
+
});
|
|
2445
|
+
// Spec 70 criterion 2 — the declaration-resolution environment `identifyHandle`
|
|
2446
|
+
// reads for handle admission (mirrors buildSchemaHandleEnv). Absent for Go,
|
|
2447
|
+
// which has no local binding env and resolves cross-file.
|
|
2448
|
+
const handleEnv = adapter.name === 'go'
|
|
2449
|
+
? undefined
|
|
2450
|
+
: {
|
|
2451
|
+
provenance: provenanceContext.dbProvenanced,
|
|
2452
|
+
bindings: buildBindingEnv(ast, adapter, sourceCode),
|
|
2453
|
+
adapter: adapter,
|
|
2454
|
+
sourceCode,
|
|
2455
|
+
};
|
|
2456
|
+
// File gate — skip files without DB usage
|
|
2457
|
+
if (!passesFileGate(context.filePath, sourceCode, schemaConfig, provenanceContext)) {
|
|
2458
|
+
const facts = { [context.filePath]: { tableRefs: [], ormTables, tableProvenance: tableProvenances } };
|
|
2459
|
+
if (doDDL.length > 0) {
|
|
2460
|
+
facts[context.filePath].ddlOps = parseMigrationOps(doDDLSql, dialect ?? null);
|
|
2461
|
+
if (doDDLColumns.length > 0)
|
|
2462
|
+
facts[context.filePath].ddlColumns = doDDLColumns;
|
|
2463
|
+
if (Object.keys(doDDLTableColumns).length > 0)
|
|
2464
|
+
facts[context.filePath].ddlTableColumns = doDDLTableColumns;
|
|
2465
|
+
}
|
|
2466
|
+
return { violations: [], facts };
|
|
2467
|
+
}
|
|
2468
|
+
// Build known-tables set from schemas config (pre-pipeline + DB-loaded schemas)
|
|
2469
|
+
const schemas = schemaConfig.schemas ?? [];
|
|
2470
|
+
const knownTablesArr = schemaConfig.knownTables ?? [];
|
|
2471
|
+
const allTables = new Set();
|
|
2472
|
+
for (const t of knownTablesArr)
|
|
2473
|
+
allTables.add(t);
|
|
2474
|
+
for (const schema of schemas) {
|
|
2475
|
+
for (const table of (schema.tables ?? [])) {
|
|
2476
|
+
allTables.add(table.name);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
// Find table references (per-file, uses allTables for short-id false-positive
|
|
2480
|
+
// filtering). The `unresolved`/`unparseable` halves are no longer consumed
|
|
2481
|
+
// here: both the `unresolved-query` (Spec 58 R1 / 70 1b) and `unparseable`
|
|
2482
|
+
// (Spec 70 R2) coverage diagnostics are re-derived corpus-side
|
|
2483
|
+
// (classifyUnresolvedQuerySites / classifyUnparseableSql →
|
|
2484
|
+
// buildReceiverDiagnostics), so this visitor stops emitting them.
|
|
2485
|
+
const { references: tableRefs } = findTableReferences(ast, adapter, sourceCode, { config: schemaConfig, provenanceContext, allTables, handleEnv });
|
|
2486
|
+
// Check naming conventions
|
|
2487
|
+
if (schemaConfig.checkNamingConventions !== false) {
|
|
2488
|
+
violations.push(...checkNamingConventions(tableRefs, context.filePath));
|
|
2489
|
+
}
|
|
2490
|
+
// Check query patterns
|
|
2491
|
+
if (schemaConfig.validateQueryPatterns !== false) {
|
|
2492
|
+
violations.push(...checkQueryPatterns(ast, adapter, sourceCode, schemaConfig));
|
|
2493
|
+
}
|
|
2494
|
+
// Check SQL injection
|
|
2495
|
+
violations.push(...checkSQLInjection(ast, adapter, sourceCode));
|
|
2496
|
+
// Emit facts for the Stage 3 reducer. Fluent-builder references
|
|
2497
|
+
// (`origin: 'query-builder'`) are excluded from the unknown-table check:
|
|
2498
|
+
// a scratch/test table name carried by `db('t').select(...)` is not a
|
|
2499
|
+
// schema catalog miss, and feeding it in flips the 10:1 fail-open ratio.
|
|
2500
|
+
const fileFacts = {
|
|
2501
|
+
tableRefs: tableRefs
|
|
2502
|
+
.filter((r) => r.origin !== 'query-builder')
|
|
2503
|
+
.map((r) => ({
|
|
2504
|
+
table: r.table,
|
|
2505
|
+
type: r.type,
|
|
2506
|
+
line: r.location.line,
|
|
2507
|
+
column: r.location.column,
|
|
2508
|
+
context: r.context,
|
|
2509
|
+
})),
|
|
2510
|
+
ormTables,
|
|
2511
|
+
tableProvenance: tableProvenances,
|
|
2512
|
+
};
|
|
2513
|
+
if (doDDL.length > 0) {
|
|
2514
|
+
fileFacts.ddlOps = parseMigrationOps(doDDLSql, dialect ?? null);
|
|
2515
|
+
if (doDDLColumns.length > 0)
|
|
2516
|
+
fileFacts.ddlColumns = doDDLColumns;
|
|
2517
|
+
if (Object.keys(doDDLTableColumns).length > 0)
|
|
2518
|
+
fileFacts.ddlTableColumns = doDDLTableColumns;
|
|
2519
|
+
}
|
|
2520
|
+
return {
|
|
2521
|
+
violations,
|
|
2522
|
+
facts: { [context.filePath]: fileFacts },
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2249
2525
|
/**
|
|
2250
2526
|
* Schema code visitor (.ts/.tsx/.js/.jsx) — per-file analysis for naming conventions,
|
|
2251
2527
|
* query patterns, SQL injection, table references, and ORM table extraction.
|
|
2528
|
+
*
|
|
2529
|
+
* @returns The schema-code stage-2 visitor.
|
|
2252
2530
|
*/
|
|
2253
2531
|
export function createSchemaCodeVisitor() {
|
|
2254
|
-
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js').then((m) => ({
|
|
2255
|
-
analyzer: new m.UniversalSchemaAnalyzer(),
|
|
2256
|
-
defaults: m.DEFAULT_SCHEMA_CONFIG,
|
|
2257
|
-
parseMigrationOps: m.parseMigrationOps,
|
|
2258
|
-
extractDdlColumnNames: m.extractDdlColumnNames,
|
|
2259
|
-
extractDdlTableColumns: m.extractDdlTableColumns,
|
|
2260
|
-
})));
|
|
2261
2532
|
return {
|
|
2262
2533
|
name: 'schema-code',
|
|
2263
2534
|
stage: 'visitor',
|
|
2264
2535
|
extensions: ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'],
|
|
2265
2536
|
getRuleIds: () => getRuleIdsFor('schema'),
|
|
2266
|
-
|
|
2267
|
-
const { analyzer: a, defaults, parseMigrationOps, extractDdlColumnNames, extractDdlTableColumns } = await getAnalyzer();
|
|
2268
|
-
const pm = await _getProvenanceModule();
|
|
2269
|
-
const violations = [];
|
|
2270
|
-
const diagnostics = [];
|
|
2271
|
-
const indexFacts = [];
|
|
2272
|
-
// Pipeline config for this analyzer (moved before table extraction, needed
|
|
2273
|
-
// by the table-source registry and provenance context).
|
|
2274
|
-
const schemaConfig = (context.config ?? {});
|
|
2275
|
-
// Spec 29 R2: Extract ORM table names via declarative table-source registry.
|
|
2276
|
-
// Adding an ORM is now a config entry, not code. Falls back to Drizzle
|
|
2277
|
-
// entries when no user-specified tableSources are configured.
|
|
2278
|
-
const ormTables = [];
|
|
2279
|
-
const tableProvenances = [];
|
|
2280
|
-
const tableSources = schemaConfig.tableSources ?? [
|
|
2281
|
-
{ kind: 'callee', name: 'pgTable', arg: 0, description: 'Drizzle PostgreSQL table' },
|
|
2282
|
-
{ kind: 'callee', name: 'mysqlTable', arg: 0, description: 'Drizzle MySQL table' },
|
|
2283
|
-
{ kind: 'callee', name: 'sqliteTable', arg: 0, description: 'Drizzle SQLite table' },
|
|
2284
|
-
];
|
|
2285
|
-
if (tableSources.length > 0) {
|
|
2286
|
-
const registered = extractTablesFromRegistry(tableSources, {
|
|
2287
|
-
ast: ast,
|
|
2288
|
-
adapter: adapter,
|
|
2289
|
-
sourceCode,
|
|
2290
|
-
filePath: context.filePath,
|
|
2291
|
-
});
|
|
2292
|
-
for (const { table, source } of registered) {
|
|
2293
|
-
ormTables.push(table);
|
|
2294
|
-
tableProvenances.push({ table, source });
|
|
2295
|
-
}
|
|
2296
|
-
}
|
|
2297
|
-
// Extract DDL from sql.exec(...) string literals inside Durable Object classes.
|
|
2298
|
-
// These are CREATE TABLE / DROP TABLE statements at runtime that migration
|
|
2299
|
-
// discovery never sees. Extracting them completes the authoritative catalog
|
|
2300
|
-
// so unknown-table doesn't false-positive on DO-local tables.
|
|
2301
|
-
const doDDL = [];
|
|
2302
|
-
const doTemplateDDL = /`([^`]*(?:CREATE|DROP|ALTER)\s+(?:TABLE|VIRTUAL\s+TABLE)\s+[^`]+)`/gis;
|
|
2303
|
-
const doStringDDL = /(["'])((?:\s*(?:CREATE|DROP|ALTER)\s+(?:TABLE|VIRTUAL\s+TABLE)\s+[^"']+))\1/gis;
|
|
2304
|
-
let ddlMatch;
|
|
2305
|
-
while ((ddlMatch = doTemplateDDL.exec(sourceCode)) !== null) {
|
|
2306
|
-
const sql = ddlMatch[1].trim();
|
|
2307
|
-
if (sql)
|
|
2308
|
-
doDDL.push(sql);
|
|
2309
|
-
}
|
|
2310
|
-
while ((ddlMatch = doStringDDL.exec(sourceCode)) !== null) {
|
|
2311
|
-
const sql = ddlMatch[2].trim();
|
|
2312
|
-
if (sql)
|
|
2313
|
-
doDDL.push(sql);
|
|
2314
|
-
}
|
|
2315
|
-
const doDDLSql = doDDL.length > 0 ? doDDL.join(';\n') : null;
|
|
2316
|
-
const doDDLColumns = doDDLSql ? extractDdlColumnNames(doDDLSql) : [];
|
|
2317
|
-
const doDDLTableColumns = doDDLSql ? extractDdlTableColumns(doDDLSql) : {};
|
|
2318
|
-
// Build provenance context for this file — defaults from DEFAULT_SCHEMA_CONFIG
|
|
2319
|
-
const detectionMode = schemaConfig.detection?.mode ?? 'hybrid';
|
|
2320
|
-
const provenanceContext = pm.buildProvenanceContext(ast, adapter, sourceCode, {
|
|
2321
|
-
mode: detectionMode,
|
|
2322
|
-
dbReceiverNames: schemaConfig.dbReceiverNames ?? defaults.dbReceiverNames,
|
|
2323
|
-
dbBindingNames: schemaConfig.dbBindingNames ?? defaults.dbBindingNames,
|
|
2324
|
-
dbCallMethods: schemaConfig.dbCallMethods ?? defaults.dbCallMethods,
|
|
2325
|
-
dbWrapperNames: schemaConfig.dbWrapperNames ?? defaults.dbWrapperNames,
|
|
2326
|
-
});
|
|
2327
|
-
// File gate — skip files without DB usage
|
|
2328
|
-
if (!passesFileGate(context.filePath, sourceCode, schemaConfig, provenanceContext)) {
|
|
2329
|
-
const facts = { [context.filePath]: { tableRefs: [], ormTables, tableProvenance: tableProvenances } };
|
|
2330
|
-
if (doDDL.length > 0) {
|
|
2331
|
-
facts[context.filePath].ddlOps = parseMigrationOps(doDDLSql);
|
|
2332
|
-
if (doDDLColumns.length > 0)
|
|
2333
|
-
facts[context.filePath].ddlColumns = doDDLColumns;
|
|
2334
|
-
if (Object.keys(doDDLTableColumns).length > 0)
|
|
2335
|
-
facts[context.filePath].ddlTableColumns = doDDLTableColumns;
|
|
2336
|
-
}
|
|
2337
|
-
return { violations: [], facts };
|
|
2338
|
-
}
|
|
2339
|
-
// Build known-tables set from schemas config (pre-pipeline + DB-loaded schemas)
|
|
2340
|
-
const schemas = schemaConfig.schemas ?? [];
|
|
2341
|
-
const knownTablesArr = schemaConfig.knownTables ?? [];
|
|
2342
|
-
const allTables = new Set();
|
|
2343
|
-
for (const t of knownTablesArr)
|
|
2344
|
-
allTables.add(t);
|
|
2345
|
-
for (const schema of schemas) {
|
|
2346
|
-
for (const table of (schema.tables ?? [])) {
|
|
2347
|
-
allTables.add(table.name);
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2350
|
-
// Find table references (per-file, uses allTables for short-id false-positive filtering)
|
|
2351
|
-
const { references: tableRefs, unresolved } = findTableReferences(ast, adapter, sourceCode, { config: schemaConfig, provenanceContext, allTables });
|
|
2352
|
-
// Record schema usage → emit as indexFacts via the shared instance
|
|
2353
|
-
a.recordTableUsage(ast, adapter, context.filePath, tableRefs, sourceCode);
|
|
2354
|
-
const pending = a.getPendingSchemaRecords();
|
|
2355
|
-
// Emit clear-by-file + per-usage index facts
|
|
2356
|
-
if (pending.clearFiles.length > 0) {
|
|
2357
|
-
for (const filePath of pending.clearFiles) {
|
|
2358
|
-
indexFacts.push({ table: 'schema_usage', data: { _action: 'clear-by-file', file_path: filePath } });
|
|
2359
|
-
}
|
|
2360
|
-
}
|
|
2361
|
-
for (const usage of pending.usages) {
|
|
2362
|
-
indexFacts.push({
|
|
2363
|
-
table: 'schema_usage',
|
|
2364
|
-
data: {
|
|
2365
|
-
file_path: usage.filePath,
|
|
2366
|
-
table_name: usage.tableName,
|
|
2367
|
-
function_name: usage.functionName,
|
|
2368
|
-
function_start_line: usage.functionStartLine,
|
|
2369
|
-
function_start_column: usage.functionStartColumn,
|
|
2370
|
-
usage_type: usage.usageType,
|
|
2371
|
-
line: usage.line,
|
|
2372
|
-
column: usage.column,
|
|
2373
|
-
raw_query: usage.rawQuery,
|
|
2374
|
-
origin: usage.origin ?? null,
|
|
2375
|
-
},
|
|
2376
|
-
});
|
|
2377
|
-
}
|
|
2378
|
-
// Check naming conventions
|
|
2379
|
-
if (schemaConfig.checkNamingConventions !== false) {
|
|
2380
|
-
violations.push(...checkNamingConventions(tableRefs, context.filePath));
|
|
2381
|
-
}
|
|
2382
|
-
// Spec 58 R1 — DB-call SQL held in an unresolvable identifier is a coverage
|
|
2383
|
-
// diagnostic (the analyzer can't see the SQL), not a finding (the code isn't wrong).
|
|
2384
|
-
if (schemaConfig.reportUnresolvedQueries !== false) {
|
|
2385
|
-
diagnostics.push(...checkUnresolvedQueries(unresolved, context.filePath));
|
|
2386
|
-
}
|
|
2387
|
-
// Check query patterns
|
|
2388
|
-
if (schemaConfig.validateQueryPatterns !== false) {
|
|
2389
|
-
violations.push(...checkQueryPatterns(ast, adapter, sourceCode, schemaConfig));
|
|
2390
|
-
}
|
|
2391
|
-
// Check SQL injection
|
|
2392
|
-
violations.push(...checkSQLInjection(ast, adapter, sourceCode));
|
|
2393
|
-
// Emit facts for the Stage 3 reducer. Fluent-builder references
|
|
2394
|
-
// (`origin: 'query-builder'`) are excluded from the unknown-table check:
|
|
2395
|
-
// a scratch/test table name carried by `db('t').select(...)` is not a
|
|
2396
|
-
// schema catalog miss, and feeding it in flips the 10:1 fail-open ratio.
|
|
2397
|
-
const fileFacts = {
|
|
2398
|
-
tableRefs: tableRefs
|
|
2399
|
-
.filter((r) => r.origin !== 'query-builder')
|
|
2400
|
-
.map((r) => ({
|
|
2401
|
-
table: r.table,
|
|
2402
|
-
type: r.type,
|
|
2403
|
-
line: r.location.line,
|
|
2404
|
-
column: r.location.column,
|
|
2405
|
-
context: r.context,
|
|
2406
|
-
})),
|
|
2407
|
-
ormTables,
|
|
2408
|
-
tableProvenance: tableProvenances,
|
|
2409
|
-
};
|
|
2410
|
-
if (doDDL.length > 0) {
|
|
2411
|
-
fileFacts.ddlOps = parseMigrationOps(doDDLSql);
|
|
2412
|
-
if (doDDLColumns.length > 0)
|
|
2413
|
-
fileFacts.ddlColumns = doDDLColumns;
|
|
2414
|
-
if (Object.keys(doDDLTableColumns).length > 0)
|
|
2415
|
-
fileFacts.ddlTableColumns = doDDLTableColumns;
|
|
2416
|
-
}
|
|
2417
|
-
return {
|
|
2418
|
-
violations,
|
|
2419
|
-
facts: { [context.filePath]: fileFacts },
|
|
2420
|
-
indexFacts: indexFacts.length > 0 ? indexFacts : undefined,
|
|
2421
|
-
...(diagnostics.length > 0 && { diagnostics }),
|
|
2422
|
-
};
|
|
2423
|
-
},
|
|
2537
|
+
visit: runSchemaCodeVisit,
|
|
2424
2538
|
defaultConfig: {},
|
|
2425
2539
|
description: 'Per-file schema analysis: naming conventions, query patterns, SQL injection, table references',
|
|
2426
2540
|
category: 'database',
|
|
@@ -2428,6 +2542,8 @@ export function createSchemaCodeVisitor() {
|
|
|
2428
2542
|
}
|
|
2429
2543
|
/**
|
|
2430
2544
|
* Schema Prisma visitor (.prisma files) — extracts model names for known-table catalog.
|
|
2545
|
+
*
|
|
2546
|
+
* @returns The schema-prisma stage-2 visitor.
|
|
2431
2547
|
*/
|
|
2432
2548
|
export function createSchemaPrismaVisitor() {
|
|
2433
2549
|
return {
|
|
@@ -2470,6 +2586,8 @@ export function createSchemaPrismaVisitor() {
|
|
|
2470
2586
|
}
|
|
2471
2587
|
/**
|
|
2472
2588
|
* Schema JSON visitor (.json files) — parses JSON for the Stage 3 reducer to validate.
|
|
2589
|
+
*
|
|
2590
|
+
* @returns The schema-json stage-2 visitor.
|
|
2473
2591
|
*/
|
|
2474
2592
|
export function createSchemaJsonVisitor() {
|
|
2475
2593
|
return {
|
|
@@ -2491,11 +2609,329 @@ export function createSchemaJsonVisitor() {
|
|
|
2491
2609
|
category: 'database',
|
|
2492
2610
|
};
|
|
2493
2611
|
}
|
|
2612
|
+
/**
|
|
2613
|
+
* Flatten `allFacts` (keyed by visitor name, each value `{ [filePath]: facts }`)
|
|
2614
|
+
* into per-file `[filePath, fact]` pairs.
|
|
2615
|
+
*/
|
|
2616
|
+
function* iteratePerFileFacts(allFacts) {
|
|
2617
|
+
for (const [, visitorFacts] of Object.entries(allFacts)) {
|
|
2618
|
+
if (typeof visitorFacts === 'object' && visitorFacts !== null) {
|
|
2619
|
+
for (const [filePath, fact] of Object.entries(visitorFacts)) {
|
|
2620
|
+
if (typeof fact === 'object' && fact !== null) {
|
|
2621
|
+
yield [filePath, fact];
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Build the known-tables catalog (and derived column sets) from the schema-sql,
|
|
2629
|
+
* schema-code, and schema-prisma facts. Also replays SQL migrations to compute
|
|
2630
|
+
* drop provenance for stale-table-reference detection.
|
|
2631
|
+
*/
|
|
2632
|
+
function buildSchemaCatalog(allFacts) {
|
|
2633
|
+
const knownTables = new Set();
|
|
2634
|
+
const tableProvenances = new Map(); // table name → origins
|
|
2635
|
+
// 1a. SQL DDL replay — collect all DDL facts, sort by numeric prefix, replay
|
|
2636
|
+
const sqlFiles = [];
|
|
2637
|
+
for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
|
|
2638
|
+
if ('ddlOps' in fact) {
|
|
2639
|
+
sqlFiles.push({ filePath, ops: fact.ddlOps });
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
// Sort by numeric prefix in basename: "009_something" < "0010_rename"
|
|
2643
|
+
// Falls back to localeCompare for non-numeric-prefixed names.
|
|
2644
|
+
const numericPrefix = (p) => {
|
|
2645
|
+
const base = p.split('/').pop() ?? p;
|
|
2646
|
+
const m = base.match(/^(\d+)/);
|
|
2647
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
2648
|
+
};
|
|
2649
|
+
sqlFiles.sort((a, b) => {
|
|
2650
|
+
const na = numericPrefix(a.filePath);
|
|
2651
|
+
const nb = numericPrefix(b.filePath);
|
|
2652
|
+
if (na !== nb)
|
|
2653
|
+
return na - nb;
|
|
2654
|
+
return a.filePath.localeCompare(b.filePath);
|
|
2655
|
+
});
|
|
2656
|
+
// Drop provenance — the last migration that removed a table, and the tables
|
|
2657
|
+
// that same migration introduced. Lets unknown-table detection distinguish
|
|
2658
|
+
// "never existed" from "existed and was dropped": the latter is a stale code
|
|
2659
|
+
// reference, not a typo. `dropProvenance` ends up holding exactly the tables
|
|
2660
|
+
// dropped and not subsequently recreated (CREATE clears the entry).
|
|
2661
|
+
const dropProvenance = new Map();
|
|
2662
|
+
for (const sqlFile of sqlFiles) {
|
|
2663
|
+
const before = new Set(knownTables);
|
|
2664
|
+
applyMigrationOps(sqlFile.ops, knownTables);
|
|
2665
|
+
// Record provenance for newly created tables
|
|
2666
|
+
for (const table of knownTables) {
|
|
2667
|
+
if (!before.has(table)) {
|
|
2668
|
+
const sources = tableProvenances.get(table) ?? [];
|
|
2669
|
+
sources.push({ table, tier: 'sql-migration', sourceFile: sqlFile.filePath, description: 'SQL migration' });
|
|
2670
|
+
tableProvenances.set(table, sources);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
// Genuinely-new tables introduced by this migration (excludes rename/rebuild
|
|
2674
|
+
// churn like `ALTER … RENAME TO x_old` + re-CREATE of the same name).
|
|
2675
|
+
const createdHere = [];
|
|
2676
|
+
const createdTables = new Set();
|
|
2677
|
+
const droppedTables = new Set();
|
|
2678
|
+
for (const op of sqlFile.ops) {
|
|
2679
|
+
const t = stripIdentifier(op.table);
|
|
2680
|
+
if (op.op === 'CREATE') {
|
|
2681
|
+
createdTables.add(t);
|
|
2682
|
+
if (!before.has(t))
|
|
2683
|
+
createdHere.push(t);
|
|
2684
|
+
}
|
|
2685
|
+
else if (op.op === 'DROP') {
|
|
2686
|
+
droppedTables.add(t);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
for (const op of sqlFile.ops) {
|
|
2690
|
+
const t = stripIdentifier(op.table);
|
|
2691
|
+
if (op.op === 'DROP') {
|
|
2692
|
+
dropProvenance.set(t, { migrationFile: sqlFile.filePath, createdInSameMigration: createdHere });
|
|
2693
|
+
}
|
|
2694
|
+
else if (op.op === 'CREATE') {
|
|
2695
|
+
dropProvenance.delete(t);
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
// A table created and dropped within this same file is a self-contained
|
|
2699
|
+
// fixture — a scratch table a test or script creates, uses, then tears
|
|
2700
|
+
// down. Its DROP is teardown, not a migration, so it must neither surface
|
|
2701
|
+
// as a stale-table-reference nor leave the name unknown: keep it known and
|
|
2702
|
+
// clear the drop provenance this file recorded for it. (A genuine dropped
|
|
2703
|
+
// table stays stale because its DROP lives in a migration that never also
|
|
2704
|
+
// CREATEs it — e.g. recall's `generation_queue`, dropped in 0198.)
|
|
2705
|
+
for (const t of createdTables) {
|
|
2706
|
+
if (droppedTables.has(t)) {
|
|
2707
|
+
knownTables.add(t);
|
|
2708
|
+
if (dropProvenance.get(t)?.migrationFile === sqlFile.filePath) {
|
|
2709
|
+
dropProvenance.delete(t);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
// 1b. ORM tables from code files — with provenance from the table-source registry
|
|
2715
|
+
for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
|
|
2716
|
+
const ormTables = fact.ormTables ?? [];
|
|
2717
|
+
for (const t of ormTables)
|
|
2718
|
+
knownTables.add(t);
|
|
2719
|
+
const provenances = fact.tableProvenance ?? [];
|
|
2720
|
+
for (const p of provenances) {
|
|
2721
|
+
const sources = tableProvenances.get(p.table) ?? [];
|
|
2722
|
+
sources.push(p.source);
|
|
2723
|
+
tableProvenances.set(p.table, sources);
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
// 1c. Prisma models
|
|
2727
|
+
for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
|
|
2728
|
+
const prismaModels = fact.prismaModels ?? [];
|
|
2729
|
+
for (const m of prismaModels) {
|
|
2730
|
+
knownTables.add(m);
|
|
2731
|
+
const sources = tableProvenances.get(m) ?? [];
|
|
2732
|
+
sources.push({ table: m, tier: 'prisma-model', sourceFile: filePath, description: 'Prisma model' });
|
|
2733
|
+
tableProvenances.set(m, sources);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
// 1d. Aggregate DDL-declared columns (Spec 39 — derived applicability).
|
|
2737
|
+
// The schema-sql and schema-code visitors emit per-file `ddlColumns`;
|
|
2738
|
+
// the reducer folds them into a single case-insensitive, deduplicated
|
|
2739
|
+
// set so downstream applicability predicates can ask "does ANY table
|
|
2740
|
+
// carry a tenant-scoping column?" without per-table column lists.
|
|
2741
|
+
//
|
|
2742
|
+
// Spec 62 Amendment B — additionally fold the per-table
|
|
2743
|
+
// `ddlTableColumns` into a corpus-wide `tableColumns` (Record<table,
|
|
2744
|
+
// string[]>) so the Stage-4 missing-org-filter reducer can answer the
|
|
2745
|
+
// per-query question "does THIS table carry a tenant column?" that the
|
|
2746
|
+
// flat set cannot.
|
|
2747
|
+
const ddlColumns = new Set();
|
|
2748
|
+
const tableColumnsMap = new Map();
|
|
2749
|
+
for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
|
|
2750
|
+
const cols = fact.ddlColumns ?? [];
|
|
2751
|
+
for (const c of cols)
|
|
2752
|
+
ddlColumns.add(String(c).toLowerCase());
|
|
2753
|
+
const perTable = fact.ddlTableColumns ?? {};
|
|
2754
|
+
for (const [table, tableCols] of Object.entries(perTable)) {
|
|
2755
|
+
let set = tableColumnsMap.get(table);
|
|
2756
|
+
if (!set) {
|
|
2757
|
+
set = new Set();
|
|
2758
|
+
tableColumnsMap.set(table, set);
|
|
2759
|
+
}
|
|
2760
|
+
for (const c of tableCols)
|
|
2761
|
+
set.add(String(c).toLowerCase());
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
const tableColumns = {};
|
|
2765
|
+
for (const [table, set] of tableColumnsMap)
|
|
2766
|
+
tableColumns[table] = [...set];
|
|
2767
|
+
return { knownTables, tableProvenances, dropProvenance, ddlColumns, tableColumns };
|
|
2768
|
+
}
|
|
2769
|
+
/**
|
|
2770
|
+
* Merge external config tables into the catalog and detect table references
|
|
2771
|
+
* that resolve to no known table. Emits `unknown-table` and
|
|
2772
|
+
* `stale-table-reference` violations.
|
|
2773
|
+
*/
|
|
2774
|
+
function detectUnknownTables(opts) {
|
|
2775
|
+
const { knownTables, tableProvenances, dropProvenance, allFacts, schemaConfig } = opts;
|
|
2776
|
+
const violations = [];
|
|
2777
|
+
// ── 2. Unknown-table detection ────────────────────────────────────────
|
|
2778
|
+
//
|
|
2779
|
+
// Fail-open guardrail: we can only accuse when the table catalog is
|
|
2780
|
+
// built from authoritative sources. Empty catalog → cannot accuse.
|
|
2781
|
+
//
|
|
2782
|
+
// Authoritative sources (independent of query sites, not circular):
|
|
2783
|
+
// • SQL migration files via schema-sql (step 1a)
|
|
2784
|
+
// • ORM model definitions via schema-code (step 1b)
|
|
2785
|
+
// • Prisma schemas via schema-prisma (step 1c)
|
|
2786
|
+
//
|
|
2787
|
+
// External config tables (schemas from CodeIndexDB, wrangler.toml
|
|
2788
|
+
// pre-discovered tables) are added as a bonus but are not required.
|
|
2789
|
+
//
|
|
2790
|
+
// NOT authoritative: tables inferred from query-text alone. Those
|
|
2791
|
+
// never enter the catalog (table refs are for checking, not building).
|
|
2792
|
+
// Merge external tables from config (bonus, not required).
|
|
2793
|
+
const externalKnownTables = schemaConfig.knownTables ?? [];
|
|
2794
|
+
for (const t of externalKnownTables) {
|
|
2795
|
+
knownTables.add(t);
|
|
2796
|
+
const sources = tableProvenances.get(t) ?? [];
|
|
2797
|
+
sources.push({ table: t, tier: 'external-config', description: 'External configuration' });
|
|
2798
|
+
tableProvenances.set(t, sources);
|
|
2799
|
+
}
|
|
2800
|
+
// Also read the documented schemas config (structured {name, tables} objects).
|
|
2801
|
+
// The standalone UniversalSchemaAnalyzer.analyze() path reads schemas; the
|
|
2802
|
+
// pipeline reducer must read it too, otherwise configuring only schemas
|
|
2803
|
+
// silently triggers fail-open (knownTables.size === 0).
|
|
2804
|
+
const externalSchemas = schemaConfig.schemas ?? [];
|
|
2805
|
+
for (const schema of externalSchemas) {
|
|
2806
|
+
for (const table of schema.tables) {
|
|
2807
|
+
knownTables.add(table.name);
|
|
2808
|
+
const sources = tableProvenances.get(table.name) ?? [];
|
|
2809
|
+
sources.push({ table: table.name, tier: 'external-config', description: `Schema: ${schema.name}` });
|
|
2810
|
+
tableProvenances.set(table.name, sources);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
if (knownTables.size > 0) {
|
|
2814
|
+
// Collect all table references across all files
|
|
2815
|
+
const allTableRefs = [];
|
|
2816
|
+
for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
|
|
2817
|
+
const refs = fact.tableRefs ?? [];
|
|
2818
|
+
for (const ref of refs) {
|
|
2819
|
+
allTableRefs.push({ file: filePath, ...ref });
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
// 10:1 fail-open ratio guard
|
|
2823
|
+
const unknownRefs = allTableRefs.filter(ref => !knownTables.has(ref.table));
|
|
2824
|
+
if (unknownRefs.length / Math.max(knownTables.size, 1) <= 10) {
|
|
2825
|
+
for (const ref of unknownRefs) {
|
|
2826
|
+
// The table existed and was dropped in a migration — a stale code
|
|
2827
|
+
// reference, not a typo. Name the dropping migration and the tables
|
|
2828
|
+
// that migration introduced (evidence, not proof of a successor).
|
|
2829
|
+
const drop = dropProvenance.get(ref.table);
|
|
2830
|
+
if (drop) {
|
|
2831
|
+
const migrationName = drop.migrationFile.split('/').pop() ?? drop.migrationFile;
|
|
2832
|
+
const created = drop.createdInSameMigration;
|
|
2833
|
+
const msg = created.length > 0
|
|
2834
|
+
? `${ref.table} was dropped in ${migrationName}; that migration creates ${joinEnglish(created)}.`
|
|
2835
|
+
: `${ref.table} was dropped in ${migrationName} and was not recreated.`;
|
|
2836
|
+
violations.push({
|
|
2837
|
+
file: ref.file,
|
|
2838
|
+
line: ref.line,
|
|
2839
|
+
column: ref.column,
|
|
2840
|
+
severity: 'critical',
|
|
2841
|
+
message: msg,
|
|
2842
|
+
rule: 'stale-table-reference',
|
|
2843
|
+
analyzer: 'schema',
|
|
2844
|
+
symbol: ref.table,
|
|
2845
|
+
resolution: {
|
|
2846
|
+
action: 'update-stale-reference',
|
|
2847
|
+
// The tables `created` are evidence the migration replaced the
|
|
2848
|
+
// dropped table, not proof of a drop-in successor — schemas can
|
|
2849
|
+
// be split or changed incompatibly, in which case the file must
|
|
2850
|
+
// be removed rather than updated. The summary names them as
|
|
2851
|
+
// context and leaves update-vs-remove to the reviewer.
|
|
2852
|
+
summary: created.length > 0
|
|
2853
|
+
? `The table '${ref.table}' was dropped in ${migrationName}. That migration introduces ${joinEnglish(created)} — review this reference and update or remove it.`
|
|
2854
|
+
: `The table '${ref.table}' was dropped in ${migrationName} and was not recreated — update or remove this reference.`,
|
|
2855
|
+
symbols: created.length > 0 ? created : [ref.table],
|
|
2856
|
+
files: [ref.file],
|
|
2857
|
+
lines: [ref.line],
|
|
2858
|
+
},
|
|
2859
|
+
});
|
|
2860
|
+
continue;
|
|
2861
|
+
}
|
|
2862
|
+
const suggestions = getNearestTableSuggestions(ref.table, knownTables, 2);
|
|
2863
|
+
const msg = suggestions.length > 0
|
|
2864
|
+
? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`
|
|
2865
|
+
: `Reference to unknown table '${ref.table}' (${ref.type})`;
|
|
2866
|
+
const suggestionNames = suggestions.map((s) => s.replace(/^'|'$/g, ''));
|
|
2867
|
+
violations.push({
|
|
2868
|
+
file: ref.file,
|
|
2869
|
+
line: ref.line,
|
|
2870
|
+
column: ref.column,
|
|
2871
|
+
severity: 'critical',
|
|
2872
|
+
message: msg,
|
|
2873
|
+
rule: 'unknown-table',
|
|
2874
|
+
analyzer: 'schema',
|
|
2875
|
+
symbol: ref.table,
|
|
2876
|
+
resolution: {
|
|
2877
|
+
action: suggestionNames.length > 0 ? 'use-known-table' : 'register-or-fix-table',
|
|
2878
|
+
summary: suggestionNames.length > 0
|
|
2879
|
+
? `Rename the table reference '${ref.table}' to the nearest known table: ${suggestions.join(', ')}.`
|
|
2880
|
+
: `The table '${ref.table}' is not in the known catalog — register it, or fix the reference to a known table.`,
|
|
2881
|
+
symbols: suggestionNames.length > 0 ? suggestionNames : [ref.table],
|
|
2882
|
+
files: [ref.file],
|
|
2883
|
+
lines: [ref.line],
|
|
2884
|
+
},
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
return violations;
|
|
2890
|
+
}
|
|
2891
|
+
/**
|
|
2892
|
+
* Validate JSON schemas found in the corpus. Best-effort: failures are non-fatal.
|
|
2893
|
+
*/
|
|
2894
|
+
function validateJsonSchemas(opts) {
|
|
2895
|
+
const { schemaConfig, allFacts, context, analyzeJsonSchemas } = opts;
|
|
2896
|
+
const violations = [];
|
|
2897
|
+
if (schemaConfig.validateJsonSchemas !== false) {
|
|
2898
|
+
try {
|
|
2899
|
+
// Collect JSON file paths from the lightweight visitor markers, then
|
|
2900
|
+
// parse each on demand so parsed objects never survive past this loop.
|
|
2901
|
+
const jsonFiles = [];
|
|
2902
|
+
for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
|
|
2903
|
+
if ('isJson' in fact) {
|
|
2904
|
+
jsonFiles.push(filePath);
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
const readJson = (filePath) => {
|
|
2908
|
+
const raw = context.readSource?.(filePath);
|
|
2909
|
+
if (raw === undefined)
|
|
2910
|
+
return null;
|
|
2911
|
+
try {
|
|
2912
|
+
const parsed = JSON.parse(raw);
|
|
2913
|
+
return parsed !== null && typeof parsed === 'object' ? parsed : null;
|
|
2914
|
+
}
|
|
2915
|
+
catch {
|
|
2916
|
+
return null;
|
|
2917
|
+
}
|
|
2918
|
+
};
|
|
2919
|
+
const jsonResult = analyzeJsonSchemas(jsonFiles, readJson, schemaConfig);
|
|
2920
|
+
violations.push(...(jsonResult?.violations ?? []));
|
|
2921
|
+
}
|
|
2922
|
+
catch (e) {
|
|
2923
|
+
// JSON schema validation is best-effort (non-fatal)
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
return violations;
|
|
2927
|
+
}
|
|
2494
2928
|
/**
|
|
2495
2929
|
* Schema Stage 3 reducer — cross-file unknown-table detection and JSON schema validation.
|
|
2496
2930
|
*
|
|
2497
2931
|
* Consumes facts from schema-sql, schema-code, schema-prisma, and schema-json visitors.
|
|
2498
2932
|
* Builds the complete known-tables catalog and checks all table references against it.
|
|
2933
|
+
*
|
|
2934
|
+
* @returns The schema stage-3 reducer.
|
|
2499
2935
|
*/
|
|
2500
2936
|
export function createSchemaReducer() {
|
|
2501
2937
|
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js').then((m) => ({
|
|
@@ -2512,296 +2948,20 @@ export function createSchemaReducer() {
|
|
|
2512
2948
|
const { analyzer: a, analyzeJsonSchemas } = await getAnalyzer();
|
|
2513
2949
|
const violations = [];
|
|
2514
2950
|
const schemaConfig = (context.config ?? {});
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
const knownTables = new Set();
|
|
2530
|
-
const tableProvenances = new Map(); // table name → origins
|
|
2531
|
-
// 1a. SQL DDL replay — collect all DDL facts, sort by numeric prefix, replay
|
|
2532
|
-
const sqlFiles = [];
|
|
2533
|
-
for (const [filePath, fact] of perFile()) {
|
|
2534
|
-
if ('ddlOps' in fact) {
|
|
2535
|
-
sqlFiles.push({ filePath, ops: fact.ddlOps });
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
// Sort by numeric prefix in basename: "009_something" < "0010_rename"
|
|
2539
|
-
// Falls back to localeCompare for non-numeric-prefixed names.
|
|
2540
|
-
const numericPrefix = (p) => {
|
|
2541
|
-
const base = p.split('/').pop() ?? p;
|
|
2542
|
-
const m = base.match(/^(\d+)/);
|
|
2543
|
-
return m ? parseInt(m[1], 10) : 0;
|
|
2544
|
-
};
|
|
2545
|
-
sqlFiles.sort((a, b) => {
|
|
2546
|
-
const na = numericPrefix(a.filePath);
|
|
2547
|
-
const nb = numericPrefix(b.filePath);
|
|
2548
|
-
if (na !== nb)
|
|
2549
|
-
return na - nb;
|
|
2550
|
-
return a.filePath.localeCompare(b.filePath);
|
|
2551
|
-
});
|
|
2552
|
-
// Drop provenance — the last migration that removed a table, and the tables
|
|
2553
|
-
// that same migration introduced. Lets unknown-table detection distinguish
|
|
2554
|
-
// "never existed" from "existed and was dropped": the latter is a stale code
|
|
2555
|
-
// reference, not a typo. `dropProvenance` ends up holding exactly the tables
|
|
2556
|
-
// dropped and not subsequently recreated (CREATE clears the entry).
|
|
2557
|
-
const dropProvenance = new Map();
|
|
2558
|
-
for (const sqlFile of sqlFiles) {
|
|
2559
|
-
const before = new Set(knownTables);
|
|
2560
|
-
applyMigrationOps(sqlFile.ops, knownTables);
|
|
2561
|
-
// Record provenance for newly created tables
|
|
2562
|
-
for (const table of knownTables) {
|
|
2563
|
-
if (!before.has(table)) {
|
|
2564
|
-
const sources = tableProvenances.get(table) ?? [];
|
|
2565
|
-
sources.push({ table, tier: 'sql-migration', sourceFile: sqlFile.filePath, description: 'SQL migration' });
|
|
2566
|
-
tableProvenances.set(table, sources);
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
// Genuinely-new tables introduced by this migration (excludes rename/rebuild
|
|
2570
|
-
// churn like `ALTER … RENAME TO x_old` + re-CREATE of the same name).
|
|
2571
|
-
const createdHere = [];
|
|
2572
|
-
const createdTables = new Set();
|
|
2573
|
-
const droppedTables = new Set();
|
|
2574
|
-
for (const op of sqlFile.ops) {
|
|
2575
|
-
const t = stripIdentifier(op.table);
|
|
2576
|
-
if (op.op === 'CREATE') {
|
|
2577
|
-
createdTables.add(t);
|
|
2578
|
-
if (!before.has(t))
|
|
2579
|
-
createdHere.push(t);
|
|
2580
|
-
}
|
|
2581
|
-
else if (op.op === 'DROP') {
|
|
2582
|
-
droppedTables.add(t);
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
for (const op of sqlFile.ops) {
|
|
2586
|
-
const t = stripIdentifier(op.table);
|
|
2587
|
-
if (op.op === 'DROP') {
|
|
2588
|
-
dropProvenance.set(t, { migrationFile: sqlFile.filePath, createdInSameMigration: createdHere });
|
|
2589
|
-
}
|
|
2590
|
-
else if (op.op === 'CREATE') {
|
|
2591
|
-
dropProvenance.delete(t);
|
|
2592
|
-
}
|
|
2593
|
-
}
|
|
2594
|
-
// A table created and dropped within this same file is a self-contained
|
|
2595
|
-
// fixture — a scratch table a test or script creates, uses, then tears
|
|
2596
|
-
// down. Its DROP is teardown, not a migration, so it must neither surface
|
|
2597
|
-
// as a stale-table-reference nor leave the name unknown: keep it known and
|
|
2598
|
-
// clear the drop provenance this file recorded for it. (A genuine dropped
|
|
2599
|
-
// table stays stale because its DROP lives in a migration that never also
|
|
2600
|
-
// CREATEs it — e.g. recall's `generation_queue`, dropped in 0198.)
|
|
2601
|
-
for (const t of createdTables) {
|
|
2602
|
-
if (droppedTables.has(t)) {
|
|
2603
|
-
knownTables.add(t);
|
|
2604
|
-
if (dropProvenance.get(t)?.migrationFile === sqlFile.filePath) {
|
|
2605
|
-
dropProvenance.delete(t);
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
}
|
|
2610
|
-
// 1b. ORM tables from code files — with provenance from the table-source registry
|
|
2611
|
-
for (const [filePath, fact] of perFile()) {
|
|
2612
|
-
const ormTables = fact.ormTables ?? [];
|
|
2613
|
-
for (const t of ormTables)
|
|
2614
|
-
knownTables.add(t);
|
|
2615
|
-
const provenances = fact.tableProvenance ?? [];
|
|
2616
|
-
for (const p of provenances) {
|
|
2617
|
-
const sources = tableProvenances.get(p.table) ?? [];
|
|
2618
|
-
sources.push(p.source);
|
|
2619
|
-
tableProvenances.set(p.table, sources);
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
// 1c. Prisma models
|
|
2623
|
-
for (const [filePath, fact] of perFile()) {
|
|
2624
|
-
const prismaModels = fact.prismaModels ?? [];
|
|
2625
|
-
for (const m of prismaModels) {
|
|
2626
|
-
knownTables.add(m);
|
|
2627
|
-
const sources = tableProvenances.get(m) ?? [];
|
|
2628
|
-
sources.push({ table: m, tier: 'prisma-model', sourceFile: filePath, description: 'Prisma model' });
|
|
2629
|
-
tableProvenances.set(m, sources);
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
// 1d. Aggregate DDL-declared columns (Spec 39 — derived applicability).
|
|
2633
|
-
// The schema-sql and schema-code visitors emit per-file `ddlColumns`;
|
|
2634
|
-
// the reducer folds them into a single case-insensitive, deduplicated
|
|
2635
|
-
// set so downstream applicability predicates can ask "does ANY table
|
|
2636
|
-
// carry a tenant-scoping column?" without per-table column lists.
|
|
2637
|
-
//
|
|
2638
|
-
// Spec 62 Amendment B — additionally fold the per-table
|
|
2639
|
-
// `ddlTableColumns` into a corpus-wide `tableColumns` (Record<table,
|
|
2640
|
-
// string[]>) so the Stage-4 missing-org-filter reducer can answer the
|
|
2641
|
-
// per-query question "does THIS table carry a tenant column?" that the
|
|
2642
|
-
// flat set cannot.
|
|
2643
|
-
const ddlColumns = new Set();
|
|
2644
|
-
const tableColumnsMap = new Map();
|
|
2645
|
-
for (const [filePath, fact] of perFile()) {
|
|
2646
|
-
const cols = fact.ddlColumns ?? [];
|
|
2647
|
-
for (const c of cols)
|
|
2648
|
-
ddlColumns.add(String(c).toLowerCase());
|
|
2649
|
-
const perTable = fact.ddlTableColumns ?? {};
|
|
2650
|
-
for (const [table, tableCols] of Object.entries(perTable)) {
|
|
2651
|
-
let set = tableColumnsMap.get(table);
|
|
2652
|
-
if (!set) {
|
|
2653
|
-
set = new Set();
|
|
2654
|
-
tableColumnsMap.set(table, set);
|
|
2655
|
-
}
|
|
2656
|
-
for (const c of tableCols)
|
|
2657
|
-
set.add(String(c).toLowerCase());
|
|
2658
|
-
}
|
|
2659
|
-
}
|
|
2660
|
-
const tableColumns = {};
|
|
2661
|
-
for (const [table, set] of tableColumnsMap)
|
|
2662
|
-
tableColumns[table] = [...set];
|
|
2663
|
-
// ── 2. Unknown-table detection ────────────────────────────────────────
|
|
2664
|
-
//
|
|
2665
|
-
// Fail-open guardrail: we can only accuse when the table catalog is
|
|
2666
|
-
// built from authoritative sources. Empty catalog → cannot accuse.
|
|
2667
|
-
//
|
|
2668
|
-
// Authoritative sources (independent of query sites, not circular):
|
|
2669
|
-
// • SQL migration files via schema-sql (step 1a)
|
|
2670
|
-
// • ORM model definitions via schema-code (step 1b)
|
|
2671
|
-
// • Prisma schemas via schema-prisma (step 1c)
|
|
2672
|
-
//
|
|
2673
|
-
// External config tables (schemas from CodeIndexDB, wrangler.toml
|
|
2674
|
-
// pre-discovered tables) are added as a bonus but are not required.
|
|
2675
|
-
//
|
|
2676
|
-
// NOT authoritative: tables inferred from query-text alone. Those
|
|
2677
|
-
// never enter the catalog (table refs are for checking, not building).
|
|
2678
|
-
// Merge external tables from config (bonus, not required).
|
|
2679
|
-
const externalKnownTables = schemaConfig.knownTables ?? [];
|
|
2680
|
-
for (const t of externalKnownTables) {
|
|
2681
|
-
knownTables.add(t);
|
|
2682
|
-
const sources = tableProvenances.get(t) ?? [];
|
|
2683
|
-
sources.push({ table: t, tier: 'external-config', description: 'External configuration' });
|
|
2684
|
-
tableProvenances.set(t, sources);
|
|
2685
|
-
}
|
|
2686
|
-
// Also read the documented schemas config (structured {name, tables} objects).
|
|
2687
|
-
// The standalone UniversalSchemaAnalyzer.analyze() path reads schemas; the
|
|
2688
|
-
// pipeline reducer must read it too, otherwise configuring only schemas
|
|
2689
|
-
// silently triggers fail-open (knownTables.size === 0).
|
|
2690
|
-
const externalSchemas = schemaConfig.schemas ?? [];
|
|
2691
|
-
for (const schema of externalSchemas) {
|
|
2692
|
-
for (const table of schema.tables) {
|
|
2693
|
-
knownTables.add(table.name);
|
|
2694
|
-
const sources = tableProvenances.get(table.name) ?? [];
|
|
2695
|
-
sources.push({ table: table.name, tier: 'external-config', description: `Schema: ${schema.name}` });
|
|
2696
|
-
tableProvenances.set(table.name, sources);
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
if (knownTables.size > 0) {
|
|
2700
|
-
// Collect all table references across all files
|
|
2701
|
-
const allTableRefs = [];
|
|
2702
|
-
for (const [filePath, fact] of perFile()) {
|
|
2703
|
-
const refs = fact.tableRefs ?? [];
|
|
2704
|
-
for (const ref of refs) {
|
|
2705
|
-
allTableRefs.push({ file: filePath, ...ref });
|
|
2706
|
-
}
|
|
2707
|
-
}
|
|
2708
|
-
// 10:1 fail-open ratio guard
|
|
2709
|
-
const unknownRefs = allTableRefs.filter(ref => !knownTables.has(ref.table));
|
|
2710
|
-
if (unknownRefs.length / Math.max(knownTables.size, 1) <= 10) {
|
|
2711
|
-
for (const ref of unknownRefs) {
|
|
2712
|
-
// The table existed and was dropped in a migration — a stale code
|
|
2713
|
-
// reference, not a typo. Name the dropping migration and the tables
|
|
2714
|
-
// that migration introduced (evidence, not proof of a successor).
|
|
2715
|
-
const drop = dropProvenance.get(ref.table);
|
|
2716
|
-
if (drop) {
|
|
2717
|
-
const migrationName = drop.migrationFile.split('/').pop() ?? drop.migrationFile;
|
|
2718
|
-
const created = drop.createdInSameMigration;
|
|
2719
|
-
const msg = created.length > 0
|
|
2720
|
-
? `${ref.table} was dropped in ${migrationName}; that migration creates ${joinEnglish(created)}.`
|
|
2721
|
-
: `${ref.table} was dropped in ${migrationName} and was not recreated.`;
|
|
2722
|
-
violations.push({
|
|
2723
|
-
file: ref.file,
|
|
2724
|
-
line: ref.line,
|
|
2725
|
-
column: ref.column,
|
|
2726
|
-
severity: 'critical',
|
|
2727
|
-
message: msg,
|
|
2728
|
-
rule: 'stale-table-reference',
|
|
2729
|
-
analyzer: 'schema',
|
|
2730
|
-
symbol: ref.table,
|
|
2731
|
-
resolution: {
|
|
2732
|
-
action: 'update-stale-reference',
|
|
2733
|
-
// The tables `created` are evidence the migration replaced the
|
|
2734
|
-
// dropped table, not proof of a drop-in successor — schemas can
|
|
2735
|
-
// be split or changed incompatibly, in which case the file must
|
|
2736
|
-
// be removed rather than updated. The summary names them as
|
|
2737
|
-
// context and leaves update-vs-remove to the reviewer.
|
|
2738
|
-
summary: created.length > 0
|
|
2739
|
-
? `The table '${ref.table}' was dropped in ${migrationName}. That migration introduces ${joinEnglish(created)} — review this reference and update or remove it.`
|
|
2740
|
-
: `The table '${ref.table}' was dropped in ${migrationName} and was not recreated — update or remove this reference.`,
|
|
2741
|
-
symbols: created.length > 0 ? created : [ref.table],
|
|
2742
|
-
files: [ref.file],
|
|
2743
|
-
lines: [ref.line],
|
|
2744
|
-
},
|
|
2745
|
-
});
|
|
2746
|
-
continue;
|
|
2747
|
-
}
|
|
2748
|
-
const suggestions = getNearestTableSuggestions(ref.table, knownTables, 2);
|
|
2749
|
-
const msg = suggestions.length > 0
|
|
2750
|
-
? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`
|
|
2751
|
-
: `Reference to unknown table '${ref.table}' (${ref.type})`;
|
|
2752
|
-
const suggestionNames = suggestions.map((s) => s.replace(/^'|'$/g, ''));
|
|
2753
|
-
violations.push({
|
|
2754
|
-
file: ref.file,
|
|
2755
|
-
line: ref.line,
|
|
2756
|
-
column: ref.column,
|
|
2757
|
-
severity: 'critical',
|
|
2758
|
-
message: msg,
|
|
2759
|
-
rule: 'unknown-table',
|
|
2760
|
-
analyzer: 'schema',
|
|
2761
|
-
symbol: ref.table,
|
|
2762
|
-
resolution: {
|
|
2763
|
-
action: suggestionNames.length > 0 ? 'use-known-table' : 'register-or-fix-table',
|
|
2764
|
-
summary: suggestionNames.length > 0
|
|
2765
|
-
? `Rename the table reference '${ref.table}' to the nearest known table: ${suggestions.join(', ')}.`
|
|
2766
|
-
: `The table '${ref.table}' is not in the known catalog — register it, or fix the reference to a known table.`,
|
|
2767
|
-
symbols: suggestionNames.length > 0 ? suggestionNames : [ref.table],
|
|
2768
|
-
files: [ref.file],
|
|
2769
|
-
lines: [ref.line],
|
|
2770
|
-
},
|
|
2771
|
-
});
|
|
2772
|
-
}
|
|
2773
|
-
}
|
|
2774
|
-
}
|
|
2775
|
-
// ── 3. JSON schema validation ──────────────────────────────────────────
|
|
2776
|
-
if (schemaConfig.validateJsonSchemas !== false) {
|
|
2777
|
-
try {
|
|
2778
|
-
// Collect JSON file paths from the lightweight visitor markers, then
|
|
2779
|
-
// parse each on demand so parsed objects never survive past this loop.
|
|
2780
|
-
const jsonFiles = [];
|
|
2781
|
-
for (const [filePath, fact] of perFile()) {
|
|
2782
|
-
if ('isJson' in fact) {
|
|
2783
|
-
jsonFiles.push(filePath);
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
const readJson = (filePath) => {
|
|
2787
|
-
const raw = context.readSource?.(filePath);
|
|
2788
|
-
if (raw === undefined)
|
|
2789
|
-
return null;
|
|
2790
|
-
try {
|
|
2791
|
-
const parsed = JSON.parse(raw);
|
|
2792
|
-
return parsed !== null && typeof parsed === 'object' ? parsed : null;
|
|
2793
|
-
}
|
|
2794
|
-
catch {
|
|
2795
|
-
return null;
|
|
2796
|
-
}
|
|
2797
|
-
};
|
|
2798
|
-
const jsonResult = analyzeJsonSchemas(jsonFiles, readJson, schemaConfig);
|
|
2799
|
-
violations.push(...(jsonResult?.violations ?? []));
|
|
2800
|
-
}
|
|
2801
|
-
catch (e) {
|
|
2802
|
-
// JSON schema validation is best-effort (non-fatal)
|
|
2803
|
-
}
|
|
2804
|
-
}
|
|
2951
|
+
const { knownTables, tableProvenances, dropProvenance, ddlColumns, tableColumns } = buildSchemaCatalog(allFacts);
|
|
2952
|
+
violations.push(...detectUnknownTables({
|
|
2953
|
+
knownTables,
|
|
2954
|
+
tableProvenances,
|
|
2955
|
+
dropProvenance,
|
|
2956
|
+
allFacts,
|
|
2957
|
+
schemaConfig,
|
|
2958
|
+
}));
|
|
2959
|
+
violations.push(...validateJsonSchemas({
|
|
2960
|
+
schemaConfig,
|
|
2961
|
+
allFacts,
|
|
2962
|
+
context,
|
|
2963
|
+
analyzeJsonSchemas,
|
|
2964
|
+
}));
|
|
2805
2965
|
// ── 4. Build table catalog for metadata ───────────────────────────────
|
|
2806
2966
|
const catalogEntries = [];
|
|
2807
2967
|
for (const [table, sources] of tableProvenances) {
|
|
@@ -2810,7 +2970,7 @@ export function createSchemaReducer() {
|
|
|
2810
2970
|
return {
|
|
2811
2971
|
violations,
|
|
2812
2972
|
facts: { tableCatalog: catalogEntries, ddlColumns: [...ddlColumns].sort(), tableColumns },
|
|
2813
|
-
factsConsumed: [...
|
|
2973
|
+
factsConsumed: [...iteratePerFileFacts(allFacts)].length,
|
|
2814
2974
|
};
|
|
2815
2975
|
},
|
|
2816
2976
|
defaultConfig: {},
|