code-auditor-mcp 4.1.0 → 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 +374 -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 +1 -12
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +24 -101
- 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 +1537 -1051
- 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 +997 -812
- 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 +130 -277
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +364 -2748
- 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 +16 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +26 -23
- 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 +21 -20
- 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/constants.d.ts.map +1 -1
- package/dist/constants.js +10 -5
- package/dist/constants.js.map +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 +22 -7
- package/dist/daemon/resolve.d.ts.map +1 -1
- package/dist/daemon/resolve.js +29 -8
- 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 +27 -8
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +28 -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 +832 -851
- 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 +1145 -988
- 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 +106 -92
- 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 +14 -8
- 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,94 +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(fullFunctionIndex) {
|
|
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();
|
|
92
|
-
const
|
|
93
|
-
? { ...context.config, fullFunctionIndex }
|
|
94
|
-
: context.config;
|
|
95
|
-
const violations = await a.analyzeAST(ast, adapter, config, sourceCode);
|
|
57
|
+
const a = await spec.getAnalyzer();
|
|
58
|
+
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
96
59
|
return { violations, facts: {} };
|
|
97
60
|
},
|
|
98
61
|
defaultConfig: {},
|
|
99
|
-
description:
|
|
100
|
-
category:
|
|
101
|
-
};
|
|
102
|
-
return {
|
|
103
|
-
visitor,
|
|
104
|
-
getDryPairs: async () => {
|
|
105
|
-
try {
|
|
106
|
-
const a = await getAnalyzer();
|
|
107
|
-
return a.dryPairs ?? [];
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return [];
|
|
111
|
-
}
|
|
112
|
-
},
|
|
62
|
+
description: spec.description,
|
|
63
|
+
category: spec.category,
|
|
113
64
|
};
|
|
114
65
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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);
|
|
118
81
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// The violations returned here exclude missing-org-filter (moved to the
|
|
127
|
-
// derived reducer); the calls carry resolved table + filter presence +
|
|
128
|
-
// DB provenance + the query-site location.
|
|
129
|
-
const { violations, calls } = await a.analyzeWithFacts(ast, adapter, context.config, sourceCode);
|
|
130
|
-
const facts = {};
|
|
131
|
-
if (calls.length > 0) {
|
|
132
|
-
facts[context.filePath] = { calls };
|
|
133
|
-
}
|
|
134
|
-
return { violations, facts };
|
|
135
|
-
},
|
|
136
|
-
defaultConfig: {},
|
|
137
|
-
description: 'Analyzes database access patterns and data layer interactions',
|
|
138
|
-
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),
|
|
139
89
|
};
|
|
140
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
|
+
}
|
|
141
107
|
// ── Data-Access org-filter reducer (Spec 62 Amendment B) ─────────────────────
|
|
142
108
|
/**
|
|
143
109
|
* Stage-4 derived reducer for `missing-org-filter`. It joins the data-access
|
|
@@ -151,6 +117,8 @@ export function createDataAccessVisitor() {
|
|
|
151
117
|
*
|
|
152
118
|
* Firing and applicability both derive from {@link buildOrgFilterTierSet}, so
|
|
153
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.
|
|
154
122
|
*/
|
|
155
123
|
export function createOrgFilterReducer() {
|
|
156
124
|
return {
|
|
@@ -158,9 +126,8 @@ export function createOrgFilterReducer() {
|
|
|
158
126
|
stage: 'derivedReducer',
|
|
159
127
|
// Hard dependency on the data-access visitor's per-query facts. The schema
|
|
160
128
|
// table catalog (`allFacts['schema'].tableColumns`) is read opportunistically
|
|
161
|
-
// so
|
|
162
|
-
//
|
|
163
|
-
consumes: ['data-access'],
|
|
129
|
+
// so a run without `schema` facts degrades to Tier 1–2 (config-only) instead
|
|
130
|
+
// of throwing — see buildOrgFilterTierSet.
|
|
164
131
|
getRuleIds: () => getRuleIdsFor('data-access-org-filter'),
|
|
165
132
|
async reduce(allFacts, context) {
|
|
166
133
|
const violations = [];
|
|
@@ -195,7 +162,7 @@ export function createOrgFilterReducer() {
|
|
|
195
162
|
},
|
|
196
163
|
rule: 'missing-org-filter',
|
|
197
164
|
analyzer: 'data-access',
|
|
198
|
-
|
|
165
|
+
symbol,
|
|
199
166
|
});
|
|
200
167
|
}
|
|
201
168
|
}
|
|
@@ -206,41 +173,28 @@ export function createOrgFilterReducer() {
|
|
|
206
173
|
category: 'security',
|
|
207
174
|
};
|
|
208
175
|
}
|
|
209
|
-
// ── Documentation visitor ────────────────────────────────────────────────────
|
|
210
|
-
export function createDocumentationVisitor() {
|
|
211
|
-
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalDocumentationAnalyzer.js').then((m) => new m.UniversalDocumentationAnalyzer()));
|
|
212
|
-
return {
|
|
213
|
-
name: 'documentation',
|
|
214
|
-
stage: 'visitor',
|
|
215
|
-
getRuleIds: () => getRuleIdsFor('documentation'),
|
|
216
|
-
async visit(ast, adapter, context, sourceCode) {
|
|
217
|
-
const a = await getAnalyzer();
|
|
218
|
-
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
219
|
-
return { violations, facts: {} };
|
|
220
|
-
},
|
|
221
|
-
defaultConfig: {},
|
|
222
|
-
description: 'Checks documentation completeness',
|
|
223
|
-
category: 'style',
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
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
|
+
*/
|
|
227
183
|
export function createSecretsVisitor() {
|
|
228
|
-
|
|
229
|
-
return {
|
|
184
|
+
return createAnalyzerAstVisitor({
|
|
230
185
|
name: 'secrets',
|
|
231
|
-
|
|
232
|
-
getRuleIds: () => getRuleIdsFor('secrets'),
|
|
233
|
-
async visit(ast, adapter, context, sourceCode) {
|
|
234
|
-
const a = await getAnalyzer();
|
|
235
|
-
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
236
|
-
return { violations, facts: {} };
|
|
237
|
-
},
|
|
238
|
-
defaultConfig: {},
|
|
186
|
+
getAnalyzer: lazySingleton(() => import('./analyzers/universal/UniversalSecretsAnalyzer.js').then((m) => new m.UniversalSecretsAnalyzer())),
|
|
239
187
|
description: 'Detects hardcoded credentials, API keys, and tokens',
|
|
240
188
|
category: 'security',
|
|
241
|
-
};
|
|
189
|
+
});
|
|
242
190
|
}
|
|
243
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
|
+
*/
|
|
244
198
|
export function createSecurityVisitor() {
|
|
245
199
|
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSecurityAnalyzer.js').then((m) => new m.UniversalSecurityAnalyzer()));
|
|
246
200
|
return {
|
|
@@ -250,23 +204,276 @@ export function createSecurityVisitor() {
|
|
|
250
204
|
async visit(ast, adapter, context, sourceCode) {
|
|
251
205
|
const a = await getAnalyzer();
|
|
252
206
|
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
253
|
-
|
|
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 };
|
|
254
220
|
},
|
|
255
221
|
defaultConfig: {},
|
|
256
222
|
description: 'Detects command injection, dynamic require of project paths, and unescaped HTML interpolation',
|
|
257
223
|
category: 'security',
|
|
258
224
|
};
|
|
259
225
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
function
|
|
267
|
-
const
|
|
268
|
-
|
|
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;
|
|
269
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
|
+
};
|
|
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
|
+
*/
|
|
270
477
|
export function createFunctionIndexVisitor() {
|
|
271
478
|
return {
|
|
272
479
|
name: 'function-index',
|
|
@@ -278,9 +485,12 @@ export function createFunctionIndexVisitor() {
|
|
|
278
485
|
if (!filePath)
|
|
279
486
|
return { violations: [], facts: {} };
|
|
280
487
|
const indexFacts = [];
|
|
281
|
-
// 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`).
|
|
282
492
|
const lang = getLanguageFromPath(filePath);
|
|
283
|
-
if (lang
|
|
493
|
+
if (lang !== 'typescript' && lang !== 'javascript')
|
|
284
494
|
return { violations: [], facts: {} };
|
|
285
495
|
// Clear existing entries for this file
|
|
286
496
|
indexFacts.push({
|
|
@@ -299,148 +509,12 @@ export function createFunctionIndexVisitor() {
|
|
|
299
509
|
}
|
|
300
510
|
// Build import map once per file for resolving call targets
|
|
301
511
|
const importMap = buildImportMap(root, sourceCode);
|
|
302
|
-
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
if (node.type === 'function_declaration') {
|
|
309
|
-
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
310
|
-
if (!nameNode)
|
|
311
|
-
return;
|
|
312
|
-
const name = getNodeText(nameNode, sourceCode);
|
|
313
|
-
if (!name)
|
|
314
|
-
return;
|
|
315
|
-
const { line } = getLineAndColumn(node);
|
|
316
|
-
const endLine = node.location.end.line;
|
|
317
|
-
const body = getFunctionBody(node, sourceCode);
|
|
318
|
-
const calls = extractFunctionCalls(node, sourceCode, importMap);
|
|
319
|
-
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
320
|
-
fnEntries.push({
|
|
321
|
-
name,
|
|
322
|
-
line,
|
|
323
|
-
endLine,
|
|
324
|
-
entityType: 'function',
|
|
325
|
-
componentType: null,
|
|
326
|
-
isExported: isExported(node),
|
|
327
|
-
complexity: calculateComplexity(node),
|
|
328
|
-
body,
|
|
329
|
-
functionCalls: callNames,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
// Class methods
|
|
333
|
-
if (node.type === 'method_definition') {
|
|
334
|
-
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
335
|
-
if (!nameNode)
|
|
336
|
-
return;
|
|
337
|
-
const methodName = getNodeText(nameNode, sourceCode);
|
|
338
|
-
if (!methodName)
|
|
339
|
-
return;
|
|
340
|
-
// Walk up to find class name
|
|
341
|
-
let parent = node.parent;
|
|
342
|
-
let className = 'AnonymousClass';
|
|
343
|
-
while (parent) {
|
|
344
|
-
if (parent.type === 'class_declaration') {
|
|
345
|
-
const cn = parent.children?.find((c) => c.type === 'identifier');
|
|
346
|
-
if (cn)
|
|
347
|
-
className = getNodeText(cn, sourceCode);
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
parent = parent.parent;
|
|
351
|
-
}
|
|
352
|
-
const { line } = getLineAndColumn(node);
|
|
353
|
-
const endLine = node.location.end.line;
|
|
354
|
-
const body = getFunctionBody(node, sourceCode);
|
|
355
|
-
const calls = extractFunctionCalls(node, sourceCode, importMap);
|
|
356
|
-
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
357
|
-
fnEntries.push({
|
|
358
|
-
name: `${className}.${methodName}`,
|
|
359
|
-
line,
|
|
360
|
-
endLine,
|
|
361
|
-
entityType: 'method',
|
|
362
|
-
componentType: null,
|
|
363
|
-
isExported: isExported(node),
|
|
364
|
-
complexity: calculateComplexity(node),
|
|
365
|
-
body,
|
|
366
|
-
functionCalls: callNames,
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
// Pass 2 — arrow functions assigned to variables
|
|
371
|
-
walkAST(root, (node) => {
|
|
372
|
-
if (node.type !== 'variable_declarator')
|
|
373
|
-
return;
|
|
374
|
-
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
375
|
-
const arrowFunc = node.children?.find((c) => c.type === 'arrow_function');
|
|
376
|
-
if (!nameNode || !arrowFunc)
|
|
377
|
-
return;
|
|
378
|
-
const name = getNodeText(nameNode, sourceCode);
|
|
379
|
-
if (!name)
|
|
380
|
-
return;
|
|
381
|
-
// Don't duplicate if already covered as function_declaration
|
|
382
|
-
// (shouldn't happen — function_declaration is a different node type)
|
|
383
|
-
const { line } = getLineAndColumn(arrowFunc);
|
|
384
|
-
const endLine = arrowFunc.location.end.line;
|
|
385
|
-
const body = getFunctionBody(arrowFunc, sourceCode);
|
|
386
|
-
const calls = extractFunctionCalls(arrowFunc, sourceCode, importMap);
|
|
387
|
-
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
388
|
-
fnEntries.push({
|
|
389
|
-
name,
|
|
390
|
-
line,
|
|
391
|
-
endLine,
|
|
392
|
-
entityType: 'function',
|
|
393
|
-
componentType: null,
|
|
394
|
-
isExported: isExported(node),
|
|
395
|
-
complexity: calculateComplexity(arrowFunc),
|
|
396
|
-
body,
|
|
397
|
-
functionCalls: callNames,
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
// Pass 3 — React component detection
|
|
401
|
-
// Detect JSX-returning functions to set entity_type='component' and
|
|
402
|
-
// component_type, matching what functionScanner's deepSync produces.
|
|
403
|
-
// The convention miner's classifyExportKind() uses these columns to
|
|
404
|
-
// partition naming conventions.
|
|
405
|
-
const hasReactImport = [...importMap.values()].some((v) => v.modulePath === 'react');
|
|
406
|
-
if (filePath.endsWith('.tsx') ||
|
|
407
|
-
filePath.endsWith('.jsx') ||
|
|
408
|
-
(filePath.endsWith('.js') && hasReactImport)) {
|
|
409
|
-
walkAST(root, (node) => {
|
|
410
|
-
if (!isReactComponent(node, sourceCode))
|
|
411
|
-
return;
|
|
412
|
-
const ct = detectComponentType(node, sourceCode);
|
|
413
|
-
if (!ct)
|
|
414
|
-
return;
|
|
415
|
-
const cName = getComponentName(node, sourceCode);
|
|
416
|
-
if (!cName || cName === 'AnonymousComponent')
|
|
417
|
-
return;
|
|
418
|
-
const existing = fnEntries.find((f) => f.name === cName);
|
|
419
|
-
if (existing) {
|
|
420
|
-
// Upgrade existing function_declaration or arrow-function entry
|
|
421
|
-
existing.entityType = 'component';
|
|
422
|
-
existing.componentType = ct;
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
// New entry — class component, function_expression, or memo/forwardRef
|
|
426
|
-
// wrapper not already captured by passes 1 or 2.
|
|
427
|
-
const { line } = getLineAndColumn(node);
|
|
428
|
-
const endLine = node.location.end.line;
|
|
429
|
-
const body = getFunctionBody(node, sourceCode);
|
|
430
|
-
fnEntries.push({
|
|
431
|
-
name: cName,
|
|
432
|
-
line,
|
|
433
|
-
endLine,
|
|
434
|
-
entityType: 'component',
|
|
435
|
-
componentType: ct,
|
|
436
|
-
isExported: isExported(node),
|
|
437
|
-
complexity: calculateComplexity(node),
|
|
438
|
-
body,
|
|
439
|
-
functionCalls: [],
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
|
-
}
|
|
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);
|
|
444
518
|
// Build IndexFactsEntry for each function
|
|
445
519
|
for (const fn of fnEntries) {
|
|
446
520
|
const metadata = {
|
|
@@ -449,7 +523,7 @@ export function createFunctionIndexVisitor() {
|
|
|
449
523
|
complexity: fn.complexity,
|
|
450
524
|
functionCalls: fn.functionCalls,
|
|
451
525
|
};
|
|
452
|
-
const contentHash = computeContentHash(fn.body
|
|
526
|
+
const contentHash = computeContentHash(fn.body);
|
|
453
527
|
const now = new Date().toISOString();
|
|
454
528
|
indexFacts.push({
|
|
455
529
|
table: 'functions',
|
|
@@ -462,7 +536,6 @@ export function createFunctionIndexVisitor() {
|
|
|
462
536
|
language: lang,
|
|
463
537
|
entity_type: fn.entityType,
|
|
464
538
|
component_type: fn.componentType,
|
|
465
|
-
signature: '',
|
|
466
539
|
return_type: null,
|
|
467
540
|
complexity: fn.complexity,
|
|
468
541
|
is_exported: fn.isExported ? 1 : 0,
|
|
@@ -471,105 +544,27 @@ export function createFunctionIndexVisitor() {
|
|
|
471
544
|
parameters: null,
|
|
472
545
|
body: fn.body ?? null,
|
|
473
546
|
content_hash: contentHash,
|
|
547
|
+
file_hash: fileHash,
|
|
474
548
|
last_modified: now,
|
|
475
549
|
metadata_json: JSON.stringify(metadata),
|
|
476
550
|
},
|
|
477
551
|
conflictKey: 'name, file_path, line_number',
|
|
478
552
|
});
|
|
479
553
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
moduleSpecifier: imp.source,
|
|
489
|
-
isStatic: true,
|
|
490
|
-
isDynamic: false,
|
|
491
|
-
isRequire: false,
|
|
492
|
-
line: imp.location.start.line,
|
|
493
|
-
}));
|
|
494
|
-
// Spec 60.1 — classify each static import specifier and emit one row per
|
|
495
|
-
// occurrence to `import_specifiers`. The corpus file set is the stage-1
|
|
496
|
-
// discovery list, threaded here as `_infra.files` (merged into
|
|
497
|
-
// `context.config` by pipeline.ts). Existence is checked against that
|
|
498
|
-
// in-memory set, never `fs.existsSync`. Virtual-module list + tsconfig
|
|
499
|
-
// `paths` patterns are threaded via `_infra` and passed to the classifier.
|
|
500
|
-
if (isTsJs) {
|
|
501
|
-
// Spec 60.1 Correction 1 — corpus set is the unfiltered discovery list
|
|
502
|
-
// (`_infra.corpusFiles`), not the narrowed `_infra.files`. The latter is
|
|
503
|
-
// the audit's analysis list (already pruned by includePaths/excludePaths),
|
|
504
|
-
// which drops `.json`/other non-analyzed extensions and would mis-classify
|
|
505
|
-
// a real `./invariant-rules.schema.json` as `internal-broken`.
|
|
506
|
-
const corpusFiles = new Set((context.config.corpusFiles) ??
|
|
507
|
-
(context.config.files) ??
|
|
508
|
-
[]);
|
|
509
|
-
const virtualModules = context.config.importVirtualModules
|
|
510
|
-
?? DEFAULT_VIRTUAL_MODULES;
|
|
511
|
-
const tsconfigAliases = context.config.tsconfigAliases;
|
|
512
|
-
for (const imp of staticImportInfos) {
|
|
513
|
-
const { classification, resolvedPath } = classifyImportSpecifier(imp.source, filePath, corpusFiles, {
|
|
514
|
-
virtualModules,
|
|
515
|
-
aliasPatterns: tsconfigAliases?.pathPatterns ?? [],
|
|
516
|
-
pathMappings: tsconfigAliases?.paths ?? {},
|
|
517
|
-
baseUrl: tsconfigAliases?.baseUrl,
|
|
518
|
-
projectRoot: context.projectRoot,
|
|
519
|
-
});
|
|
520
|
-
indexFacts.push({
|
|
521
|
-
table: 'import_specifiers',
|
|
522
|
-
data: {
|
|
523
|
-
file_path: filePath,
|
|
524
|
-
specifier: imp.source,
|
|
525
|
-
classification,
|
|
526
|
-
resolved_path: resolvedPath ?? null,
|
|
527
|
-
line: imp.location.start.line,
|
|
528
|
-
},
|
|
529
|
-
conflictKey: 'file_path, specifier, line',
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
// Dynamic import() and require() — first attempt with pure NodePattern
|
|
534
|
-
// Plan note: import keyword is an anonymous tree-sitter node, so
|
|
535
|
-
// hasChild cannot see it. We use `custom` with raw node access.
|
|
536
|
-
const dynamicCallNodes = langAdapter.findNodes(langAst, {
|
|
537
|
-
type: 'call_expression',
|
|
538
|
-
custom: (node) => {
|
|
539
|
-
const fn = node.children?.[0];
|
|
540
|
-
return (fn?.type === 'import') ||
|
|
541
|
-
(fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
|
|
542
|
-
},
|
|
554
|
+
const { imports: importFacts, exports: exportInfos } = collectImportExportFacts({
|
|
555
|
+
ast: ast,
|
|
556
|
+
adapter: _adapter,
|
|
557
|
+
sourceCode,
|
|
558
|
+
filePath,
|
|
559
|
+
context,
|
|
560
|
+
isTsJs,
|
|
561
|
+
indexFacts,
|
|
543
562
|
});
|
|
544
|
-
const dynamicImports = [];
|
|
545
|
-
for (const node of dynamicCallNodes) {
|
|
546
|
-
const fn = node.children?.[0];
|
|
547
|
-
const isImport = fn?.type === 'import';
|
|
548
|
-
const isRequire = !isImport && (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
|
|
549
|
-
// Walk the AST children to find the string argument
|
|
550
|
-
const argsNode = node.children?.find((c) => c.type === 'arguments');
|
|
551
|
-
const stringNode = argsNode?.children?.find((c) => c.type === 'string');
|
|
552
|
-
if (stringNode) {
|
|
553
|
-
const text = getNodeText(stringNode, sourceCode);
|
|
554
|
-
if (text.length >= 2) {
|
|
555
|
-
dynamicImports.push({
|
|
556
|
-
moduleSpecifier: text.slice(1, -1), // strip quotes
|
|
557
|
-
isStatic: false,
|
|
558
|
-
isDynamic: isImport,
|
|
559
|
-
isRequire,
|
|
560
|
-
line: node.location.start.line,
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
// Exports via the adapter's canonical extractExports()
|
|
566
|
-
// Returns ExportInfo[] with isDefault — used by both invariants and conventions (B2)
|
|
567
|
-
const exportInfos = langAdapter.extractExports(langAst);
|
|
568
563
|
return {
|
|
569
564
|
violations: [],
|
|
570
565
|
facts: {
|
|
571
566
|
[filePath]: {
|
|
572
|
-
imports:
|
|
567
|
+
imports: importFacts,
|
|
573
568
|
exports: exportInfos,
|
|
574
569
|
},
|
|
575
570
|
},
|
|
@@ -581,6 +576,12 @@ export function createFunctionIndexVisitor() {
|
|
|
581
576
|
category: 'infrastructure',
|
|
582
577
|
};
|
|
583
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
|
+
*/
|
|
584
585
|
export function createReactVisitor() {
|
|
585
586
|
const scanResults = [];
|
|
586
587
|
const visitor = {
|
|
@@ -662,6 +663,8 @@ export function createReactVisitor() {
|
|
|
662
663
|
* Styles CSS visitor — extracts declarations, tokens, and class usage from
|
|
663
664
|
* tree-sitter-css and tree-sitter-scss parsed ASTs. Replaces the regex-based
|
|
664
665
|
* CSS/SCSS extraction in styleExtractor.ts for .css and .scss files.
|
|
666
|
+
*
|
|
667
|
+
* @returns The styles-css stage-2 visitor.
|
|
665
668
|
*/
|
|
666
669
|
export function createStylesCssVisitor() {
|
|
667
670
|
return {
|
|
@@ -702,6 +705,8 @@ export function createStylesCssVisitor() {
|
|
|
702
705
|
* emitting a fact (with a content hash) even when it yields no declarations.
|
|
703
706
|
* The reducer uses that fact to (a) drop stale rows for files that *lost* their
|
|
704
707
|
* styles and (b) skip re-writing files whose stored rows are already current.
|
|
708
|
+
*
|
|
709
|
+
* @returns The styles-source stage-2 visitor.
|
|
705
710
|
*/
|
|
706
711
|
export function createStylesSourceVisitor() {
|
|
707
712
|
// Spec 61 R3.4 — a project tailwind config that exists but cannot be read
|
|
@@ -747,6 +752,44 @@ export function createStylesSourceVisitor() {
|
|
|
747
752
|
category: 'style',
|
|
748
753
|
};
|
|
749
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
|
+
}
|
|
750
793
|
/**
|
|
751
794
|
* Insert TS/JS CSS-in-JS facts (from the styles-source visitor) into the
|
|
752
795
|
* style_* tables. Mirrors the TS/JS branch the style indexer used to run
|
|
@@ -767,19 +810,10 @@ function insertSourceStyleFacts(indexHandle, sourceFacts, isScoped) {
|
|
|
767
810
|
if (entries.length === 0)
|
|
768
811
|
return false;
|
|
769
812
|
const rawDb = indexHandle.rawDb;
|
|
770
|
-
const stmts =
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
delUsage: rawDb.prepare('DELETE FROM style_class_usage WHERE file_path = ?'),
|
|
775
|
-
delTok: rawDb.prepare('DELETE FROM style_tokens WHERE file_path = ?'),
|
|
776
|
-
delUnread: rawDb.prepare('DELETE FROM style_unread_sources WHERE file_path = ?'),
|
|
777
|
-
delClass: rawDb.prepare('DELETE FROM style_defined_classes WHERE file_path = ?'),
|
|
778
|
-
insDecl: rawDb.prepare('INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'),
|
|
779
|
-
insClass: rawDb.prepare('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)'),
|
|
780
|
-
insUsage: rawDb.prepare('INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)'),
|
|
781
|
-
}
|
|
782
|
-
: null;
|
|
813
|
+
const stmts = {
|
|
814
|
+
...prepareCommonStyleStatements(rawDb),
|
|
815
|
+
getHash: rawDb.prepare(SQL_STYLE_GET_HASH),
|
|
816
|
+
};
|
|
783
817
|
let contributed = false;
|
|
784
818
|
const insertOne = (filePath, facts) => {
|
|
785
819
|
if (facts.declarations.length > 0 || facts.classUsage.length > 0)
|
|
@@ -787,59 +821,32 @@ function insertSourceStyleFacts(indexHandle, sourceFacts, isScoped) {
|
|
|
787
821
|
// Full-run content-hash skip: leave already-current rows untouched. Scoped
|
|
788
822
|
// runs always re-write, matching the old style indexer's `if (!scoped)` guard.
|
|
789
823
|
if (!isScoped) {
|
|
790
|
-
const stored = stmts
|
|
791
|
-
? stmts.getHash.get(filePath)
|
|
792
|
-
: indexHandle.query('SELECT content_hash FROM style_declarations WHERE file_path = ? LIMIT 1', [filePath])[0];
|
|
824
|
+
const stored = getStyleHash(stmts, filePath);
|
|
793
825
|
if (stored?.content_hash === facts.contentHash)
|
|
794
826
|
return;
|
|
795
827
|
}
|
|
796
|
-
// Delete stale rows — all
|
|
797
|
-
// so a file that *lost* its styles also loses its stale
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
stmts.delUnread.run(filePath);
|
|
803
|
-
stmts.delClass.run(filePath);
|
|
804
|
-
}
|
|
805
|
-
else {
|
|
806
|
-
indexHandle.run('DELETE FROM style_declarations WHERE file_path = ?', [filePath]);
|
|
807
|
-
indexHandle.run('DELETE FROM style_class_usage WHERE file_path = ?', [filePath]);
|
|
808
|
-
indexHandle.run('DELETE FROM style_tokens WHERE file_path = ?', [filePath]);
|
|
809
|
-
indexHandle.run('DELETE FROM style_unread_sources WHERE file_path = ?', [filePath]);
|
|
810
|
-
indexHandle.run('DELETE FROM style_defined_classes WHERE file_path = ?', [filePath]);
|
|
811
|
-
}
|
|
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]);
|
|
812
834
|
// Insert declarations (and their defined-class catalog entries).
|
|
813
835
|
for (const decl of facts.declarations) {
|
|
814
|
-
|
|
815
|
-
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);
|
|
816
|
-
}
|
|
817
|
-
else {
|
|
818
|
-
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]);
|
|
819
|
-
}
|
|
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]);
|
|
820
837
|
if (decl.context) {
|
|
821
838
|
for (const m of decl.context.matchAll(/\.([a-zA-Z0-9_-]+)/g)) {
|
|
822
|
-
|
|
823
|
-
stmts.insClass.run(m[1], decl.filePath);
|
|
824
|
-
else
|
|
825
|
-
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]);
|
|
826
840
|
}
|
|
827
841
|
}
|
|
828
842
|
}
|
|
829
843
|
// Insert class usage.
|
|
830
844
|
for (const cu of facts.classUsage) {
|
|
831
|
-
|
|
832
|
-
stmts.insUsage.run(cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0);
|
|
833
|
-
else
|
|
834
|
-
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]);
|
|
835
846
|
}
|
|
836
847
|
};
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
insertOne(fp, f); })();
|
|
840
|
-
else
|
|
841
|
-
for (const [fp, f] of entries)
|
|
842
|
-
insertOne(fp, f);
|
|
848
|
+
rawDb.transaction(() => { for (const [fp, f] of entries)
|
|
849
|
+
insertOne(fp, f); })();
|
|
843
850
|
return contributed;
|
|
844
851
|
}
|
|
845
852
|
/**
|
|
@@ -850,6 +857,8 @@ function insertSourceStyleFacts(indexHandle, sourceFacts, isScoped) {
|
|
|
850
857
|
* Spec 26 Phase 2: CSS facts from the styles-css visitor are inserted into
|
|
851
858
|
* the DB before the analyzer runs. For .css files, the AST extraction replaces
|
|
852
859
|
* the regex-based styleIndexer path (which now skips .css files).
|
|
860
|
+
*
|
|
861
|
+
* @returns The styles stage-3 reducer.
|
|
853
862
|
*/
|
|
854
863
|
export function createStylesReducer() {
|
|
855
864
|
return {
|
|
@@ -891,77 +900,44 @@ export function createStylesReducer() {
|
|
|
891
900
|
// statement — measured at 345ms for 5 files / ~13k declarations on
|
|
892
901
|
// recall-protocol. Prepare-once + one transaction collapses that.
|
|
893
902
|
const rawDb = context.indexHandle.rawDb;
|
|
894
|
-
const stmts =
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
delUsage: rawDb.prepare('DELETE FROM style_class_usage WHERE file_path = ?'),
|
|
899
|
-
delClass: rawDb.prepare('DELETE FROM style_defined_classes WHERE file_path = ?'),
|
|
900
|
-
insDecl: rawDb.prepare('INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'),
|
|
901
|
-
insClass: rawDb.prepare('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)'),
|
|
902
|
-
insTok: rawDb.prepare('INSERT INTO style_tokens (name, value, file_path, mechanism) VALUES (?, ?, ?, ?)'),
|
|
903
|
-
insUsage: rawDb.prepare('INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)'),
|
|
904
|
-
}
|
|
905
|
-
: null;
|
|
903
|
+
const stmts = {
|
|
904
|
+
...prepareCommonStyleStatements(rawDb),
|
|
905
|
+
insTok: rawDb.prepare(SQL_STYLE_INS_TOKEN),
|
|
906
|
+
};
|
|
906
907
|
const insertAll = () => {
|
|
907
908
|
for (const [filePath, facts] of Object.entries(cssFacts)) {
|
|
908
909
|
// Delete old entries for this file (replaces the styleIndexer path)
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
stmts.delClass.run(filePath);
|
|
914
|
-
}
|
|
915
|
-
else {
|
|
916
|
-
indexHandle.run('DELETE FROM style_declarations WHERE file_path = ?', [filePath]);
|
|
917
|
-
indexHandle.run('DELETE FROM style_tokens WHERE file_path = ?', [filePath]);
|
|
918
|
-
indexHandle.run('DELETE FROM style_class_usage WHERE file_path = ?', [filePath]);
|
|
919
|
-
indexHandle.run('DELETE FROM style_defined_classes WHERE file_path = ?', [filePath]);
|
|
920
|
-
}
|
|
910
|
+
runStyleWrite(stmts, 'delDecl', [filePath]);
|
|
911
|
+
runStyleWrite(stmts, 'delTok', [filePath]);
|
|
912
|
+
runStyleWrite(stmts, 'delUsage', [filePath]);
|
|
913
|
+
runStyleWrite(stmts, 'delClass', [filePath]);
|
|
921
914
|
// Compute content hash for the file
|
|
922
915
|
const contentStr = JSON.stringify({ declarations: facts.declarations.length, tokens: facts.tokens.length, classUsage: facts.classUsage.length });
|
|
923
916
|
const contentHash = createHash('sha256').update(contentStr).digest('hex').slice(0, 16);
|
|
924
917
|
// Insert declarations
|
|
925
918
|
for (const decl of facts.declarations) {
|
|
926
|
-
|
|
927
|
-
stmts.insDecl.run(decl.property, decl.rawValue, JSON.stringify(decl.normalizedValue), decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, contentHash);
|
|
928
|
-
}
|
|
929
|
-
else {
|
|
930
|
-
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]);
|
|
931
|
-
}
|
|
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]);
|
|
932
920
|
// Populate the defined-class catalog (Spec 45) from any class
|
|
933
921
|
// selectors in the rule's context so styles/undefined-class can
|
|
934
922
|
// resolve names via `style_defined_classes` instead of a
|
|
935
923
|
// full-corpus regex scan over every declaration.
|
|
936
924
|
if (decl.context) {
|
|
937
925
|
for (const m of decl.context.matchAll(/\.([a-zA-Z0-9_-]+)/g)) {
|
|
938
|
-
|
|
939
|
-
stmts.insClass.run(m[1], decl.filePath);
|
|
940
|
-
else
|
|
941
|
-
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]);
|
|
942
927
|
}
|
|
943
928
|
}
|
|
944
929
|
}
|
|
945
930
|
// Insert tokens
|
|
946
931
|
for (const tok of facts.tokens) {
|
|
947
|
-
|
|
948
|
-
stmts.insTok.run(tok.name, tok.value, tok.filePath, tok.mechanism);
|
|
949
|
-
else
|
|
950
|
-
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]);
|
|
951
933
|
}
|
|
952
934
|
// Insert class usage
|
|
953
935
|
for (const cu of facts.classUsage) {
|
|
954
|
-
|
|
955
|
-
stmts.insUsage.run(cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0);
|
|
956
|
-
else
|
|
957
|
-
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]);
|
|
958
937
|
}
|
|
959
938
|
}
|
|
960
939
|
};
|
|
961
|
-
|
|
962
|
-
rawDb.transaction(insertAll)();
|
|
963
|
-
else
|
|
964
|
-
insertAll();
|
|
940
|
+
rawDb.transaction(insertAll)();
|
|
965
941
|
}
|
|
966
942
|
// Short-circuit on non-style-bearing scoped runs — the source and CSS
|
|
967
943
|
// facts above are already written (so stale rows are dropped); only the
|
|
@@ -993,10 +969,32 @@ export function createStylesReducer() {
|
|
|
993
969
|
};
|
|
994
970
|
}
|
|
995
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
|
+
}
|
|
996
992
|
/**
|
|
997
993
|
* Conventions reducer — queries conventions table and flags deviations
|
|
998
994
|
* (usage-pair, import-form, error-handling, export-shape, naming).
|
|
999
995
|
* Uses analyzer.analyze([]) with rawDb injected.
|
|
996
|
+
*
|
|
997
|
+
* @returns The conventions stage-3 reducer.
|
|
1000
998
|
*/
|
|
1001
999
|
export function createConventionsReducer() {
|
|
1002
1000
|
return {
|
|
@@ -1017,12 +1015,7 @@ export function createConventionsReducer() {
|
|
|
1017
1015
|
.filter(([, data]) => data?.exports?.length)
|
|
1018
1016
|
.map(([filePath, data]) => [filePath, data.exports]))
|
|
1019
1017
|
: undefined;
|
|
1020
|
-
|
|
1021
|
-
// its function/file queries to only the changed files. `_infra.files` are
|
|
1022
|
-
// absolute; `functions.file_path` is also absolute (function-index visitor
|
|
1023
|
-
// stores `context.filePath`), so the IN clause matches directly.
|
|
1024
|
-
const scopedFiles = context.isScoped ? (context.config.files ?? []) : undefined;
|
|
1025
|
-
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 };
|
|
1026
1019
|
const result = await analyzer.analyze([], config);
|
|
1027
1020
|
// Count the INPUT the conventions analyzer reads (the function index the
|
|
1028
1021
|
// function-index visitor populated), not the derived `conventions` table it
|
|
@@ -1031,7 +1024,12 @@ export function createConventionsReducer() {
|
|
|
1031
1024
|
// analyzer — counting it as "facts consumed" misreports a present input as
|
|
1032
1025
|
// notApplicable. (Spec 39 R2/R3 bug B)
|
|
1033
1026
|
const factsConsumed = context.indexHandle.count('functions');
|
|
1034
|
-
return {
|
|
1027
|
+
return {
|
|
1028
|
+
violations: result.violations ?? [],
|
|
1029
|
+
facts: {},
|
|
1030
|
+
factsConsumed,
|
|
1031
|
+
...(result.diagnostics && result.diagnostics.length > 0 && { diagnostics: result.diagnostics }),
|
|
1032
|
+
};
|
|
1035
1033
|
}
|
|
1036
1034
|
catch {
|
|
1037
1035
|
return { violations: [], facts: {} };
|
|
@@ -1047,12 +1045,13 @@ export function createConventionsReducer() {
|
|
|
1047
1045
|
* Cross-domain reducer — runs written-never-read, read-never-written,
|
|
1048
1046
|
* transaction-boundary risk, no-validator-reachable, and uncovered-risk detectors.
|
|
1049
1047
|
* Uses analyzer.analyze([]) with rawDb injected.
|
|
1048
|
+
*
|
|
1049
|
+
* @returns The cross-domain stage-4 reducer.
|
|
1050
1050
|
*/
|
|
1051
1051
|
export function createCrossDomainReducer() {
|
|
1052
1052
|
return {
|
|
1053
1053
|
name: 'cross-domain',
|
|
1054
1054
|
stage: 'derivedReducer',
|
|
1055
|
-
consumes: [],
|
|
1056
1055
|
getRuleIds: () => getRuleIdsFor('cross-domain'),
|
|
1057
1056
|
async reduce(_allFacts, context) {
|
|
1058
1057
|
if (!context.indexHandle)
|
|
@@ -1060,11 +1059,7 @@ export function createCrossDomainReducer() {
|
|
|
1060
1059
|
try {
|
|
1061
1060
|
const { CrossDomainAnalyzer } = await import('./analyzers/crossDomain/CrossDomainAnalyzer.js');
|
|
1062
1061
|
const analyzer = new CrossDomainAnalyzer();
|
|
1063
|
-
|
|
1064
|
-
// anchor to changed files on diff audits; full audits keep the root-LIKE
|
|
1065
|
-
// scope (resolveFileScope falls back when isScoped is false).
|
|
1066
|
-
const scopedFiles = context.isScoped ? (context.config.files ?? []) : undefined;
|
|
1067
|
-
const config = { ...context.config, indexHandle: context.indexHandle, projectRoot: context.projectRoot, scopedFiles, isScoped: context.isScoped };
|
|
1062
|
+
const config = makeIndexAnalyzerConfig(context);
|
|
1068
1063
|
const result = await analyzer.analyze([], config);
|
|
1069
1064
|
// Count DB rows consumed across primary cross-domain tables
|
|
1070
1065
|
let factsConsumed = 0;
|
|
@@ -1098,7 +1093,7 @@ export function createCrossDomainReducer() {
|
|
|
1098
1093
|
// DependencyGraphBuilder) are corpus-wide: they compare schemas/endpoints/cycles
|
|
1099
1094
|
// across files and languages. They run as Stage-4 derived reducers over a single
|
|
1100
1095
|
// shared entity-extraction visitor, so each analyzer's rule IDs map to its own
|
|
1101
|
-
// result row (coverage resolves by
|
|
1096
|
+
// result row (coverage resolves by each rule's analyzer — one reducer per
|
|
1102
1097
|
// analyzer, not one reducer fanning out to three names).
|
|
1103
1098
|
//
|
|
1104
1099
|
// Scope-awareness ("Stage 4, and measure the gate"): the corpus-wide graph/cycle
|
|
@@ -1183,10 +1178,11 @@ function clCollectFileReferences(node, sourceCode) {
|
|
|
1183
1178
|
}
|
|
1184
1179
|
else if (t === 'identifier') {
|
|
1185
1180
|
// A bare identifier used as a value — an argument to a call
|
|
1186
|
-
// (`arr.map(singularize)`)
|
|
1187
|
-
//
|
|
1188
|
-
//
|
|
1189
|
-
// 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.
|
|
1190
1186
|
const parent = n.parent;
|
|
1191
1187
|
if (parent && (parent.type === 'arguments' || parent.type === 'array')) {
|
|
1192
1188
|
const text = getNodeText(n, sourceCode).trim();
|
|
@@ -1194,13 +1190,34 @@ function clCollectFileReferences(node, sourceCode) {
|
|
|
1194
1190
|
names.add(text);
|
|
1195
1191
|
}
|
|
1196
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
|
+
}
|
|
1197
1213
|
for (const child of n.children ?? [])
|
|
1198
1214
|
walk(child);
|
|
1199
1215
|
};
|
|
1200
1216
|
walk(node);
|
|
1201
1217
|
return [...names];
|
|
1202
1218
|
}
|
|
1203
|
-
function clMakeFunction(
|
|
1219
|
+
function clMakeFunction(opts) {
|
|
1220
|
+
const { filePath, lang, name, line, signature, purpose, params, callees, exported, complexity, isMethod = false } = opts;
|
|
1204
1221
|
return {
|
|
1205
1222
|
id: clEntityId(filePath, 'function', name, line),
|
|
1206
1223
|
name,
|
|
@@ -1248,7 +1265,7 @@ function clDynamicImport(node, sourceCode) {
|
|
|
1248
1265
|
// not a computed specifier — `import(\`@babel/plugin-syntax-jsx\`)` resolves
|
|
1249
1266
|
// to an edge just like `import('@babel/plugin-syntax-jsx')`. Only an
|
|
1250
1267
|
// interpolated template (`` import(`./${name}.js`) ``) is genuinely computed.
|
|
1251
|
-
const templateNode = args?.children?.find((c) => c.type === 'template_string'
|
|
1268
|
+
const templateNode = args?.children?.find((c) => c.type === 'template_string');
|
|
1252
1269
|
if (templateNode) {
|
|
1253
1270
|
const hasSubstitution = (templateNode.children ?? []).some((c) => c.type === 'template_substitution');
|
|
1254
1271
|
if (!hasSubstitution) {
|
|
@@ -1378,7 +1395,16 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
|
|
|
1378
1395
|
}
|
|
1379
1396
|
}
|
|
1380
1397
|
const line = node.location.start.line;
|
|
1381
|
-
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
|
+
});
|
|
1382
1408
|
entity.metadata.fileReferences = fileReferences;
|
|
1383
1409
|
out.push(entity);
|
|
1384
1410
|
return;
|
|
@@ -1392,7 +1418,15 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
|
|
|
1392
1418
|
if (!name)
|
|
1393
1419
|
return;
|
|
1394
1420
|
const line = node.location.start.line;
|
|
1395
|
-
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
|
+
});
|
|
1396
1430
|
entity.metadata.fileReferences = fileReferences;
|
|
1397
1431
|
out.push(entity);
|
|
1398
1432
|
return;
|
|
@@ -1452,18 +1486,24 @@ function clExtractGoParams(node, sourceCode) {
|
|
|
1452
1486
|
}
|
|
1453
1487
|
function clExtractGoStructFields(structNode, sourceCode) {
|
|
1454
1488
|
const fields = [];
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
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
|
+
}
|
|
1467
1507
|
}
|
|
1468
1508
|
return fields;
|
|
1469
1509
|
}
|
|
@@ -1478,7 +1518,15 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
|
1478
1518
|
const receiverType = receiver ? clGoReceiverType(receiver, sourceCode) : undefined;
|
|
1479
1519
|
const displayName = receiverType ? `${receiverType}.${name}` : name;
|
|
1480
1520
|
const line = node.location.start.line;
|
|
1481
|
-
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
|
+
});
|
|
1482
1530
|
entity.metadata.fileReferences = fileReferences;
|
|
1483
1531
|
out.push(entity);
|
|
1484
1532
|
return;
|
|
@@ -1537,6 +1585,38 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
|
1537
1585
|
});
|
|
1538
1586
|
}
|
|
1539
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
|
+
*/
|
|
1540
1620
|
export function createCrossLanguageEntityVisitor() {
|
|
1541
1621
|
return {
|
|
1542
1622
|
name: 'cross-language-entities',
|
|
@@ -1548,7 +1628,7 @@ export function createCrossLanguageEntityVisitor() {
|
|
|
1548
1628
|
if (!filePath)
|
|
1549
1629
|
return { violations: [], facts: {} };
|
|
1550
1630
|
const lang = getLanguageFromPath(filePath);
|
|
1551
|
-
if (lang
|
|
1631
|
+
if (lang !== 'typescript' && lang !== 'javascript' && lang !== 'go')
|
|
1552
1632
|
return { violations: [], facts: {} };
|
|
1553
1633
|
const root = ast.root;
|
|
1554
1634
|
const entities = [];
|
|
@@ -1689,6 +1769,11 @@ function clComputeReachability(fileFacts, options) {
|
|
|
1689
1769
|
* nothing *and* not a framework entry point. `deadPreException` is the
|
|
1690
1770
|
* empty-importers count before the entry-point exception, so the drop
|
|
1691
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.
|
|
1692
1777
|
*/
|
|
1693
1778
|
export function classifyTestCoverage(fileFacts, importersOf, packageEntrySet) {
|
|
1694
1779
|
const result = {
|
|
@@ -1753,6 +1838,9 @@ export function classifyTestCoverage(fileFacts, importersOf, packageEntrySet) {
|
|
|
1753
1838
|
* the same file name appearing in several sibling directories (e.g. two
|
|
1754
1839
|
* `ErrorState.tsx`, three `services.ts` under separate `reports` folders). Only
|
|
1755
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).
|
|
1756
1844
|
*/
|
|
1757
1845
|
export function clusterDeadModules(deadFiles) {
|
|
1758
1846
|
const byBasename = new Map();
|
|
@@ -1792,6 +1880,15 @@ function clBuildReferences(entities) {
|
|
|
1792
1880
|
const eDir = e.file.split('/').slice(0, -1).join('/');
|
|
1793
1881
|
for (const callee of callees) {
|
|
1794
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;
|
|
1795
1892
|
const others = (byName.get(name) ?? []).filter((t) => t.id !== e.id);
|
|
1796
1893
|
if (others.length === 0)
|
|
1797
1894
|
continue;
|
|
@@ -1828,11 +1925,16 @@ function clBuildReferences(entities) {
|
|
|
1828
1925
|
return refs;
|
|
1829
1926
|
}
|
|
1830
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
|
+
*/
|
|
1831
1934
|
export function createSchemaValidatorReducer() {
|
|
1832
1935
|
return {
|
|
1833
1936
|
name: 'schema-validator',
|
|
1834
1937
|
stage: 'derivedReducer',
|
|
1835
|
-
consumes: ['cross-language-entities'],
|
|
1836
1938
|
getRuleIds: () => getRuleIdsFor('schema-validator'),
|
|
1837
1939
|
async reduce(allFacts, context) {
|
|
1838
1940
|
if (context.isScoped) {
|
|
@@ -1884,11 +1986,16 @@ export function createSchemaValidatorReducer() {
|
|
|
1884
1986
|
};
|
|
1885
1987
|
}
|
|
1886
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
|
+
*/
|
|
1887
1995
|
export function createAPIContractReducer() {
|
|
1888
1996
|
return {
|
|
1889
1997
|
name: 'api-contract',
|
|
1890
1998
|
stage: 'derivedReducer',
|
|
1891
|
-
consumes: ['cross-language-entities'],
|
|
1892
1999
|
getRuleIds: () => getRuleIdsFor('api-contract'),
|
|
1893
2000
|
async reduce(allFacts, context) {
|
|
1894
2001
|
if (context.isScoped) {
|
|
@@ -1920,11 +2027,39 @@ export function createAPIContractReducer() {
|
|
|
1920
2027
|
};
|
|
1921
2028
|
}
|
|
1922
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
|
+
*/
|
|
1923
2059
|
export function createDependencyGraphReducer() {
|
|
1924
2060
|
return {
|
|
1925
2061
|
name: 'dependency-graph',
|
|
1926
2062
|
stage: 'derivedReducer',
|
|
1927
|
-
consumes: ['cross-language-entities'],
|
|
1928
2063
|
getRuleIds: () => getRuleIdsFor('dependency-graph'),
|
|
1929
2064
|
async reduce(allFacts, context) {
|
|
1930
2065
|
if (context.isScoped) {
|
|
@@ -1965,7 +2100,7 @@ export function createDependencyGraphReducer() {
|
|
|
1965
2100
|
type: issue.type, // dependency-graph rules match on field: 'type'
|
|
1966
2101
|
analyzer: 'dependency-graph',
|
|
1967
2102
|
category: 'cross-language-dependency',
|
|
1968
|
-
|
|
2103
|
+
symbol: orphan.name,
|
|
1969
2104
|
details: { orphanId: id },
|
|
1970
2105
|
});
|
|
1971
2106
|
}
|
|
@@ -2004,45 +2139,21 @@ export function createDependencyGraphReducer() {
|
|
|
2004
2139
|
// Persist reachability to graph_cache so the post-pipeline reorder
|
|
2005
2140
|
// (auditRunner hotspot block) can rank every finding by live/dead.
|
|
2006
2141
|
if (context.indexHandle?.rawDb) {
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
const del = db.prepare("DELETE FROM graph_cache WHERE graph_type = 'reachability'");
|
|
2010
|
-
const ins = db.prepare("INSERT OR REPLACE INTO graph_cache (graph_type, node_key, neighbor_key, weight) VALUES ('reachability', ?, '', ?)");
|
|
2011
|
-
const tx = db.transaction(() => {
|
|
2012
|
-
del.run();
|
|
2013
|
-
for (const [fp, score] of reachability) {
|
|
2014
|
-
ins.run(fp, score);
|
|
2015
|
-
}
|
|
2016
|
-
});
|
|
2017
|
-
tx();
|
|
2018
|
-
}
|
|
2019
|
-
catch {
|
|
2020
|
-
// Reachability persistence is advisory — ranking falls back to a
|
|
2021
|
-
// neutral default without it.
|
|
2022
|
-
}
|
|
2142
|
+
const reachabilityRows = [...reachability].map(([fp, score]) => [fp, '', score]);
|
|
2143
|
+
persistGraphEdges(context.indexHandle.rawDb, 'reachability', reachabilityRows);
|
|
2023
2144
|
}
|
|
2024
2145
|
// Spec 60 R1 — persist the file-level import edges (reverse adjacency:
|
|
2025
2146
|
// module → its direct importers) so "what tests exercise this module"
|
|
2026
2147
|
// is answerable downstream from `importersOf`, not the symbol-level
|
|
2027
2148
|
// `function_dependencies` table.
|
|
2028
2149
|
if (context.indexHandle?.rawDb) {
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
del.run();
|
|
2035
|
-
for (const [fp, importers] of importersOf) {
|
|
2036
|
-
for (const importer of importers) {
|
|
2037
|
-
ins.run(fp, importer);
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
});
|
|
2041
|
-
tx();
|
|
2042
|
-
}
|
|
2043
|
-
catch {
|
|
2044
|
-
// 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
|
+
}
|
|
2045
2155
|
}
|
|
2156
|
+
persistGraphEdges(context.indexHandle.rawDb, 'importers', importerRows);
|
|
2046
2157
|
}
|
|
2047
2158
|
// Flag files that export symbols yet are imported by nothing and are
|
|
2048
2159
|
// not framework entry points — a dead module. This is the signal the
|
|
@@ -2100,7 +2211,6 @@ export function createDependencyGraphReducer() {
|
|
|
2100
2211
|
const HEALTH_SEVERITY = {
|
|
2101
2212
|
'break-cycles': 'severe',
|
|
2102
2213
|
'reduce-coupling': 'high',
|
|
2103
|
-
'split-responsibilities': 'high',
|
|
2104
2214
|
'review-orphans': 'severe',
|
|
2105
2215
|
};
|
|
2106
2216
|
for (const s of health.suggestions) {
|
|
@@ -2138,6 +2248,8 @@ export function createDependencyGraphReducer() {
|
|
|
2138
2248
|
* Invariants reducer — enforces user-defined invariant rules from .codeauditor.json.
|
|
2139
2249
|
* Receives the full file list via context.files and runs the rule engine across
|
|
2140
2250
|
* all files at once (call-constraint rules inherently need cross-file scope).
|
|
2251
|
+
*
|
|
2252
|
+
* @returns The invariants stage-3 reducer.
|
|
2141
2253
|
*/
|
|
2142
2254
|
export function createInvariantsReducer() {
|
|
2143
2255
|
return {
|
|
@@ -2221,6 +2333,8 @@ async function _getProvenanceModule() {
|
|
|
2221
2333
|
* Schema SQL visitor (.sql files) — extracts ordered DDL operations for the
|
|
2222
2334
|
* Stage 3 reducer to replay into the known-tables catalog. Emits the parsed
|
|
2223
2335
|
* ops, not raw source, so the reducer retains only state transitions.
|
|
2336
|
+
*
|
|
2337
|
+
* @returns The schema-sql stage-2 visitor.
|
|
2224
2338
|
*/
|
|
2225
2339
|
export function createSchemaSqlVisitor() {
|
|
2226
2340
|
const getMigrationExtractor = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js'));
|
|
@@ -2231,7 +2345,8 @@ export function createSchemaSqlVisitor() {
|
|
|
2231
2345
|
getRuleIds: () => [],
|
|
2232
2346
|
async visit(_ast, _adapter, context, sourceCode) {
|
|
2233
2347
|
const { extractMigrationOpsFromFile } = await getMigrationExtractor();
|
|
2234
|
-
const
|
|
2348
|
+
const dialect = context.config.sqlDialect;
|
|
2349
|
+
const { ops, columns, tableColumns, skipped, bytes } = await extractMigrationOpsFromFile(context.filePath, sourceCode, dialect ?? null);
|
|
2235
2350
|
return {
|
|
2236
2351
|
violations: [],
|
|
2237
2352
|
facts: {
|
|
@@ -2249,181 +2364,177 @@ export function createSchemaSqlVisitor() {
|
|
|
2249
2364
|
category: 'database',
|
|
2250
2365
|
};
|
|
2251
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
|
+
}
|
|
2252
2525
|
/**
|
|
2253
2526
|
* Schema code visitor (.ts/.tsx/.js/.jsx) — per-file analysis for naming conventions,
|
|
2254
2527
|
* query patterns, SQL injection, table references, and ORM table extraction.
|
|
2528
|
+
*
|
|
2529
|
+
* @returns The schema-code stage-2 visitor.
|
|
2255
2530
|
*/
|
|
2256
2531
|
export function createSchemaCodeVisitor() {
|
|
2257
|
-
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js').then((m) => ({
|
|
2258
|
-
analyzer: new m.UniversalSchemaAnalyzer(),
|
|
2259
|
-
defaults: m.DEFAULT_SCHEMA_CONFIG,
|
|
2260
|
-
parseMigrationOps: m.parseMigrationOps,
|
|
2261
|
-
extractDdlColumnNames: m.extractDdlColumnNames,
|
|
2262
|
-
extractDdlTableColumns: m.extractDdlTableColumns,
|
|
2263
|
-
})));
|
|
2264
2532
|
return {
|
|
2265
2533
|
name: 'schema-code',
|
|
2266
2534
|
stage: 'visitor',
|
|
2267
2535
|
extensions: ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'],
|
|
2268
2536
|
getRuleIds: () => getRuleIdsFor('schema'),
|
|
2269
|
-
|
|
2270
|
-
const { analyzer: a, defaults, parseMigrationOps, extractDdlColumnNames, extractDdlTableColumns } = await getAnalyzer();
|
|
2271
|
-
const pm = await _getProvenanceModule();
|
|
2272
|
-
const violations = [];
|
|
2273
|
-
const diagnostics = [];
|
|
2274
|
-
const indexFacts = [];
|
|
2275
|
-
// Pipeline config for this analyzer (moved before table extraction, needed
|
|
2276
|
-
// by the table-source registry and provenance context).
|
|
2277
|
-
const schemaConfig = (context.config ?? {});
|
|
2278
|
-
// Spec 29 R2: Extract ORM table names via declarative table-source registry.
|
|
2279
|
-
// Adding an ORM is now a config entry, not code. Falls back to Drizzle
|
|
2280
|
-
// entries when no user-specified tableSources are configured.
|
|
2281
|
-
const ormTables = [];
|
|
2282
|
-
const tableProvenances = [];
|
|
2283
|
-
const tableSources = schemaConfig.tableSources ?? [
|
|
2284
|
-
{ kind: 'callee', name: 'pgTable', arg: 0, description: 'Drizzle PostgreSQL table' },
|
|
2285
|
-
{ kind: 'callee', name: 'mysqlTable', arg: 0, description: 'Drizzle MySQL table' },
|
|
2286
|
-
{ kind: 'callee', name: 'sqliteTable', arg: 0, description: 'Drizzle SQLite table' },
|
|
2287
|
-
];
|
|
2288
|
-
if (tableSources.length > 0) {
|
|
2289
|
-
const registered = extractTablesFromRegistry(tableSources, {
|
|
2290
|
-
ast: ast,
|
|
2291
|
-
adapter: adapter,
|
|
2292
|
-
sourceCode,
|
|
2293
|
-
filePath: context.filePath,
|
|
2294
|
-
});
|
|
2295
|
-
for (const { table, source } of registered) {
|
|
2296
|
-
ormTables.push(table);
|
|
2297
|
-
tableProvenances.push({ table, source });
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
// Extract DDL from sql.exec(...) string literals inside Durable Object classes.
|
|
2301
|
-
// These are CREATE TABLE / DROP TABLE statements at runtime that migration
|
|
2302
|
-
// discovery never sees. Extracting them completes the authoritative catalog
|
|
2303
|
-
// so unknown-table doesn't false-positive on DO-local tables.
|
|
2304
|
-
const doDDL = [];
|
|
2305
|
-
const doTemplateDDL = /`([^`]*(?:CREATE|DROP|ALTER)\s+(?:TABLE|VIRTUAL\s+TABLE)\s+[^`]+)`/gis;
|
|
2306
|
-
const doStringDDL = /(["'])((?:\s*(?:CREATE|DROP|ALTER)\s+(?:TABLE|VIRTUAL\s+TABLE)\s+[^"']+))\1/gis;
|
|
2307
|
-
let ddlMatch;
|
|
2308
|
-
while ((ddlMatch = doTemplateDDL.exec(sourceCode)) !== null) {
|
|
2309
|
-
const sql = ddlMatch[1].trim();
|
|
2310
|
-
if (sql)
|
|
2311
|
-
doDDL.push(sql);
|
|
2312
|
-
}
|
|
2313
|
-
while ((ddlMatch = doStringDDL.exec(sourceCode)) !== null) {
|
|
2314
|
-
const sql = ddlMatch[2].trim();
|
|
2315
|
-
if (sql)
|
|
2316
|
-
doDDL.push(sql);
|
|
2317
|
-
}
|
|
2318
|
-
const doDDLSql = doDDL.length > 0 ? doDDL.join(';\n') : null;
|
|
2319
|
-
const doDDLColumns = doDDLSql ? extractDdlColumnNames(doDDLSql) : [];
|
|
2320
|
-
const doDDLTableColumns = doDDLSql ? extractDdlTableColumns(doDDLSql) : {};
|
|
2321
|
-
// Build provenance context for this file — defaults from DEFAULT_SCHEMA_CONFIG
|
|
2322
|
-
const detectionMode = schemaConfig.detection?.mode ?? 'hybrid';
|
|
2323
|
-
const provenanceContext = pm.buildProvenanceContext(ast, adapter, sourceCode, {
|
|
2324
|
-
mode: detectionMode,
|
|
2325
|
-
dbReceiverNames: schemaConfig.dbReceiverNames ?? defaults.dbReceiverNames,
|
|
2326
|
-
dbBindingNames: schemaConfig.dbBindingNames ?? defaults.dbBindingNames,
|
|
2327
|
-
dbCallMethods: schemaConfig.dbCallMethods ?? defaults.dbCallMethods,
|
|
2328
|
-
dbWrapperNames: schemaConfig.dbWrapperNames ?? defaults.dbWrapperNames,
|
|
2329
|
-
});
|
|
2330
|
-
// File gate — skip files without DB usage
|
|
2331
|
-
if (!passesFileGate(context.filePath, sourceCode, schemaConfig, provenanceContext)) {
|
|
2332
|
-
const facts = { [context.filePath]: { tableRefs: [], ormTables, tableProvenance: tableProvenances } };
|
|
2333
|
-
if (doDDL.length > 0) {
|
|
2334
|
-
facts[context.filePath].ddlOps = parseMigrationOps(doDDLSql);
|
|
2335
|
-
if (doDDLColumns.length > 0)
|
|
2336
|
-
facts[context.filePath].ddlColumns = doDDLColumns;
|
|
2337
|
-
if (Object.keys(doDDLTableColumns).length > 0)
|
|
2338
|
-
facts[context.filePath].ddlTableColumns = doDDLTableColumns;
|
|
2339
|
-
}
|
|
2340
|
-
return { violations: [], facts };
|
|
2341
|
-
}
|
|
2342
|
-
// Build known-tables set from schemas config (pre-pipeline + DB-loaded schemas)
|
|
2343
|
-
const schemas = schemaConfig.schemas ?? [];
|
|
2344
|
-
const knownTablesArr = schemaConfig.knownTables ?? [];
|
|
2345
|
-
const allTables = new Set();
|
|
2346
|
-
for (const t of knownTablesArr)
|
|
2347
|
-
allTables.add(t);
|
|
2348
|
-
for (const schema of schemas) {
|
|
2349
|
-
for (const table of (schema.tables ?? [])) {
|
|
2350
|
-
allTables.add(table.name);
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
// Find table references (per-file, uses allTables for short-id false-positive filtering)
|
|
2354
|
-
const { references: tableRefs, unresolved } = findTableReferences(ast, adapter, sourceCode, { config: schemaConfig, provenanceContext, allTables });
|
|
2355
|
-
// Record schema usage → emit as indexFacts via the shared instance
|
|
2356
|
-
a.recordTableUsage(ast, adapter, context.filePath, tableRefs, sourceCode);
|
|
2357
|
-
const pending = a.getPendingSchemaRecords();
|
|
2358
|
-
// Emit clear-by-file + per-usage index facts
|
|
2359
|
-
if (pending.clearFiles.length > 0) {
|
|
2360
|
-
for (const filePath of pending.clearFiles) {
|
|
2361
|
-
indexFacts.push({ table: 'schema_usage', data: { _action: 'clear-by-file', file_path: filePath } });
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2364
|
-
for (const usage of pending.usages) {
|
|
2365
|
-
indexFacts.push({
|
|
2366
|
-
table: 'schema_usage',
|
|
2367
|
-
data: {
|
|
2368
|
-
file_path: usage.filePath,
|
|
2369
|
-
table_name: usage.tableName,
|
|
2370
|
-
function_name: usage.functionName,
|
|
2371
|
-
function_start_line: usage.functionStartLine,
|
|
2372
|
-
function_start_column: usage.functionStartColumn,
|
|
2373
|
-
usage_type: usage.usageType,
|
|
2374
|
-
line: usage.line,
|
|
2375
|
-
column: usage.column,
|
|
2376
|
-
raw_query: usage.rawQuery,
|
|
2377
|
-
origin: usage.origin ?? null,
|
|
2378
|
-
},
|
|
2379
|
-
});
|
|
2380
|
-
}
|
|
2381
|
-
// Check naming conventions
|
|
2382
|
-
if (schemaConfig.checkNamingConventions !== false) {
|
|
2383
|
-
violations.push(...checkNamingConventions(tableRefs, context.filePath));
|
|
2384
|
-
}
|
|
2385
|
-
// Spec 58 R1 — DB-call SQL held in an unresolvable identifier is a coverage
|
|
2386
|
-
// diagnostic (the analyzer can't see the SQL), not a finding (the code isn't wrong).
|
|
2387
|
-
if (schemaConfig.reportUnresolvedQueries !== false) {
|
|
2388
|
-
diagnostics.push(...checkUnresolvedQueries(unresolved, context.filePath));
|
|
2389
|
-
}
|
|
2390
|
-
// Check query patterns
|
|
2391
|
-
if (schemaConfig.validateQueryPatterns !== false) {
|
|
2392
|
-
violations.push(...checkQueryPatterns(ast, adapter, sourceCode, schemaConfig));
|
|
2393
|
-
}
|
|
2394
|
-
// Check SQL injection
|
|
2395
|
-
violations.push(...checkSQLInjection(ast, adapter, sourceCode));
|
|
2396
|
-
// Emit facts for the Stage 3 reducer. Fluent-builder references
|
|
2397
|
-
// (`origin: 'query-builder'`) are excluded from the unknown-table check:
|
|
2398
|
-
// a scratch/test table name carried by `db('t').select(...)` is not a
|
|
2399
|
-
// schema catalog miss, and feeding it in flips the 10:1 fail-open ratio.
|
|
2400
|
-
const fileFacts = {
|
|
2401
|
-
tableRefs: tableRefs
|
|
2402
|
-
.filter((r) => r.origin !== 'query-builder')
|
|
2403
|
-
.map((r) => ({
|
|
2404
|
-
table: r.table,
|
|
2405
|
-
type: r.type,
|
|
2406
|
-
line: r.location.line,
|
|
2407
|
-
column: r.location.column,
|
|
2408
|
-
context: r.context,
|
|
2409
|
-
})),
|
|
2410
|
-
ormTables,
|
|
2411
|
-
tableProvenance: tableProvenances,
|
|
2412
|
-
};
|
|
2413
|
-
if (doDDL.length > 0) {
|
|
2414
|
-
fileFacts.ddlOps = parseMigrationOps(doDDLSql);
|
|
2415
|
-
if (doDDLColumns.length > 0)
|
|
2416
|
-
fileFacts.ddlColumns = doDDLColumns;
|
|
2417
|
-
if (Object.keys(doDDLTableColumns).length > 0)
|
|
2418
|
-
fileFacts.ddlTableColumns = doDDLTableColumns;
|
|
2419
|
-
}
|
|
2420
|
-
return {
|
|
2421
|
-
violations,
|
|
2422
|
-
facts: { [context.filePath]: fileFacts },
|
|
2423
|
-
indexFacts: indexFacts.length > 0 ? indexFacts : undefined,
|
|
2424
|
-
...(diagnostics.length > 0 && { diagnostics }),
|
|
2425
|
-
};
|
|
2426
|
-
},
|
|
2537
|
+
visit: runSchemaCodeVisit,
|
|
2427
2538
|
defaultConfig: {},
|
|
2428
2539
|
description: 'Per-file schema analysis: naming conventions, query patterns, SQL injection, table references',
|
|
2429
2540
|
category: 'database',
|
|
@@ -2431,6 +2542,8 @@ export function createSchemaCodeVisitor() {
|
|
|
2431
2542
|
}
|
|
2432
2543
|
/**
|
|
2433
2544
|
* Schema Prisma visitor (.prisma files) — extracts model names for known-table catalog.
|
|
2545
|
+
*
|
|
2546
|
+
* @returns The schema-prisma stage-2 visitor.
|
|
2434
2547
|
*/
|
|
2435
2548
|
export function createSchemaPrismaVisitor() {
|
|
2436
2549
|
return {
|
|
@@ -2473,6 +2586,8 @@ export function createSchemaPrismaVisitor() {
|
|
|
2473
2586
|
}
|
|
2474
2587
|
/**
|
|
2475
2588
|
* Schema JSON visitor (.json files) — parses JSON for the Stage 3 reducer to validate.
|
|
2589
|
+
*
|
|
2590
|
+
* @returns The schema-json stage-2 visitor.
|
|
2476
2591
|
*/
|
|
2477
2592
|
export function createSchemaJsonVisitor() {
|
|
2478
2593
|
return {
|
|
@@ -2494,11 +2609,329 @@ export function createSchemaJsonVisitor() {
|
|
|
2494
2609
|
category: 'database',
|
|
2495
2610
|
};
|
|
2496
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
|
+
}
|
|
2497
2928
|
/**
|
|
2498
2929
|
* Schema Stage 3 reducer — cross-file unknown-table detection and JSON schema validation.
|
|
2499
2930
|
*
|
|
2500
2931
|
* Consumes facts from schema-sql, schema-code, schema-prisma, and schema-json visitors.
|
|
2501
2932
|
* Builds the complete known-tables catalog and checks all table references against it.
|
|
2933
|
+
*
|
|
2934
|
+
* @returns The schema stage-3 reducer.
|
|
2502
2935
|
*/
|
|
2503
2936
|
export function createSchemaReducer() {
|
|
2504
2937
|
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js').then((m) => ({
|
|
@@ -2515,296 +2948,20 @@ export function createSchemaReducer() {
|
|
|
2515
2948
|
const { analyzer: a, analyzeJsonSchemas } = await getAnalyzer();
|
|
2516
2949
|
const violations = [];
|
|
2517
2950
|
const schemaConfig = (context.config ?? {});
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
const knownTables = new Set();
|
|
2533
|
-
const tableProvenances = new Map(); // table name → origins
|
|
2534
|
-
// 1a. SQL DDL replay — collect all DDL facts, sort by numeric prefix, replay
|
|
2535
|
-
const sqlFiles = [];
|
|
2536
|
-
for (const [filePath, fact] of perFile()) {
|
|
2537
|
-
if ('ddlOps' in fact) {
|
|
2538
|
-
sqlFiles.push({ filePath, ops: fact.ddlOps });
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
|
-
// Sort by numeric prefix in basename: "009_something" < "0010_rename"
|
|
2542
|
-
// Falls back to localeCompare for non-numeric-prefixed names.
|
|
2543
|
-
const numericPrefix = (p) => {
|
|
2544
|
-
const base = p.split('/').pop() ?? p;
|
|
2545
|
-
const m = base.match(/^(\d+)/);
|
|
2546
|
-
return m ? parseInt(m[1], 10) : 0;
|
|
2547
|
-
};
|
|
2548
|
-
sqlFiles.sort((a, b) => {
|
|
2549
|
-
const na = numericPrefix(a.filePath);
|
|
2550
|
-
const nb = numericPrefix(b.filePath);
|
|
2551
|
-
if (na !== nb)
|
|
2552
|
-
return na - nb;
|
|
2553
|
-
return a.filePath.localeCompare(b.filePath);
|
|
2554
|
-
});
|
|
2555
|
-
// Drop provenance — the last migration that removed a table, and the tables
|
|
2556
|
-
// that same migration introduced. Lets unknown-table detection distinguish
|
|
2557
|
-
// "never existed" from "existed and was dropped": the latter is a stale code
|
|
2558
|
-
// reference, not a typo. `dropProvenance` ends up holding exactly the tables
|
|
2559
|
-
// dropped and not subsequently recreated (CREATE clears the entry).
|
|
2560
|
-
const dropProvenance = new Map();
|
|
2561
|
-
for (const sqlFile of sqlFiles) {
|
|
2562
|
-
const before = new Set(knownTables);
|
|
2563
|
-
applyMigrationOps(sqlFile.ops, knownTables);
|
|
2564
|
-
// Record provenance for newly created tables
|
|
2565
|
-
for (const table of knownTables) {
|
|
2566
|
-
if (!before.has(table)) {
|
|
2567
|
-
const sources = tableProvenances.get(table) ?? [];
|
|
2568
|
-
sources.push({ table, tier: 'sql-migration', sourceFile: sqlFile.filePath, description: 'SQL migration' });
|
|
2569
|
-
tableProvenances.set(table, sources);
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
// Genuinely-new tables introduced by this migration (excludes rename/rebuild
|
|
2573
|
-
// churn like `ALTER … RENAME TO x_old` + re-CREATE of the same name).
|
|
2574
|
-
const createdHere = [];
|
|
2575
|
-
const createdTables = new Set();
|
|
2576
|
-
const droppedTables = new Set();
|
|
2577
|
-
for (const op of sqlFile.ops) {
|
|
2578
|
-
const t = stripIdentifier(op.table);
|
|
2579
|
-
if (op.op === 'CREATE') {
|
|
2580
|
-
createdTables.add(t);
|
|
2581
|
-
if (!before.has(t))
|
|
2582
|
-
createdHere.push(t);
|
|
2583
|
-
}
|
|
2584
|
-
else if (op.op === 'DROP') {
|
|
2585
|
-
droppedTables.add(t);
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
for (const op of sqlFile.ops) {
|
|
2589
|
-
const t = stripIdentifier(op.table);
|
|
2590
|
-
if (op.op === 'DROP') {
|
|
2591
|
-
dropProvenance.set(t, { migrationFile: sqlFile.filePath, createdInSameMigration: createdHere });
|
|
2592
|
-
}
|
|
2593
|
-
else if (op.op === 'CREATE') {
|
|
2594
|
-
dropProvenance.delete(t);
|
|
2595
|
-
}
|
|
2596
|
-
}
|
|
2597
|
-
// A table created and dropped within this same file is a self-contained
|
|
2598
|
-
// fixture — a scratch table a test or script creates, uses, then tears
|
|
2599
|
-
// down. Its DROP is teardown, not a migration, so it must neither surface
|
|
2600
|
-
// as a stale-table-reference nor leave the name unknown: keep it known and
|
|
2601
|
-
// clear the drop provenance this file recorded for it. (A genuine dropped
|
|
2602
|
-
// table stays stale because its DROP lives in a migration that never also
|
|
2603
|
-
// CREATEs it — e.g. recall's `generation_queue`, dropped in 0198.)
|
|
2604
|
-
for (const t of createdTables) {
|
|
2605
|
-
if (droppedTables.has(t)) {
|
|
2606
|
-
knownTables.add(t);
|
|
2607
|
-
if (dropProvenance.get(t)?.migrationFile === sqlFile.filePath) {
|
|
2608
|
-
dropProvenance.delete(t);
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
2613
|
-
// 1b. ORM tables from code files — with provenance from the table-source registry
|
|
2614
|
-
for (const [filePath, fact] of perFile()) {
|
|
2615
|
-
const ormTables = fact.ormTables ?? [];
|
|
2616
|
-
for (const t of ormTables)
|
|
2617
|
-
knownTables.add(t);
|
|
2618
|
-
const provenances = fact.tableProvenance ?? [];
|
|
2619
|
-
for (const p of provenances) {
|
|
2620
|
-
const sources = tableProvenances.get(p.table) ?? [];
|
|
2621
|
-
sources.push(p.source);
|
|
2622
|
-
tableProvenances.set(p.table, sources);
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2625
|
-
// 1c. Prisma models
|
|
2626
|
-
for (const [filePath, fact] of perFile()) {
|
|
2627
|
-
const prismaModels = fact.prismaModels ?? [];
|
|
2628
|
-
for (const m of prismaModels) {
|
|
2629
|
-
knownTables.add(m);
|
|
2630
|
-
const sources = tableProvenances.get(m) ?? [];
|
|
2631
|
-
sources.push({ table: m, tier: 'prisma-model', sourceFile: filePath, description: 'Prisma model' });
|
|
2632
|
-
tableProvenances.set(m, sources);
|
|
2633
|
-
}
|
|
2634
|
-
}
|
|
2635
|
-
// 1d. Aggregate DDL-declared columns (Spec 39 — derived applicability).
|
|
2636
|
-
// The schema-sql and schema-code visitors emit per-file `ddlColumns`;
|
|
2637
|
-
// the reducer folds them into a single case-insensitive, deduplicated
|
|
2638
|
-
// set so downstream applicability predicates can ask "does ANY table
|
|
2639
|
-
// carry a tenant-scoping column?" without per-table column lists.
|
|
2640
|
-
//
|
|
2641
|
-
// Spec 62 Amendment B — additionally fold the per-table
|
|
2642
|
-
// `ddlTableColumns` into a corpus-wide `tableColumns` (Record<table,
|
|
2643
|
-
// string[]>) so the Stage-4 missing-org-filter reducer can answer the
|
|
2644
|
-
// per-query question "does THIS table carry a tenant column?" that the
|
|
2645
|
-
// flat set cannot.
|
|
2646
|
-
const ddlColumns = new Set();
|
|
2647
|
-
const tableColumnsMap = new Map();
|
|
2648
|
-
for (const [filePath, fact] of perFile()) {
|
|
2649
|
-
const cols = fact.ddlColumns ?? [];
|
|
2650
|
-
for (const c of cols)
|
|
2651
|
-
ddlColumns.add(String(c).toLowerCase());
|
|
2652
|
-
const perTable = fact.ddlTableColumns ?? {};
|
|
2653
|
-
for (const [table, tableCols] of Object.entries(perTable)) {
|
|
2654
|
-
let set = tableColumnsMap.get(table);
|
|
2655
|
-
if (!set) {
|
|
2656
|
-
set = new Set();
|
|
2657
|
-
tableColumnsMap.set(table, set);
|
|
2658
|
-
}
|
|
2659
|
-
for (const c of tableCols)
|
|
2660
|
-
set.add(String(c).toLowerCase());
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
const tableColumns = {};
|
|
2664
|
-
for (const [table, set] of tableColumnsMap)
|
|
2665
|
-
tableColumns[table] = [...set];
|
|
2666
|
-
// ── 2. Unknown-table detection ────────────────────────────────────────
|
|
2667
|
-
//
|
|
2668
|
-
// Fail-open guardrail: we can only accuse when the table catalog is
|
|
2669
|
-
// built from authoritative sources. Empty catalog → cannot accuse.
|
|
2670
|
-
//
|
|
2671
|
-
// Authoritative sources (independent of query sites, not circular):
|
|
2672
|
-
// • SQL migration files via schema-sql (step 1a)
|
|
2673
|
-
// • ORM model definitions via schema-code (step 1b)
|
|
2674
|
-
// • Prisma schemas via schema-prisma (step 1c)
|
|
2675
|
-
//
|
|
2676
|
-
// External config tables (schemas from CodeIndexDB, wrangler.toml
|
|
2677
|
-
// pre-discovered tables) are added as a bonus but are not required.
|
|
2678
|
-
//
|
|
2679
|
-
// NOT authoritative: tables inferred from query-text alone. Those
|
|
2680
|
-
// never enter the catalog (table refs are for checking, not building).
|
|
2681
|
-
// Merge external tables from config (bonus, not required).
|
|
2682
|
-
const externalKnownTables = schemaConfig.knownTables ?? [];
|
|
2683
|
-
for (const t of externalKnownTables) {
|
|
2684
|
-
knownTables.add(t);
|
|
2685
|
-
const sources = tableProvenances.get(t) ?? [];
|
|
2686
|
-
sources.push({ table: t, tier: 'external-config', description: 'External configuration' });
|
|
2687
|
-
tableProvenances.set(t, sources);
|
|
2688
|
-
}
|
|
2689
|
-
// Also read the documented schemas config (structured {name, tables} objects).
|
|
2690
|
-
// The standalone UniversalSchemaAnalyzer.analyze() path reads schemas; the
|
|
2691
|
-
// pipeline reducer must read it too, otherwise configuring only schemas
|
|
2692
|
-
// silently triggers fail-open (knownTables.size === 0).
|
|
2693
|
-
const externalSchemas = schemaConfig.schemas ?? [];
|
|
2694
|
-
for (const schema of externalSchemas) {
|
|
2695
|
-
for (const table of schema.tables) {
|
|
2696
|
-
knownTables.add(table.name);
|
|
2697
|
-
const sources = tableProvenances.get(table.name) ?? [];
|
|
2698
|
-
sources.push({ table: table.name, tier: 'external-config', description: `Schema: ${schema.name}` });
|
|
2699
|
-
tableProvenances.set(table.name, sources);
|
|
2700
|
-
}
|
|
2701
|
-
}
|
|
2702
|
-
if (knownTables.size > 0) {
|
|
2703
|
-
// Collect all table references across all files
|
|
2704
|
-
const allTableRefs = [];
|
|
2705
|
-
for (const [filePath, fact] of perFile()) {
|
|
2706
|
-
const refs = fact.tableRefs ?? [];
|
|
2707
|
-
for (const ref of refs) {
|
|
2708
|
-
allTableRefs.push({ file: filePath, ...ref });
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
// 10:1 fail-open ratio guard
|
|
2712
|
-
const unknownRefs = allTableRefs.filter(ref => !knownTables.has(ref.table));
|
|
2713
|
-
if (unknownRefs.length / Math.max(knownTables.size, 1) <= 10) {
|
|
2714
|
-
for (const ref of unknownRefs) {
|
|
2715
|
-
// The table existed and was dropped in a migration — a stale code
|
|
2716
|
-
// reference, not a typo. Name the dropping migration and the tables
|
|
2717
|
-
// that migration introduced (evidence, not proof of a successor).
|
|
2718
|
-
const drop = dropProvenance.get(ref.table);
|
|
2719
|
-
if (drop) {
|
|
2720
|
-
const migrationName = drop.migrationFile.split('/').pop() ?? drop.migrationFile;
|
|
2721
|
-
const created = drop.createdInSameMigration;
|
|
2722
|
-
const msg = created.length > 0
|
|
2723
|
-
? `${ref.table} was dropped in ${migrationName}; that migration creates ${joinEnglish(created)}.`
|
|
2724
|
-
: `${ref.table} was dropped in ${migrationName} and was not recreated.`;
|
|
2725
|
-
violations.push({
|
|
2726
|
-
file: ref.file,
|
|
2727
|
-
line: ref.line,
|
|
2728
|
-
column: ref.column,
|
|
2729
|
-
severity: 'critical',
|
|
2730
|
-
message: msg,
|
|
2731
|
-
rule: 'stale-table-reference',
|
|
2732
|
-
analyzer: 'schema',
|
|
2733
|
-
symbol: ref.table,
|
|
2734
|
-
resolution: {
|
|
2735
|
-
action: 'update-stale-reference',
|
|
2736
|
-
// The tables `created` are evidence the migration replaced the
|
|
2737
|
-
// dropped table, not proof of a drop-in successor — schemas can
|
|
2738
|
-
// be split or changed incompatibly, in which case the file must
|
|
2739
|
-
// be removed rather than updated. The summary names them as
|
|
2740
|
-
// context and leaves update-vs-remove to the reviewer.
|
|
2741
|
-
summary: created.length > 0
|
|
2742
|
-
? `The table '${ref.table}' was dropped in ${migrationName}. That migration introduces ${joinEnglish(created)} — review this reference and update or remove it.`
|
|
2743
|
-
: `The table '${ref.table}' was dropped in ${migrationName} and was not recreated — update or remove this reference.`,
|
|
2744
|
-
symbols: created.length > 0 ? created : [ref.table],
|
|
2745
|
-
files: [ref.file],
|
|
2746
|
-
lines: [ref.line],
|
|
2747
|
-
},
|
|
2748
|
-
});
|
|
2749
|
-
continue;
|
|
2750
|
-
}
|
|
2751
|
-
const suggestions = getNearestTableSuggestions(ref.table, knownTables, 2);
|
|
2752
|
-
const msg = suggestions.length > 0
|
|
2753
|
-
? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`
|
|
2754
|
-
: `Reference to unknown table '${ref.table}' (${ref.type})`;
|
|
2755
|
-
const suggestionNames = suggestions.map((s) => s.replace(/^'|'$/g, ''));
|
|
2756
|
-
violations.push({
|
|
2757
|
-
file: ref.file,
|
|
2758
|
-
line: ref.line,
|
|
2759
|
-
column: ref.column,
|
|
2760
|
-
severity: 'critical',
|
|
2761
|
-
message: msg,
|
|
2762
|
-
rule: 'unknown-table',
|
|
2763
|
-
analyzer: 'schema',
|
|
2764
|
-
symbol: ref.table,
|
|
2765
|
-
resolution: {
|
|
2766
|
-
action: suggestionNames.length > 0 ? 'use-known-table' : 'register-or-fix-table',
|
|
2767
|
-
summary: suggestionNames.length > 0
|
|
2768
|
-
? `Rename the table reference '${ref.table}' to the nearest known table: ${suggestions.join(', ')}.`
|
|
2769
|
-
: `The table '${ref.table}' is not in the known catalog — register it, or fix the reference to a known table.`,
|
|
2770
|
-
symbols: suggestionNames.length > 0 ? suggestionNames : [ref.table],
|
|
2771
|
-
files: [ref.file],
|
|
2772
|
-
lines: [ref.line],
|
|
2773
|
-
},
|
|
2774
|
-
});
|
|
2775
|
-
}
|
|
2776
|
-
}
|
|
2777
|
-
}
|
|
2778
|
-
// ── 3. JSON schema validation ──────────────────────────────────────────
|
|
2779
|
-
if (schemaConfig.validateJsonSchemas !== false) {
|
|
2780
|
-
try {
|
|
2781
|
-
// Collect JSON file paths from the lightweight visitor markers, then
|
|
2782
|
-
// parse each on demand so parsed objects never survive past this loop.
|
|
2783
|
-
const jsonFiles = [];
|
|
2784
|
-
for (const [filePath, fact] of perFile()) {
|
|
2785
|
-
if ('isJson' in fact) {
|
|
2786
|
-
jsonFiles.push(filePath);
|
|
2787
|
-
}
|
|
2788
|
-
}
|
|
2789
|
-
const readJson = (filePath) => {
|
|
2790
|
-
const raw = context.readSource?.(filePath);
|
|
2791
|
-
if (raw === undefined)
|
|
2792
|
-
return null;
|
|
2793
|
-
try {
|
|
2794
|
-
const parsed = JSON.parse(raw);
|
|
2795
|
-
return parsed !== null && typeof parsed === 'object' ? parsed : null;
|
|
2796
|
-
}
|
|
2797
|
-
catch {
|
|
2798
|
-
return null;
|
|
2799
|
-
}
|
|
2800
|
-
};
|
|
2801
|
-
const jsonResult = analyzeJsonSchemas(jsonFiles, readJson, schemaConfig);
|
|
2802
|
-
violations.push(...(jsonResult?.violations ?? []));
|
|
2803
|
-
}
|
|
2804
|
-
catch (e) {
|
|
2805
|
-
// JSON schema validation is best-effort (non-fatal)
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
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
|
+
}));
|
|
2808
2965
|
// ── 4. Build table catalog for metadata ───────────────────────────────
|
|
2809
2966
|
const catalogEntries = [];
|
|
2810
2967
|
for (const [table, sources] of tableProvenances) {
|
|
@@ -2813,7 +2970,7 @@ export function createSchemaReducer() {
|
|
|
2813
2970
|
return {
|
|
2814
2971
|
violations,
|
|
2815
2972
|
facts: { tableCatalog: catalogEntries, ddlColumns: [...ddlColumns].sort(), tableColumns },
|
|
2816
|
-
factsConsumed: [...
|
|
2973
|
+
factsConsumed: [...iteratePerFileFacts(allFacts)].length,
|
|
2817
2974
|
};
|
|
2818
2975
|
},
|
|
2819
2976
|
defaultConfig: {},
|