code-auditor-mcp 4.1.1 → 5.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +381 -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 +427 -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 +348 -0
- package/dist/analyzers/receiverRoot.d.ts.map +1 -0
- package/dist/analyzers/receiverRoot.js +1032 -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 +241 -0
- package/dist/analyzers/tsEcosystem.js.map +1 -0
- package/dist/analyzers/tsExpressionDescriptor.d.ts +156 -0
- package/dist/analyzers/tsExpressionDescriptor.d.ts.map +1 -0
- package/dist/analyzers/tsExpressionDescriptor.js +374 -0
- package/dist/analyzers/tsExpressionDescriptor.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +6 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +107 -44
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +23 -35
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +287 -49
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +2130 -512
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +2 -18
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +0 -33
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +20 -143
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +4 -3
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +36 -13
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.js +6 -0
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts +15 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js +25 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +21 -32
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +166 -196
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/functionConcerns.d.ts +3 -0
- package/dist/analyzers/universal/functionConcerns.d.ts.map +1 -1
- package/dist/analyzers/universal/functionConcerns.js +4 -1
- package/dist/analyzers/universal/functionConcerns.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +310 -69
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +709 -383
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/config.d.ts +3 -14
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/config.js +3 -16
- package/dist/analyzers/universal/schema/config.js.map +1 -1
- package/dist/analyzers/universal/schema/discovery.d.ts +22 -1
- package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/discovery.js +36 -20
- package/dist/analyzers/universal/schema/discovery.js.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.js +13 -3
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
- package/dist/analyzers/universal/schema/migrations.d.ts +187 -27
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/migrations.js +443 -186
- package/dist/analyzers/universal/schema/migrations.js.map +1 -1
- package/dist/analyzers/universal/schema/types.d.ts +17 -3
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/violations.js +1 -1
- package/dist/analyzers/universal/schema/violations.js.map +1 -1
- package/dist/analyzers/universal/styleScale.d.ts +52 -0
- package/dist/analyzers/universal/styleScale.d.ts.map +1 -0
- package/dist/analyzers/universal/styleScale.js +95 -0
- package/dist/analyzers/universal/styleScale.js.map +1 -0
- package/dist/applyDataDirEnv.js +1 -1
- package/dist/applyDataDirEnv.js.map +1 -1
- package/dist/auditRouter.d.ts +26 -30
- package/dist/auditRouter.d.ts.map +1 -1
- package/dist/auditRouter.js +373 -143
- package/dist/auditRouter.js.map +1 -1
- package/dist/auditRunner.d.ts +66 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +1544 -1034
- 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 +983 -821
- package/dist/cli.js.map +1 -1
- package/dist/codeIndex/analyzerConfig.d.ts +60 -0
- package/dist/codeIndex/analyzerConfig.d.ts.map +1 -0
- package/dist/codeIndex/analyzerConfig.js +98 -0
- package/dist/codeIndex/analyzerConfig.js.map +1 -0
- package/dist/codeIndex/auditResults.d.ts +39 -0
- package/dist/codeIndex/auditResults.d.ts.map +1 -0
- package/dist/codeIndex/auditResults.js +99 -0
- package/dist/codeIndex/auditResults.js.map +1 -0
- package/dist/codeIndex/codeMap.d.ts +59 -0
- package/dist/codeIndex/codeMap.d.ts.map +1 -0
- package/dist/codeIndex/codeMap.js +72 -0
- package/dist/codeIndex/codeMap.js.map +1 -0
- package/dist/codeIndex/conventions.d.ts +33 -0
- package/dist/codeIndex/conventions.d.ts.map +1 -0
- package/dist/codeIndex/conventions.js +84 -0
- package/dist/codeIndex/conventions.js.map +1 -0
- package/dist/codeIndex/coverage.d.ts +78 -0
- package/dist/codeIndex/coverage.d.ts.map +1 -0
- package/dist/codeIndex/coverage.js +164 -0
- package/dist/codeIndex/coverage.js.map +1 -0
- package/dist/codeIndex/dependencyGraph.d.ts +97 -0
- package/dist/codeIndex/dependencyGraph.d.ts.map +1 -0
- package/dist/codeIndex/dependencyGraph.js +241 -0
- package/dist/codeIndex/dependencyGraph.js.map +1 -0
- package/dist/codeIndex/functionIndex.d.ts +111 -0
- package/dist/codeIndex/functionIndex.d.ts.map +1 -0
- package/dist/codeIndex/functionIndex.js +306 -0
- package/dist/codeIndex/functionIndex.js.map +1 -0
- package/dist/codeIndex/inferredReceivers.d.ts +32 -0
- package/dist/codeIndex/inferredReceivers.d.ts.map +1 -0
- package/dist/codeIndex/inferredReceivers.js +41 -0
- package/dist/codeIndex/inferredReceivers.js.map +1 -0
- package/dist/codeIndex/meta.d.ts +30 -0
- package/dist/codeIndex/meta.d.ts.map +1 -0
- package/dist/codeIndex/meta.js +37 -0
- package/dist/codeIndex/meta.js.map +1 -0
- package/dist/codeIndex/migrations.d.ts +96 -0
- package/dist/codeIndex/migrations.d.ts.map +1 -0
- package/dist/codeIndex/migrations.js +1302 -0
- package/dist/codeIndex/migrations.js.map +1 -0
- package/dist/codeIndex/projectTasks.d.ts +54 -0
- package/dist/codeIndex/projectTasks.d.ts.map +1 -0
- package/dist/codeIndex/projectTasks.js +81 -0
- package/dist/codeIndex/projectTasks.js.map +1 -0
- package/dist/codeIndex/provenance.d.ts +47 -0
- package/dist/codeIndex/provenance.d.ts.map +1 -0
- package/dist/codeIndex/provenance.js +48 -0
- package/dist/codeIndex/provenance.js.map +1 -0
- package/dist/codeIndex/rawSql.d.ts +54 -0
- package/dist/codeIndex/rawSql.d.ts.map +1 -0
- package/dist/codeIndex/rawSql.js +71 -0
- package/dist/codeIndex/rawSql.js.map +1 -0
- package/dist/codeIndex/schema.d.ts +99 -0
- package/dist/codeIndex/schema.d.ts.map +1 -0
- package/dist/codeIndex/schema.js +174 -0
- package/dist/codeIndex/schema.js.map +1 -0
- package/dist/codeIndex/search.d.ts +40 -0
- package/dist/codeIndex/search.d.ts.map +1 -0
- package/dist/codeIndex/search.js +316 -0
- package/dist/codeIndex/search.js.map +1 -0
- package/dist/codeIndex/shared.d.ts +27 -0
- package/dist/codeIndex/shared.d.ts.map +1 -0
- package/dist/codeIndex/shared.js +46 -0
- package/dist/codeIndex/shared.js.map +1 -0
- package/dist/codeIndex/sqliteCollection.d.ts +81 -0
- package/dist/codeIndex/sqliteCollection.d.ts.map +1 -0
- package/dist/codeIndex/sqliteCollection.js +250 -0
- package/dist/codeIndex/sqliteCollection.js.map +1 -0
- package/dist/codeIndex/whitelist.d.ts +59 -0
- package/dist/codeIndex/whitelist.d.ts.map +1 -0
- package/dist/codeIndex/whitelist.js +188 -0
- package/dist/codeIndex/whitelist.js.map +1 -0
- package/dist/codeIndexDB.d.ts +132 -271
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +365 -2729
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/codeIndexService.d.ts +87 -12
- package/dist/codeIndexService.d.ts.map +1 -1
- package/dist/codeIndexService.js +110 -34
- package/dist/codeIndexService.js.map +1 -1
- package/dist/componentScanner.d.ts +35 -0
- package/dist/componentScanner.d.ts.map +1 -1
- package/dist/componentScanner.js +114 -80
- package/dist/componentScanner.js.map +1 -1
- package/dist/config/configGeneratePath.d.ts +4 -0
- package/dist/config/configGeneratePath.d.ts.map +1 -1
- package/dist/config/configGeneratePath.js +4 -0
- package/dist/config/configGeneratePath.js.map +1 -1
- package/dist/config/configLoader.d.ts +15 -0
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +15 -16
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +11 -7
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +16 -14
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +17 -2
- package/dist/config/effectiveConfig.d.ts.map +1 -1
- package/dist/config/effectiveConfig.js +32 -25
- package/dist/config/effectiveConfig.js.map +1 -1
- package/dist/config/lintConfigReader.d.ts +6 -0
- package/dist/config/lintConfigReader.d.ts.map +1 -1
- package/dist/config/lintConfigReader.js +6 -0
- package/dist/config/lintConfigReader.js.map +1 -1
- package/dist/config/staticObjectExtract.d.ts +4 -0
- package/dist/config/staticObjectExtract.d.ts.map +1 -1
- package/dist/config/staticObjectExtract.js +7 -3
- package/dist/config/staticObjectExtract.js.map +1 -1
- package/dist/config/thresholdRationales.d.ts +2 -0
- package/dist/config/thresholdRationales.d.ts.map +1 -1
- package/dist/config/thresholdRationales.js +5 -2
- package/dist/config/thresholdRationales.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/conventions/conventionMiner.d.ts +105 -2
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +334 -259
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/coverage/coverageService.js +4 -4
- package/dist/coverage/coverageService.js.map +1 -1
- package/dist/daemon/args.d.ts +3 -0
- package/dist/daemon/args.d.ts.map +1 -1
- package/dist/daemon/args.js +3 -0
- package/dist/daemon/args.js.map +1 -1
- package/dist/daemon/core.d.ts +33 -27
- package/dist/daemon/core.d.ts.map +1 -1
- package/dist/daemon/core.js +198 -162
- package/dist/daemon/core.js.map +1 -1
- package/dist/daemon/lspServer.d.ts +8 -0
- package/dist/daemon/lspServer.d.ts.map +1 -1
- package/dist/daemon/lspServer.js +8 -0
- package/dist/daemon/lspServer.js.map +1 -1
- package/dist/daemon/main.d.ts +2 -0
- package/dist/daemon/main.d.ts.map +1 -1
- package/dist/daemon/main.js +22 -4
- package/dist/daemon/main.js.map +1 -1
- package/dist/daemon/resolve.d.ts +18 -3
- package/dist/daemon/resolve.d.ts.map +1 -1
- package/dist/daemon/resolve.js +25 -4
- package/dist/daemon/resolve.js.map +1 -1
- package/dist/daemon/socketClient.d.ts +10 -1
- package/dist/daemon/socketClient.d.ts.map +1 -1
- package/dist/daemon/socketClient.js +10 -1
- package/dist/daemon/socketClient.js.map +1 -1
- package/dist/daemon/socketServer.d.ts +13 -0
- package/dist/daemon/socketServer.d.ts.map +1 -1
- package/dist/daemon/socketServer.js +13 -0
- package/dist/daemon/socketServer.js.map +1 -1
- package/dist/dataPaths.d.ts +17 -8
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +16 -44
- package/dist/dataPaths.js.map +1 -1
- package/dist/dismissals.d.ts +22 -1
- package/dist/dismissals.d.ts.map +1 -1
- package/dist/dismissals.js +22 -1
- package/dist/dismissals.js.map +1 -1
- package/dist/enforcement/gate.d.ts +1 -0
- package/dist/enforcement/gate.d.ts.map +1 -1
- package/dist/enforcement/gate.js +1 -0
- package/dist/enforcement/gate.js.map +1 -1
- package/dist/fingerprint.d.ts +6 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +46 -1
- package/dist/fingerprint.js.map +1 -1
- package/dist/functionScanner.d.ts +30 -1
- package/dist/functionScanner.d.ts.map +1 -1
- package/dist/functionScanner.js +258 -256
- package/dist/functionScanner.js.map +1 -1
- package/dist/generators/AWSQConfigGenerator.d.ts +23 -0
- package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
- package/dist/generators/AWSQConfigGenerator.js +23 -0
- package/dist/generators/AWSQConfigGenerator.js.map +1 -1
- package/dist/generators/BaseConfigGenerator.d.ts +7 -0
- package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.js +7 -0
- package/dist/generators/BaseConfigGenerator.js.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.d.ts +23 -0
- package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.js +23 -0
- package/dist/generators/ClaudeConfigGenerator.js.map +1 -1
- package/dist/generators/ClineConfigGenerator.d.ts +23 -0
- package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClineConfigGenerator.js +23 -0
- package/dist/generators/ClineConfigGenerator.js.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.d.ts +23 -0
- package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.js +23 -0
- package/dist/generators/CodeiumConfigGenerator.js.map +1 -1
- package/dist/generators/CodexConfigGenerator.d.ts +23 -0
- package/dist/generators/CodexConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodexConfigGenerator.js +23 -0
- package/dist/generators/CodexConfigGenerator.js.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.d.ts +9 -0
- package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.js +9 -0
- package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
- package/dist/generators/ContinueConfigGenerator.d.ts +23 -0
- package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ContinueConfigGenerator.js +23 -0
- package/dist/generators/ContinueConfigGenerator.js.map +1 -1
- package/dist/generators/CopilotConfigGenerator.d.ts +23 -0
- package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CopilotConfigGenerator.js +23 -0
- package/dist/generators/CopilotConfigGenerator.js.map +1 -1
- package/dist/generators/CursorConfigGenerator.d.ts +23 -0
- package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CursorConfigGenerator.js +23 -0
- package/dist/generators/CursorConfigGenerator.js.map +1 -1
- package/dist/generators/GeminiConfigGenerator.d.ts +23 -0
- package/dist/generators/GeminiConfigGenerator.d.ts.map +1 -1
- package/dist/generators/GeminiConfigGenerator.js +23 -0
- package/dist/generators/GeminiConfigGenerator.js.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.d.ts +23 -0
- package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.js +23 -0
- package/dist/generators/VSCodeConfigGenerator.js.map +1 -1
- package/dist/generators/ZCodeConfigGenerator.d.ts +23 -0
- package/dist/generators/ZCodeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ZCodeConfigGenerator.js +23 -0
- package/dist/generators/ZCodeConfigGenerator.js.map +1 -1
- package/dist/graph/blastRadius.d.ts.map +1 -1
- package/dist/graph/blastRadius.js +12 -7
- package/dist/graph/blastRadius.js.map +1 -1
- package/dist/graph/callGraph.d.ts +31 -0
- package/dist/graph/callGraph.d.ts.map +1 -1
- package/dist/graph/callGraph.js +62 -32
- package/dist/graph/callGraph.js.map +1 -1
- package/dist/graph/importClassification.d.ts +28 -0
- package/dist/graph/importClassification.d.ts.map +1 -1
- package/dist/graph/importClassification.js +57 -0
- package/dist/graph/importClassification.js.map +1 -1
- package/dist/graph/importGraph.d.ts +38 -0
- package/dist/graph/importGraph.d.ts.map +1 -1
- package/dist/graph/importGraph.js +38 -0
- package/dist/graph/importGraph.js.map +1 -1
- package/dist/graph/outputFormatter.d.ts +12 -4
- package/dist/graph/outputFormatter.d.ts.map +1 -1
- package/dist/graph/outputFormatter.js +12 -62
- package/dist/graph/outputFormatter.js.map +1 -1
- package/dist/graph/resolutionEnvironment.d.ts +61 -0
- package/dist/graph/resolutionEnvironment.d.ts.map +1 -0
- package/dist/graph/resolutionEnvironment.js +57 -0
- package/dist/graph/resolutionEnvironment.js.map +1 -0
- package/dist/hooks/codex.d.ts +8 -1
- package/dist/hooks/codex.d.ts.map +1 -1
- package/dist/hooks/codex.js +7 -0
- package/dist/hooks/codex.js.map +1 -1
- package/dist/hooks/core.d.ts +6 -0
- package/dist/hooks/core.d.ts.map +1 -1
- package/dist/hooks/core.js +6 -1
- package/dist/hooks/core.js.map +1 -1
- package/dist/hooks/cursor.d.ts +11 -1
- package/dist/hooks/cursor.d.ts.map +1 -1
- package/dist/hooks/cursor.js +10 -0
- package/dist/hooks/cursor.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/installConfig.d.ts +23 -5
- package/dist/installConfig.d.ts.map +1 -1
- package/dist/installConfig.js +23 -5
- package/dist/installConfig.js.map +1 -1
- package/dist/installer.d.ts +6 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +6 -0
- package/dist/installer.js.map +1 -1
- package/dist/invariants/ruleEngine.d.ts +3 -0
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +156 -119
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/invariants/ruleValidator.d.ts +3 -0
- package/dist/invariants/ruleValidator.d.ts.map +1 -1
- package/dist/invariants/ruleValidator.js +185 -178
- package/dist/invariants/ruleValidator.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +4 -1
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +8 -19
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +8 -0
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +55 -5
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +1 -1
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +4 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +37 -3
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +11 -14
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/go/analyzer-darwin-amd64 +0 -0
- package/dist/languages/go/analyzer-darwin-arm64 +0 -0
- package/dist/languages/go/analyzer-linux-amd64 +0 -0
- package/dist/languages/go/analyzer-linux-arm64 +0 -0
- package/dist/languages/go/analyzer-src/analyzer.go +2 -0
- package/dist/languages/go/analyzer-src/dataaccess.go +388 -0
- package/dist/languages/go/analyzer-src/types.go +8 -0
- package/dist/languages/go/analyzer-windows-amd64.exe +0 -0
- package/dist/languages/go/database-packages.json +8 -0
- package/dist/languages/go/goResolution.d.ts +229 -0
- package/dist/languages/go/goResolution.d.ts.map +1 -0
- package/dist/languages/go/goResolution.js +799 -0
- package/dist/languages/go/goResolution.js.map +1 -0
- package/dist/languages/go/stdlib-packages.json +353 -0
- package/dist/languages/go/tenantInputs.d.ts +37 -0
- package/dist/languages/go/tenantInputs.d.ts.map +1 -0
- package/dist/languages/go/tenantInputs.js +121 -0
- package/dist/languages/go/tenantInputs.js.map +1 -0
- package/dist/languages/index.d.ts +1 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +5 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/json/JsonAdapter.d.ts +177 -0
- package/dist/languages/json/JsonAdapter.d.ts.map +1 -0
- package/dist/languages/json/JsonAdapter.js +531 -0
- package/dist/languages/json/JsonAdapter.js.map +1 -0
- package/dist/languages/locationIndex.d.ts +41 -0
- package/dist/languages/locationIndex.d.ts.map +1 -0
- package/dist/languages/locationIndex.js +42 -0
- package/dist/languages/locationIndex.js.map +1 -0
- package/dist/languages/sql/dialectDetection.d.ts +57 -0
- package/dist/languages/sql/dialectDetection.d.ts.map +1 -0
- package/dist/languages/sql/dialectDetection.js +135 -0
- package/dist/languages/sql/dialectDetection.js.map +1 -0
- package/dist/languages/sql/sqlAst.d.ts +494 -0
- package/dist/languages/sql/sqlAst.d.ts.map +1 -0
- package/dist/languages/sql/sqlAst.js +1817 -0
- package/dist/languages/sql/sqlAst.js.map +1 -0
- package/dist/languages/testConventions.d.ts +12 -1
- package/dist/languages/testConventions.d.ts.map +1 -1
- package/dist/languages/testConventions.js +13 -1
- package/dist/languages/testConventions.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/converter.js +2 -2
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/languages/tree-sitter/parser.d.ts +3 -4
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +10 -4
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/tree-sitter/rawNode.d.ts +3 -0
- package/dist/languages/tree-sitter/rawNode.d.ts.map +1 -1
- package/dist/languages/tree-sitter/rawNode.js +3 -0
- package/dist/languages/tree-sitter/rawNode.js.map +1 -1
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +36 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +168 -45
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/languages/typescript/database-packages.json +19 -0
- package/dist/ledger.d.ts +118 -2
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +167 -45
- package/dist/ledger.js.map +1 -1
- package/dist/legacyIndexNotice.d.ts +32 -0
- package/dist/legacyIndexNotice.d.ts.map +1 -0
- package/dist/legacyIndexNotice.js +89 -0
- package/dist/legacyIndexNotice.js.map +1 -0
- package/dist/mcp-tools/discoveryQueries.d.ts +55 -0
- package/dist/mcp-tools/discoveryQueries.d.ts.map +1 -0
- package/dist/mcp-tools/discoveryQueries.js +157 -0
- package/dist/mcp-tools/discoveryQueries.js.map +1 -0
- package/dist/mcp-tools/projectTasks.d.ts +9 -123
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +180 -170
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp-tools/whitelistTools.d.ts +20 -0
- package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
- package/dist/mcp-tools/whitelistTools.js +20 -0
- package/dist/mcp-tools/whitelistTools.js.map +1 -1
- package/dist/mcp-tools/workflowGuide.d.ts +9 -0
- package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
- package/dist/mcp-tools/workflowGuide.js +13 -5
- package/dist/mcp-tools/workflowGuide.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts +78 -0
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js +203 -340
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp-ui-simple.d.ts +2 -0
- package/dist/mcp-ui-simple.d.ts.map +1 -1
- package/dist/mcp-ui-simple.js +12 -5
- package/dist/mcp-ui-simple.js.map +1 -1
- package/dist/mcp.d.ts +6 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +827 -850
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts +29 -33
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +102 -791
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/mcpAutoIndex.d.ts +5 -0
- package/dist/mcpAutoIndex.d.ts.map +1 -1
- package/dist/mcpAutoIndex.js +7 -2
- package/dist/mcpAutoIndex.js.map +1 -1
- package/dist/mcpDiagnostics.d.ts +7 -0
- package/dist/mcpDiagnostics.d.ts.map +1 -1
- package/dist/mcpDiagnostics.js +34 -9
- package/dist/mcpDiagnostics.js.map +1 -1
- package/dist/mcpToolErrors.d.ts +16 -0
- package/dist/mcpToolErrors.d.ts.map +1 -1
- package/dist/mcpToolErrors.js +16 -0
- package/dist/mcpToolErrors.js.map +1 -1
- package/dist/nextFile.d.ts +6 -0
- package/dist/nextFile.d.ts.map +1 -1
- package/dist/nextFile.js +6 -0
- package/dist/nextFile.js.map +1 -1
- package/dist/nextFileIncremental.d.ts +44 -4
- package/dist/nextFileIncremental.d.ts.map +1 -1
- package/dist/nextFileIncremental.js +47 -7
- package/dist/nextFileIncremental.js.map +1 -1
- package/dist/phase/batchFunctions.d.ts +38 -0
- package/dist/phase/batchFunctions.d.ts.map +1 -0
- package/dist/phase/batchFunctions.js +64 -0
- package/dist/phase/batchFunctions.js.map +1 -0
- package/dist/phase/checks.d.ts +39 -0
- package/dist/phase/checks.d.ts.map +1 -0
- package/dist/phase/checks.js +48 -0
- package/dist/phase/checks.js.map +1 -0
- package/dist/phase/codeBlocks.d.ts +25 -0
- package/dist/phase/codeBlocks.d.ts.map +1 -0
- package/dist/phase/codeBlocks.js +268 -0
- package/dist/phase/codeBlocks.js.map +1 -0
- package/dist/phase/colorMath.d.ts +41 -0
- package/dist/phase/colorMath.d.ts.map +1 -0
- package/dist/phase/colorMath.js +109 -0
- package/dist/phase/colorMath.js.map +1 -0
- package/dist/phase/config.d.ts +29 -0
- package/dist/phase/config.d.ts.map +1 -0
- package/dist/phase/config.js +54 -0
- package/dist/phase/config.js.map +1 -0
- package/dist/phase/consumed.d.ts +47 -0
- package/dist/phase/consumed.d.ts.map +1 -0
- package/dist/phase/consumed.js +22 -0
- package/dist/phase/consumed.js.map +1 -0
- package/dist/phase/conventionMining.d.ts +59 -0
- package/dist/phase/conventionMining.d.ts.map +1 -0
- package/dist/phase/conventionMining.js +152 -0
- package/dist/phase/conventionMining.js.map +1 -0
- package/dist/phase/coverage.d.ts +65 -0
- package/dist/phase/coverage.d.ts.map +1 -0
- package/dist/phase/coverage.js +121 -0
- package/dist/phase/coverage.js.map +1 -0
- package/dist/phase/dag.d.ts +49 -0
- package/dist/phase/dag.d.ts.map +1 -0
- package/dist/phase/dag.js +120 -0
- package/dist/phase/dag.js.map +1 -0
- package/dist/phase/dataAccessCalls.d.ts +33 -0
- package/dist/phase/dataAccessCalls.d.ts.map +1 -0
- package/dist/phase/dataAccessCalls.js +42 -0
- package/dist/phase/dataAccessCalls.js.map +1 -0
- package/dist/phase/dataAccessCallsCandidates.d.ts +25 -0
- package/dist/phase/dataAccessCallsCandidates.d.ts.map +1 -0
- package/dist/phase/dataAccessCallsCandidates.js +27 -0
- package/dist/phase/dataAccessCallsCandidates.js.map +1 -0
- package/dist/phase/dynamicSql.d.ts +18 -0
- package/dist/phase/dynamicSql.d.ts.map +1 -0
- package/dist/phase/dynamicSql.js +20 -0
- package/dist/phase/dynamicSql.js.map +1 -0
- package/dist/phase/exportForm.d.ts +23 -0
- package/dist/phase/exportForm.d.ts.map +1 -0
- package/dist/phase/exportForm.js +29 -0
- package/dist/phase/exportForm.js.map +1 -0
- package/dist/phase/exportSymbols.d.ts +21 -0
- package/dist/phase/exportSymbols.d.ts.map +1 -0
- package/dist/phase/exportSymbols.js +28 -0
- package/dist/phase/exportSymbols.js.map +1 -0
- package/dist/phase/fileHeader.d.ts +15 -0
- package/dist/phase/fileHeader.d.ts.map +1 -0
- package/dist/phase/fileHeader.js +33 -0
- package/dist/phase/fileHeader.js.map +1 -0
- package/dist/phase/fileImports.d.ts +39 -0
- package/dist/phase/fileImports.d.ts.map +1 -0
- package/dist/phase/fileImports.js +138 -0
- package/dist/phase/fileImports.js.map +1 -0
- package/dist/phase/fileSymbols.d.ts +25 -0
- package/dist/phase/fileSymbols.d.ts.map +1 -0
- package/dist/phase/fileSymbols.js +302 -0
- package/dist/phase/fileSymbols.js.map +1 -0
- package/dist/phase/functionBodies.d.ts +28 -0
- package/dist/phase/functionBodies.d.ts.map +1 -0
- package/dist/phase/functionBodies.js +67 -0
- package/dist/phase/functionBodies.js.map +1 -0
- package/dist/phase/functionIndex.d.ts +28 -0
- package/dist/phase/functionIndex.d.ts.map +1 -0
- package/dist/phase/functionIndex.js +152 -0
- package/dist/phase/functionIndex.js.map +1 -0
- package/dist/phase/goFunctionAnalysis.d.ts +47 -0
- package/dist/phase/goFunctionAnalysis.d.ts.map +1 -0
- package/dist/phase/goFunctionAnalysis.js +291 -0
- package/dist/phase/goFunctionAnalysis.js.map +1 -0
- package/dist/phase/goFunctions.d.ts +38 -0
- package/dist/phase/goFunctions.d.ts.map +1 -0
- package/dist/phase/goFunctions.js +154 -0
- package/dist/phase/goFunctions.js.map +1 -0
- package/dist/phase/goImports.d.ts +29 -0
- package/dist/phase/goImports.d.ts.map +1 -0
- package/dist/phase/goImports.js +68 -0
- package/dist/phase/goImports.js.map +1 -0
- package/dist/phase/goPackageBindings.d.ts +22 -0
- package/dist/phase/goPackageBindings.d.ts.map +1 -0
- package/dist/phase/goPackageBindings.js +30 -0
- package/dist/phase/goPackageBindings.js.map +1 -0
- package/dist/phase/goSwitches.d.ts +28 -0
- package/dist/phase/goSwitches.d.ts.map +1 -0
- package/dist/phase/goSwitches.js +52 -0
- package/dist/phase/goSwitches.js.map +1 -0
- package/dist/phase/importForm.d.ts +20 -0
- package/dist/phase/importForm.d.ts.map +1 -0
- package/dist/phase/importForm.js +27 -0
- package/dist/phase/importForm.js.map +1 -0
- package/dist/phase/importSpecifiers.d.ts +20 -0
- package/dist/phase/importSpecifiers.d.ts.map +1 -0
- package/dist/phase/importSpecifiers.js +30 -0
- package/dist/phase/importSpecifiers.js.map +1 -0
- package/dist/phase/imports.d.ts +18 -0
- package/dist/phase/imports.d.ts.map +1 -0
- package/dist/phase/imports.js +25 -0
- package/dist/phase/imports.js.map +1 -0
- package/dist/phase/jsonDocument.d.ts +26 -0
- package/dist/phase/jsonDocument.d.ts.map +1 -0
- package/dist/phase/jsonDocument.js +36 -0
- package/dist/phase/jsonDocument.js.map +1 -0
- package/dist/phase/localBinding.d.ts +33 -0
- package/dist/phase/localBinding.d.ts.map +1 -0
- package/dist/phase/localBinding.js +208 -0
- package/dist/phase/localBinding.js.map +1 -0
- package/dist/phase/loopQueries.d.ts +31 -0
- package/dist/phase/loopQueries.d.ts.map +1 -0
- package/dist/phase/loopQueries.js +44 -0
- package/dist/phase/loopQueries.js.map +1 -0
- package/dist/phase/loopQueryCandidates.d.ts +26 -0
- package/dist/phase/loopQueryCandidates.d.ts.map +1 -0
- package/dist/phase/loopQueryCandidates.js +28 -0
- package/dist/phase/loopQueryCandidates.js.map +1 -0
- package/dist/phase/oracles.d.ts +216 -0
- package/dist/phase/oracles.d.ts.map +1 -0
- package/dist/phase/oracles.js +484 -0
- package/dist/phase/oracles.js.map +1 -0
- package/dist/phase/phaseModel.d.ts +190 -0
- package/dist/phase/phaseModel.d.ts.map +1 -0
- package/dist/phase/phaseModel.js +491 -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 +1030 -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 +207 -0
- package/dist/phase/receiverConsumers.d.ts.map +1 -0
- package/dist/phase/receiverConsumers.js +1063 -0
- package/dist/phase/receiverConsumers.js.map +1 -0
- package/dist/phase/receiverProvenance.d.ts +120 -0
- package/dist/phase/receiverProvenance.d.ts.map +1 -0
- package/dist/phase/receiverProvenance.js +377 -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 +2199 -0
- package/dist/phase/types.d.ts.map +1 -0
- package/dist/phase/types.js +27 -0
- package/dist/phase/types.js.map +1 -0
- package/dist/phase/withinFileProvenance.d.ts +50 -0
- package/dist/phase/withinFileProvenance.d.ts.map +1 -0
- package/dist/phase/withinFileProvenance.js +112 -0
- package/dist/phase/withinFileProvenance.js.map +1 -0
- package/dist/pipeline.d.ts +37 -30
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +131 -326
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +104 -21
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +1144 -984
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/pipelineTypes.d.ts +21 -0
- package/dist/pipelineTypes.d.ts.map +1 -1
- package/dist/pipelineTypes.js +21 -10
- package/dist/pipelineTypes.js.map +1 -1
- package/dist/presets/presets.d.ts +9 -2
- package/dist/presets/presets.d.ts.map +1 -1
- package/dist/presets/presets.js +15 -26
- package/dist/presets/presets.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts +14 -3
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +14 -3
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts +5 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +5 -1
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts +9 -2
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +19 -5
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/reportGenerator.d.ts +9 -2
- package/dist/reporting/reportGenerator.d.ts.map +1 -1
- package/dist/reporting/reportGenerator.js +9 -2
- package/dist/reporting/reportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts +14 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +14 -0
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/reporting/sizeDistribution.d.ts +19 -2
- package/dist/reporting/sizeDistribution.d.ts.map +1 -1
- package/dist/reporting/sizeDistribution.js +23 -5
- package/dist/reporting/sizeDistribution.js.map +1 -1
- package/dist/ruleAliases.d.ts +6 -8
- package/dist/ruleAliases.d.ts.map +1 -1
- package/dist/ruleAliases.js +29 -4
- package/dist/ruleAliases.js.map +1 -1
- package/dist/search/QueryParser.d.ts +1 -0
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +32 -91
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts +19 -20
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js +214 -194
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/services/ProjectTaskRepository.d.ts +65 -3
- package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
- package/dist/services/ProjectTaskRepository.js +94 -25
- package/dist/services/ProjectTaskRepository.js.map +1 -1
- package/dist/services/auditJobService.d.ts +17 -1
- package/dist/services/auditJobService.d.ts.map +1 -1
- package/dist/services/auditJobService.js +16 -0
- package/dist/services/auditJobService.js.map +1 -1
- package/dist/services/fileAccounting.d.ts +43 -5
- package/dist/services/fileAccounting.d.ts.map +1 -1
- package/dist/services/fileAccounting.js +48 -9
- package/dist/services/fileAccounting.js.map +1 -1
- package/dist/services/projectTaskHelpers.d.ts +45 -0
- package/dist/services/projectTaskHelpers.d.ts.map +1 -1
- package/dist/services/projectTaskHelpers.js +45 -0
- package/dist/services/projectTaskHelpers.js.map +1 -1
- package/dist/services/whitelistService.d.ts +40 -5
- package/dist/services/whitelistService.d.ts.map +1 -1
- package/dist/services/whitelistService.js +87 -46
- package/dist/services/whitelistService.js.map +1 -1
- package/dist/sqlite/betterSqlite3.d.ts +35 -1
- package/dist/sqlite/betterSqlite3.d.ts.map +1 -1
- package/dist/sqlite/betterSqlite3.js +35 -1
- package/dist/sqlite/betterSqlite3.js.map +1 -1
- package/dist/sqlite/driver.d.ts +23 -0
- package/dist/sqlite/driver.d.ts.map +1 -1
- package/dist/sqlite/driver.js +23 -0
- package/dist/sqlite/driver.js.map +1 -1
- package/dist/sqlite/nodeSqlite.d.ts +35 -1
- package/dist/sqlite/nodeSqlite.d.ts.map +1 -1
- package/dist/sqlite/nodeSqlite.js +35 -1
- package/dist/sqlite/nodeSqlite.js.map +1 -1
- package/dist/structuralSignature.d.ts +9 -0
- package/dist/structuralSignature.d.ts.map +1 -1
- package/dist/structuralSignature.js +9 -0
- package/dist/structuralSignature.js.map +1 -1
- package/dist/styles/cssAstExtractor.d.ts +15 -0
- package/dist/styles/cssAstExtractor.d.ts.map +1 -1
- package/dist/styles/cssAstExtractor.js +15 -0
- package/dist/styles/cssAstExtractor.js.map +1 -1
- package/dist/styles/normalizer.d.ts +6 -0
- package/dist/styles/normalizer.d.ts.map +1 -1
- package/dist/styles/normalizer.js +6 -0
- package/dist/styles/normalizer.js.map +1 -1
- package/dist/styles/styleExtractor.d.ts +29 -2
- package/dist/styles/styleExtractor.d.ts.map +1 -1
- package/dist/styles/styleExtractor.js +67 -48
- package/dist/styles/styleExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.d.ts +8 -0
- package/dist/styles/styleIndexer.d.ts.map +1 -1
- package/dist/styles/styleIndexer.js +135 -138
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +12 -0
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +12 -0
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/styles/tailwindExpander.d.ts +3 -0
- package/dist/styles/tailwindExpander.d.ts.map +1 -1
- package/dist/styles/tailwindExpander.js +8 -4
- package/dist/styles/tailwindExpander.js.map +1 -1
- package/dist/styles/tailwindProbe.d.ts +9 -0
- package/dist/styles/tailwindProbe.d.ts.map +1 -1
- package/dist/styles/tailwindProbe.js +181 -166
- package/dist/styles/tailwindProbe.js.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts +13 -0
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +13 -0
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/symbols.d.ts +13 -11
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +14 -21
- package/dist/symbols.js.map +1 -1
- package/dist/telemetry.d.ts +31 -3
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +31 -3
- package/dist/telemetry.js.map +1 -1
- package/dist/tool-registry.d.ts +37 -1
- package/dist/tool-registry.d.ts.map +1 -1
- package/dist/tool-registry.js +37 -1
- package/dist/tool-registry.js.map +1 -1
- package/dist/types.d.ts +114 -71
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -16
- package/dist/types.js.map +1 -1
- package/dist/ui/InteractivePrompts.d.ts +12 -0
- package/dist/ui/InteractivePrompts.d.ts.map +1 -1
- package/dist/ui/InteractivePrompts.js +12 -0
- package/dist/ui/InteractivePrompts.js.map +1 -1
- package/dist/utils/astParser.d.ts +15 -0
- package/dist/utils/astParser.d.ts.map +1 -1
- package/dist/utils/astParser.js +15 -0
- package/dist/utils/astParser.js.map +1 -1
- package/dist/utils/astUtils.d.ts +60 -0
- package/dist/utils/astUtils.d.ts.map +1 -1
- package/dist/utils/astUtils.js +350 -362
- package/dist/utils/astUtils.js.map +1 -1
- package/dist/utils/contentHash.d.ts +22 -0
- package/dist/utils/contentHash.d.ts.map +1 -0
- package/dist/utils/contentHash.js +26 -0
- package/dist/utils/contentHash.js.map +1 -0
- package/dist/utils/dependencyExtractor.d.ts +28 -0
- package/dist/utils/dependencyExtractor.d.ts.map +1 -1
- package/dist/utils/dependencyExtractor.js +28 -1
- package/dist/utils/dependencyExtractor.js.map +1 -1
- package/dist/utils/errorMessage.d.ts +10 -0
- package/dist/utils/errorMessage.d.ts.map +1 -0
- package/dist/utils/errorMessage.js +12 -0
- package/dist/utils/errorMessage.js.map +1 -0
- package/dist/utils/fileDiscovery.d.ts +61 -2
- package/dist/utils/fileDiscovery.d.ts.map +1 -1
- package/dist/utils/fileDiscovery.js +131 -23
- package/dist/utils/fileDiscovery.js.map +1 -1
- package/dist/utils/reactDetection.d.ts +34 -5
- package/dist/utils/reactDetection.d.ts.map +1 -1
- package/dist/utils/reactDetection.js +73 -52
- package/dist/utils/reactDetection.js.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/workers/auditWorker.js +7 -3
- package/dist/workers/auditWorker.js.map +1 -1
- package/examples/.auditrc.example.json +0 -8
- package/examples/.auditrc.json +0 -11
- package/examples/.code-auditor.json +0 -2
- package/examples/nextjs.auditrc.json +0 -8
- package/examples/node-api.auditrc.json +0 -7
- package/examples/react.auditrc.json +0 -6
- package/examples/tolerant-solid.auditrc.json +0 -5
- package/package.json +18 -9
- package/plugin/.claude-plugin/manifest.json +16 -0
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/hooks.json +7 -3
- package/plugin/scripts/hook-audit.sh +46 -16
- package/plugin/scripts/hook-common.sh +81 -12
- package/plugin/scripts/hook-drift-check.sh +76 -0
- package/plugin/scripts/hook-self-audit.sh +22 -14
- package/plugin/scripts/hook-warm.sh +5 -6
- package/plugin/skills/code-auditor/SKILL.md +1 -1
- package/server.json +10 -4
package/dist/auditRunner.js
CHANGED
|
@@ -6,15 +6,16 @@ import { promises as fs } from 'fs';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { execFileSync } from 'child_process';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import { AuditAbortedError, } from './types.js';
|
|
11
|
+
import { discoverFiles, discoverFilesDetailed, getSourceExtensions } from './utils/fileDiscovery.js';
|
|
11
12
|
import { FileAccounting } from './services/fileAccounting.js';
|
|
12
13
|
import { loadConfig, findConfigFileUp } from './config/configLoader.js';
|
|
13
14
|
import { mergePathProfiles } from './config/defaults.js';
|
|
15
|
+
import { resolvePathProfile } from './config/pathProfiles.js';
|
|
14
16
|
import { checkThresholdRationales } from './config/thresholdRationales.js';
|
|
15
17
|
import { readProjectLintThresholds, thresholdsToAnalyzerConfig } from './config/lintConfigReader.js';
|
|
16
18
|
import { computeThresholdSources } from './config/effectiveConfig.js';
|
|
17
|
-
import { ALL_ANALYZERS } from './analyzers/ruleRegistry.js';
|
|
18
19
|
import { applyPresets, getPreset } from './presets/presets.js';
|
|
19
20
|
import { generateReport } from './reporting/reportGenerator.js';
|
|
20
21
|
import { extractFunctionsFromFile } from './functionScanner.js';
|
|
@@ -22,17 +23,27 @@ import { isMcpDebugEnabled, logMcpDebug, logMcpInfo } from './mcpDiagnostics.js'
|
|
|
22
23
|
import { loadBaseline, matchFindings, hashBaseline } from './baseline.js';
|
|
23
24
|
import { applyDismissals } from './dismissals.js';
|
|
24
25
|
import { computeImpact, LATENCY_BUDGET_MS } from './graph/blastRadius.js';
|
|
25
|
-
import {
|
|
26
|
+
import { buildResolutionEnvironment } from './graph/resolutionEnvironment.js';
|
|
26
27
|
// Import universal analyzers
|
|
27
28
|
import { initializeLanguages } from './languages/index.js';
|
|
28
29
|
import { initializeOrmAdapters } from './analyzers/orm/index.js';
|
|
29
30
|
import { syncStyleIndex } from './styles/styleIndexer.js';
|
|
30
31
|
import { CodeIndexDB } from './codeIndexDB.js';
|
|
32
|
+
import { notifyLegacyIndexLocation } from './legacyIndexNotice.js';
|
|
31
33
|
import { writeAuditToLedger, detectRunInput } from './ledger.js';
|
|
32
34
|
// Pipeline imports (Spec 25 — pipeline replaces hand-rolled analyzer loop)
|
|
33
|
-
import { runPipeline,
|
|
34
|
-
import {
|
|
35
|
+
import { runPipeline, makeVisitorStatus, getFilesProcessed, isVisitorStatus } from './pipeline.js';
|
|
36
|
+
import { getRuleTimingSortedDesc } from './analyzers/ruleTiming.js';
|
|
37
|
+
import { createFunctionIndexVisitor, createStylesCssVisitor, createStylesSourceVisitor, createStylesReducer, createInvariantsReducer, createSchemaCodeVisitor, } from './pipelineAdapters.js';
|
|
35
38
|
import { computeSizeDistributions } from './reporting/sizeDistribution.js';
|
|
39
|
+
import { splitRoutes, attributeRoutes, enabledMigratedRules } from './phase/routing.js';
|
|
40
|
+
import { runPhaseModel } from './phase/phaseModel.js';
|
|
41
|
+
import { resolvePhaseThresholds } from './phase/config.js';
|
|
42
|
+
import { deriveCoverage, presentFormatsOf } from './phase/coverage.js';
|
|
43
|
+
import { MIGRATED_RULES, RULE_ANALYZER } from './phase/rules/registry.js';
|
|
44
|
+
import { seedDryPairs, resolveBlockConfig } from './phase/rules/dry.js';
|
|
45
|
+
import { checkUnresolvedReceiverImports, checkUnprovenQueryReceivers, checkUnresolvedQueries, checkUnparseableSql, dedupeCannotFireByReceiver } from './analyzers/universal/schema/codeAnalysis.js';
|
|
46
|
+
import { readProjectManifest, computeManifestStaleness } from './analyzers/manifestStaleness.js';
|
|
36
47
|
// Package version — stamped into the build (see constants.ts), not read from
|
|
37
48
|
// package.json at runtime, so a stale binary reports the version it was built as.
|
|
38
49
|
import { PACKAGE_VERSION } from './constants.js';
|
|
@@ -44,1076 +55,1538 @@ initializeOrmAdapters();
|
|
|
44
55
|
/** Schema sub-visitor names — shown as separate rows in the CLI table. */
|
|
45
56
|
const SCHEMA_SUB_VISITORS = ['schema-sql', 'schema-code', 'schema-prisma', 'schema-json'];
|
|
46
57
|
/**
|
|
47
|
-
*
|
|
58
|
+
* CPU time consumed since `start` (a prior `process.cpuUsage()` snapshot), in
|
|
59
|
+
* milliseconds. Load-independent: wall clock measures how long the run took on
|
|
60
|
+
* the wall (inflated by co-tenant load), CPU time measures how much processor
|
|
61
|
+
* the run's rules actually burned. The gate-budget check asserts on this.
|
|
48
62
|
*/
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
function cpuDurationMs(start) {
|
|
64
|
+
const cpu = process.cpuUsage(start);
|
|
65
|
+
return (cpu.user + cpu.system) / 1000;
|
|
66
|
+
}
|
|
67
|
+
/** An empty visitor result for an analyzer that emitted nothing this run. The
|
|
68
|
+
* call sites below shared the same four-field object literal (violations,
|
|
69
|
+
* status, executionTime, analyzerName); this factory keeps the shape in one
|
|
70
|
+
* place so a new field can't drift between them. */
|
|
71
|
+
function makeEmptyAnalyzerResult(analyzerName, filesProcessed) {
|
|
72
|
+
return {
|
|
73
|
+
violations: [],
|
|
74
|
+
status: makeVisitorStatus(filesProcessed),
|
|
75
|
+
executionTime: 0,
|
|
76
|
+
analyzerName,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Spec 68 §15 — the full pipeline stage set, always run. Derived from the
|
|
81
|
+
* migrated rules' own `analyzer` labels (each `RuleDefinition` carries the
|
|
82
|
+
* namespace it re-emits into) plus the pipeline-only `invariants` reducer
|
|
83
|
+
* (user-defined rules from `.codeauditor.json`; it auto-disables when none are
|
|
84
|
+
* configured). No selection remains: `indexOnly` skips analysis wholesale, but
|
|
85
|
+
* config cannot pick a subset of analyzers.
|
|
86
|
+
*/
|
|
87
|
+
const RUN_ANALYZERS = [
|
|
88
|
+
...new Set(MIGRATED_RULES.map((r) => r.analyzer)),
|
|
89
|
+
'invariants',
|
|
90
|
+
].sort();
|
|
91
|
+
/**
|
|
92
|
+
* §6.6 — the phase-model pool size. Defaults to `max(1, cpus - 1)` and is a
|
|
93
|
+
* config threshold (env `CODE_AUDITOR_WORKERS`), never a selection gate: the
|
|
94
|
+
* fact merge is file-sorted, so the value never reorders findings (§6.4).
|
|
95
|
+
* `CODE_AUDITOR_WORKERS=1` is the determinism-test escape hatch that forces the
|
|
96
|
+
* serial path for byte-identical-report comparisons.
|
|
97
|
+
*/
|
|
98
|
+
function resolveWorkerCount() {
|
|
99
|
+
const env = Number(process.env.CODE_AUDITOR_WORKERS);
|
|
100
|
+
if (Number.isInteger(env) && env > 0)
|
|
101
|
+
return env;
|
|
102
|
+
return Math.max(1, os.cpus().length - 1);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the effective run configuration: load `.codeauditor.json` (walking up
|
|
106
|
+
* from the audit path so a scoped `--path src` still finds project-root config),
|
|
107
|
+
* enforce the Spec 36 R5 threshold-rationale guard, merge the project's own lint
|
|
108
|
+
* thresholds as a base layer, apply shareable presets, and merge built-in path
|
|
109
|
+
* profiles. Returns everything `run` needs to proceed plus the diagnostics that
|
|
110
|
+
* surface in result metadata (`rejected`, `lintConfigDiagnostics`).
|
|
111
|
+
*/
|
|
112
|
+
async function resolveRunConfig(baseOptions, runOptions) {
|
|
113
|
+
const rootForConfig = runOptions?.projectRoot || baseOptions.projectRoot || process.cwd();
|
|
114
|
+
const configPath = await findConfigFileUp(rootForConfig);
|
|
115
|
+
let fileConfig = {};
|
|
116
|
+
let rejected = [];
|
|
117
|
+
if (configPath) {
|
|
118
|
+
const loaded = await loadConfig({ configPath, projectRoot: rootForConfig });
|
|
119
|
+
fileConfig = loaded.config;
|
|
120
|
+
rejected = loaded.rejected;
|
|
57
121
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
const mergedOptions = { ...fileConfig, ...baseOptions, ...runOptions };
|
|
123
|
+
// Spec 36 R5 — a threshold change needs a written rationale. Check the
|
|
124
|
+
// user-facing analyzerConfigs layer (project config + inline options)
|
|
125
|
+
// BEFORE presets merge in, so curated presets never trip the guard. Any
|
|
126
|
+
// changed threshold without a rationale is a config error, not a warning.
|
|
127
|
+
const thresholdCheck = checkThresholdRationales(mergedOptions.analyzerConfigs, mergedOptions.rationales);
|
|
128
|
+
if (thresholdCheck.errors.length > 0) {
|
|
129
|
+
const message = [
|
|
130
|
+
'Configuration error — threshold changes require a rationale (Spec 36 R5):',
|
|
131
|
+
...thresholdCheck.errors.map((e) => ` - ${e}`),
|
|
132
|
+
].join('\n');
|
|
133
|
+
throw new Error(message);
|
|
134
|
+
}
|
|
135
|
+
const thresholdChanges = thresholdCheck.changes.map((c) => ({
|
|
136
|
+
key: c.key,
|
|
137
|
+
defaultValue: c.defaultValue,
|
|
138
|
+
effectiveValue: c.effectiveValue,
|
|
139
|
+
}));
|
|
140
|
+
// ── Spec 50 R2 — project lint config as threshold authority ──────────
|
|
141
|
+
// Read the project's own ESLint config (flat or legacy) and merge its size
|
|
142
|
+
// thresholds (max-lines-per-function, max-params, complexity) as a base layer
|
|
143
|
+
// UNDER the user's analyzerConfigs. Because we only fill keys the project did
|
|
144
|
+
// not set, `.codeauditor.json` always wins. Runs AFTER checkThresholdRationales
|
|
145
|
+
// so lint-sourced values never trip the Spec 36 R5 guard. Fail-open: no config,
|
|
146
|
+
// or an unloadable config, is absent — defaults, not an error.
|
|
147
|
+
const projectAnalyzerConfigs = (mergedOptions.analyzerConfigs ?? {});
|
|
148
|
+
const lintRoot = path.resolve(mergedOptions.projectRoot || process.cwd());
|
|
149
|
+
const lintResult = await readProjectLintThresholds(lintRoot);
|
|
150
|
+
const lintThresholds = lintResult?.thresholds ?? {};
|
|
151
|
+
// Spec 61 R3.2 — an ESLint config that exists but could not be read
|
|
152
|
+
// statically is a `cannot-fire` coverage diagnostic, surfaced (never
|
|
153
|
+
// silently treated as "no config").
|
|
154
|
+
const lintConfigDiagnostics = lintResult?.diagnostic ? [lintResult.diagnostic] : [];
|
|
155
|
+
if (Object.keys(lintThresholds).length > 0) {
|
|
156
|
+
mergedOptions.analyzerConfigs = mergeLintUnderProject(thresholdsToAnalyzerConfig(lintThresholds), projectAnalyzerConfigs);
|
|
157
|
+
}
|
|
158
|
+
// Resolve shareable presets (Spec 38 R4). Unknown ids are dropped (matching
|
|
159
|
+
// mergePresets semantics); the merged preset layer becomes the base under
|
|
160
|
+
// the project config / run options already present in mergedOptions.
|
|
161
|
+
const presetIds = Array.isArray(mergedOptions.presets) ? mergedOptions.presets : [];
|
|
162
|
+
if (presetIds.length > 0) {
|
|
163
|
+
mergedOptions.analyzerConfigs = applyPresets(presetIds, mergedOptions.analyzerConfigs);
|
|
164
|
+
}
|
|
165
|
+
// Spec 50 R2 — which config layer supplied each size threshold (coverage output).
|
|
166
|
+
const resolvedPresets = presetIds
|
|
167
|
+
.map((id) => getPreset(id))
|
|
168
|
+
.filter((p) => p !== undefined);
|
|
169
|
+
const thresholdSources = computeThresholdSources({
|
|
170
|
+
projectConfig: projectAnalyzerConfigs,
|
|
171
|
+
lintThresholds,
|
|
172
|
+
presets: resolvedPresets,
|
|
173
|
+
});
|
|
174
|
+
// Always merge built-in path profiles — corpus audits and projects without
|
|
175
|
+
// .codeauditor.json must still get the built-in scripts-and-tests profile.
|
|
176
|
+
mergedOptions.pathProfiles = mergePathProfiles(mergedOptions.pathProfiles, mergedOptions.builtin);
|
|
177
|
+
return {
|
|
178
|
+
mergedOptions,
|
|
179
|
+
configPath,
|
|
180
|
+
rejected,
|
|
181
|
+
lintConfigDiagnostics,
|
|
182
|
+
thresholdChanges,
|
|
183
|
+
thresholdSources,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolve the audit's file set from the configured scope (`all`, `git:<ref>`,
|
|
188
|
+
* `changed`, or an explicit files array), detect changed functions for scoped
|
|
189
|
+
* runs, compute blast-radius impact for those functions, and re-walk discovery
|
|
190
|
+
* for the unfiltered corpus file list (the import classifier's input — see the
|
|
191
|
+
* Spec 60.1 Correction 1 comment below). Returns everything `run` needs to
|
|
192
|
+
* build the pipeline, plus `scope`/`isScoped`/`scopeResultType` for downstream
|
|
193
|
+
* metadata and the ledger.
|
|
194
|
+
*/
|
|
195
|
+
async function discoverAuditFiles(mergedOptions, root, fileAccounting) {
|
|
196
|
+
const scope = mergedOptions.scope ?? 'all';
|
|
197
|
+
const isScoped = scope !== 'all';
|
|
198
|
+
const scopeResultType = isScoped ? 'scoped' : 'full';
|
|
199
|
+
reportProgress(mergedOptions, {
|
|
200
|
+
phase: 'discovery',
|
|
201
|
+
message: `Discovering files... (scope: ${scopeResultType})`
|
|
202
|
+
});
|
|
203
|
+
const { files, skippedExtensions } = await resolveDiscoveryScope(mergedOptions, scope, fileAccounting);
|
|
204
|
+
throwIfAborted(mergedOptions.abortSignal);
|
|
205
|
+
// Detect changed functions for non-all scopes
|
|
206
|
+
let changedFunctions;
|
|
207
|
+
if (isScoped && files.length > 0) {
|
|
208
|
+
changedFunctions = await detectChangedFunctionsForScope(mergedOptions, files);
|
|
209
|
+
}
|
|
210
|
+
let blastRadius;
|
|
211
|
+
if (isScoped && changedFunctions && changedFunctions.length > 0) {
|
|
212
|
+
blastRadius = await computeBlastRadiusForScope(mergedOptions, changedFunctions);
|
|
213
|
+
}
|
|
214
|
+
const corpusFiles = await buildImportClassifierCorpus(mergedOptions, root, files, isScoped);
|
|
215
|
+
logMcpInfo('discovery', 'file discovery finished', {
|
|
216
|
+
projectRoot: path.resolve(root),
|
|
217
|
+
totalFiles: files.length,
|
|
218
|
+
corpusFiles: corpusFiles.length,
|
|
219
|
+
scope: scopeResultType,
|
|
220
|
+
indexFunctions: !!mergedOptions.indexFunctions
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
files,
|
|
224
|
+
changedFunctions,
|
|
225
|
+
skippedExtensions,
|
|
226
|
+
blastRadius,
|
|
227
|
+
corpusFiles,
|
|
228
|
+
scope,
|
|
229
|
+
isScoped,
|
|
230
|
+
scopeResultType,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Resolve the audit scope into a concrete file list. This is the dispatch the
|
|
235
|
+
* orchestrator used to hold inline: `git:<ref>` diffs the worktree, `changed`
|
|
236
|
+
* reads the index's modified-file set, an array of paths/globs is an explicit
|
|
237
|
+
* files scope, and anything else is the full discovery walk.
|
|
238
|
+
*/
|
|
239
|
+
async function resolveDiscoveryScope(mergedOptions, scope, fileAccounting) {
|
|
240
|
+
let files;
|
|
241
|
+
// Extensions present on disk that discovery skipped (Spec 43 R5 follow-up).
|
|
242
|
+
// Populated only for the `all` scope — scoped runs are explicitly scoped by
|
|
243
|
+
// the user, so "what wasn't analyzed" there is the scope itself, not the
|
|
244
|
+
// extension filter.
|
|
245
|
+
let skippedExtensions;
|
|
246
|
+
if (typeof scope === 'string' && scope.startsWith('git:')) {
|
|
247
|
+
// git:<ref> scope
|
|
248
|
+
const gitRef = scope.slice(4);
|
|
249
|
+
files = resolveGitScopeFiles(mergedOptions, gitRef);
|
|
250
|
+
logMcpInfo('discovery', 'git scope resolved', {
|
|
251
|
+
ref: gitRef,
|
|
252
|
+
fileCount: files.length
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
else if (scope === 'changed') {
|
|
256
|
+
// Changed scope: detect modified files. Fail open on an unavailable index
|
|
257
|
+
// (a future schema version is the sharpest case): when the caller pinned
|
|
258
|
+
// explicit files we audit those without touching the index; otherwise there
|
|
259
|
+
// is nothing to diff against, so the run degrades to zero files and the
|
|
260
|
+
// analyze phase reports the index skip as a coverage diagnostic (never a
|
|
261
|
+
// fatal — the hook must complete with the exit code it would otherwise have).
|
|
262
|
+
let db;
|
|
263
|
+
try {
|
|
264
|
+
db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
|
|
265
|
+
await db.initialize();
|
|
112
266
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (presetIds.length > 0) {
|
|
118
|
-
mergedOptions.analyzerConfigs = applyPresets(presetIds, mergedOptions.analyzerConfigs);
|
|
267
|
+
catch (err) {
|
|
268
|
+
logMcpInfo('discovery', 'changed scope index unavailable (continuing)', {
|
|
269
|
+
error: err instanceof Error ? err.message : String(err)
|
|
270
|
+
});
|
|
119
271
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
lintThresholds,
|
|
127
|
-
presets: resolvedPresets,
|
|
272
|
+
const modifiedFiles = mergedOptions.explicitFiles !== undefined
|
|
273
|
+
? mergedOptions.explicitFiles
|
|
274
|
+
: (db ? await db.detectModifiedFiles(path.resolve(mergedOptions.projectRoot || process.cwd())) : []);
|
|
275
|
+
files = [...new Set(modifiedFiles.map((f) => path.resolve(f)))].sort();
|
|
276
|
+
logMcpInfo('discovery', 'changed scope resolved', {
|
|
277
|
+
fileCount: files.length
|
|
128
278
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const isScoped = scope !== 'all';
|
|
136
|
-
const scopeResultType = isScoped ? 'scoped' : 'full';
|
|
137
|
-
reportProgress(mergedOptions, {
|
|
138
|
-
phase: 'discovery',
|
|
139
|
-
message: `Discovering files... (scope: ${scopeResultType})`
|
|
279
|
+
}
|
|
280
|
+
else if (Array.isArray(scope)) {
|
|
281
|
+
// files scope: explicit file paths/globs
|
|
282
|
+
files = await resolveFilesScope(mergedOptions, scope);
|
|
283
|
+
logMcpInfo('discovery', 'files scope resolved', {
|
|
284
|
+
fileCount: files.length
|
|
140
285
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
// all scope: current behavior
|
|
289
|
+
const discovered = await discoverProjectFiles(mergedOptions, fileAccounting);
|
|
290
|
+
files = discovered.files;
|
|
291
|
+
skippedExtensions = discovered.skippedExtensions;
|
|
292
|
+
}
|
|
293
|
+
return { files, skippedExtensions };
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Detect changed functions for a scoped run via the code index (Spec 60.1).
|
|
297
|
+
* Returns `undefined` when the index is unavailable — the caller treats that as
|
|
298
|
+
* "no diff to report", never a fatal.
|
|
299
|
+
*/
|
|
300
|
+
async function detectChangedFunctionsForScope(mergedOptions, files) {
|
|
301
|
+
try {
|
|
302
|
+
const db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
|
|
303
|
+
await db.initialize();
|
|
304
|
+
const detection = await db.detectChangedFunctions(files);
|
|
305
|
+
logMcpInfo('discovery', 'changed function detection', {
|
|
306
|
+
changedFunctionCount: detection.changedFunctions.length,
|
|
307
|
+
deletedCount: detection.deletedFunctions.length,
|
|
308
|
+
errors: detection.errors.length
|
|
309
|
+
});
|
|
310
|
+
return detection.changedFunctions;
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
logMcpInfo('discovery', 'changed function detection failed (continuing)', {
|
|
314
|
+
error: err instanceof Error ? err.message : String(err)
|
|
315
|
+
});
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Compute transitive-caller impact for changed functions (Spec 14 R6). Advisory:
|
|
321
|
+
* on error, or when the 100ms budget is exceeded, this returns `undefined` and
|
|
322
|
+
* the feature is disabled for the run — never a fatal.
|
|
323
|
+
*/
|
|
324
|
+
async function computeBlastRadiusForScope(mergedOptions, changedFunctions) {
|
|
325
|
+
try {
|
|
326
|
+
const db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
|
|
327
|
+
const rawDb = db.rawDb;
|
|
328
|
+
const functionIds = [];
|
|
329
|
+
// One batched query per chunk of changed functions, not one query per
|
|
330
|
+
// function (loop-query). `changedFunctions` can be large, so chunk to the
|
|
331
|
+
// SQLite bind-parameter cap; `ORDER BY id` + first-wins by (name, file_path)
|
|
332
|
+
// reproduces the old `.get` (first matching row) semantics exactly.
|
|
333
|
+
const idByKey = new Map();
|
|
334
|
+
const BATCH = 900;
|
|
335
|
+
for (let i = 0; i < changedFunctions.length; i += BATCH) {
|
|
336
|
+
const chunk = changedFunctions.slice(i, i + BATCH);
|
|
337
|
+
const orClause = chunk.map(() => '(name = ? AND file_path = ?)').join(' OR ');
|
|
338
|
+
const params = chunk.flatMap((fn) => [fn.name, fn.filePath]);
|
|
339
|
+
const rows = rawDb.prepare(`SELECT id, name, file_path FROM functions WHERE ${orClause} ORDER BY id`).all(...params);
|
|
340
|
+
for (const r of rows) {
|
|
341
|
+
const key = `${r.name}\0${r.file_path}`;
|
|
342
|
+
if (!idByKey.has(key))
|
|
343
|
+
idByKey.set(key, r.id);
|
|
344
|
+
}
|
|
173
345
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
fileCount: files.length
|
|
179
|
-
});
|
|
346
|
+
for (const fn of changedFunctions) {
|
|
347
|
+
const id = idByKey.get(`${fn.name}\0${fn.filePath}`);
|
|
348
|
+
if (id != null)
|
|
349
|
+
functionIds.push(id);
|
|
180
350
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
351
|
+
if (functionIds.length === 0)
|
|
352
|
+
return undefined;
|
|
353
|
+
const impact = computeImpact(rawDb, functionIds);
|
|
354
|
+
if (impact.latencyMs > LATENCY_BUDGET_MS) {
|
|
355
|
+
logMcpInfo('blast-radius', `latency ${impact.latencyMs}ms exceeds ${LATENCY_BUDGET_MS}ms budget — blast radius disabled for this run`, {});
|
|
356
|
+
return undefined;
|
|
186
357
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
358
|
+
logMcpInfo('blast-radius', 'computed', {
|
|
359
|
+
editedFunctionCount: impact.editedFunctionCount,
|
|
360
|
+
transitiveCallers: impact.transitiveCallers,
|
|
361
|
+
reachableExports: impact.reachableExports,
|
|
362
|
+
latencyMs: impact.latencyMs,
|
|
363
|
+
});
|
|
364
|
+
return impact;
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
// Blast radius is advisory — non-fatal
|
|
368
|
+
logMcpInfo('blast-radius', 'computation failed (continuing)', {
|
|
369
|
+
error: err instanceof Error ? err.message : String(err)
|
|
370
|
+
});
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Build the import classifier's corpus file set (Spec 60.1 Correction 1). The
|
|
376
|
+
* classifier's corpus must be the UNFILTERED stage-1 discovery list, not the
|
|
377
|
+
* audit's `files` list, which is narrowed at two points before `_infra` is
|
|
378
|
+
* built:
|
|
379
|
+
* 1. The polyglot dispatch narrows `files` to the per-language list it
|
|
380
|
+
* actually hands to the TypeScript analyzer — files with no detectable
|
|
381
|
+
* language (`.json`, `.sql`) drop out here.
|
|
382
|
+
* 2. `filterFiles` (fileDiscovery.ts, called from `findFiles`) applies
|
|
383
|
+
* `includePaths` as a positive-selection glob filter — a `.json` file that
|
|
384
|
+
* survives step 1 is dropped here (or by the default `includePaths` from
|
|
385
|
+
* config/defaults.ts, which omits `.json`).
|
|
386
|
+
* Either way `_infra.files` no longer contains `.json`, so a real
|
|
387
|
+
* `./invariant-rules.schema.json` import classifies `internal-broken`.
|
|
388
|
+
* Classification answers "does this import resolve to a real file", independent
|
|
389
|
+
* of what the audit chooses to analyze.
|
|
390
|
+
*
|
|
391
|
+
* For a full (`all`) audit the corpus is the unfiltered discovery walk. For a
|
|
392
|
+
* scoped run that walk is the defect Spec 68 §hook-contract names: a
|
|
393
|
+
* single-file scope must not enumerate the whole project. A scoped run's corpus
|
|
394
|
+
* is instead "the files in scope + whatever the index already holds about the
|
|
395
|
+
* rest" — the indexed source files (`functions.file_path`) plus every file the
|
|
396
|
+
* index has ever resolved an import to (`import_specifiers.resolved_path`, which
|
|
397
|
+
* is how non-function import targets like `.json`/`.css`/`.sql` stay classifiable
|
|
398
|
+
* without a walk). If the index is unavailable the corpus degrades to the
|
|
399
|
+
* in-scope files alone — out-of-scope imports then read `internal-broken`, never
|
|
400
|
+
* a fatal.
|
|
401
|
+
*/
|
|
402
|
+
async function buildImportClassifierCorpus(mergedOptions, root, files, isScoped) {
|
|
403
|
+
if (!isScoped) {
|
|
404
|
+
return discoverFiles(root);
|
|
405
|
+
}
|
|
406
|
+
const corpus = new Set(files);
|
|
407
|
+
try {
|
|
408
|
+
const db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
|
|
409
|
+
await db.initialize();
|
|
410
|
+
const indexed = db.rawDb
|
|
411
|
+
.prepare('SELECT DISTINCT file_path AS p FROM functions')
|
|
412
|
+
.all();
|
|
413
|
+
for (const { p } of indexed)
|
|
414
|
+
corpus.add(p);
|
|
415
|
+
const resolved = db.rawDb
|
|
416
|
+
.prepare('SELECT DISTINCT resolved_path AS p FROM import_specifiers WHERE resolved_path IS NOT NULL')
|
|
417
|
+
.all();
|
|
418
|
+
for (const { p } of resolved)
|
|
419
|
+
corpus.add(p);
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
// Index unavailable — the in-scope files alone remain the corpus.
|
|
423
|
+
}
|
|
424
|
+
return [...corpus].sort();
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Collect per-file function metadata for the index (Spec 44 / Spec 60.1). Only
|
|
428
|
+
* script files are extracted here; Go files are indexed by the SOLID analyzer
|
|
429
|
+
* directly. Threads the abort signal and a progress callback so a long extraction
|
|
430
|
+
* stays cancellable and observable.
|
|
431
|
+
*/
|
|
432
|
+
async function collectIndexedFunctions(mergedOptions, files) {
|
|
433
|
+
let collectedFunctions = [];
|
|
434
|
+
const fileToFunctionsMap = new Map(); // Track functions per file for sync
|
|
435
|
+
if (mergedOptions.indexFunctions) {
|
|
436
|
+
reportProgress(mergedOptions, {
|
|
437
|
+
phase: 'function-indexing',
|
|
438
|
+
message: 'Collecting functions from files...'
|
|
439
|
+
});
|
|
440
|
+
// Collect functions from TypeScript/JavaScript files
|
|
441
|
+
// Note: Go files will be indexed by the Universal SOLID analyzer directly
|
|
442
|
+
const scriptFiles = files.filter(f => f.endsWith('.ts') || f.endsWith('.tsx') ||
|
|
443
|
+
f.endsWith('.js') || f.endsWith('.jsx') ||
|
|
444
|
+
f.endsWith('.mts') || f.endsWith('.cts') ||
|
|
445
|
+
f.endsWith('.mjs') || f.endsWith('.cjs'));
|
|
446
|
+
logMcpInfo('function-indexing', 'extracting functions from script files', {
|
|
447
|
+
scriptFileCount: scriptFiles.length
|
|
448
|
+
});
|
|
449
|
+
for (let i = 0; i < scriptFiles.length; i++) {
|
|
190
450
|
try {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
451
|
+
if (i % 10 === 0) {
|
|
452
|
+
throwIfAborted(mergedOptions.abortSignal);
|
|
453
|
+
}
|
|
454
|
+
if (i > 0 && i % 50 === 0) {
|
|
455
|
+
logMcpInfo('function-indexing', 'progress', {
|
|
456
|
+
current: i,
|
|
457
|
+
total: scriptFiles.length
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
const fileFunctions = await extractFunctionsFromFile(scriptFiles[i], {
|
|
461
|
+
unusedImportsConfig: mergedOptions.unusedImportsConfig
|
|
199
462
|
});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
463
|
+
collectedFunctions.push(...fileFunctions);
|
|
464
|
+
fileToFunctionsMap.set(scriptFiles[i], fileFunctions); // Store for sync
|
|
465
|
+
reportProgress(mergedOptions, {
|
|
466
|
+
phase: 'function-indexing',
|
|
467
|
+
current: i + 1,
|
|
468
|
+
total: scriptFiles.length,
|
|
469
|
+
message: `Collected ${fileFunctions.length} items from ${scriptFiles[i]}`
|
|
204
470
|
});
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// If latency exceeds the 100ms budget, skip and emit a warning;
|
|
210
|
-
// the feature is disabled-by-default when over budget.
|
|
211
|
-
let blastRadius;
|
|
212
|
-
if (isScoped && changedFunctions && changedFunctions.length > 0) {
|
|
213
|
-
try {
|
|
214
|
-
const db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
|
|
215
|
-
const rawDb = db.rawDb;
|
|
216
|
-
const functionIds = [];
|
|
217
|
-
for (const fn of changedFunctions) {
|
|
218
|
-
const row = rawDb.prepare('SELECT id FROM functions WHERE name = ? AND file_path = ?').get(fn.name, fn.filePath);
|
|
219
|
-
if (row)
|
|
220
|
-
functionIds.push(row.id);
|
|
221
|
-
}
|
|
222
|
-
if (functionIds.length > 0) {
|
|
223
|
-
const impact = computeImpact(rawDb, functionIds);
|
|
224
|
-
if (impact.latencyMs > LATENCY_BUDGET_MS) {
|
|
225
|
-
logMcpInfo('blast-radius', `latency ${impact.latencyMs}ms exceeds ${LATENCY_BUDGET_MS}ms budget — blast radius disabled for this run`, {});
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
blastRadius = impact;
|
|
229
|
-
logMcpInfo('blast-radius', 'computed', {
|
|
230
|
-
editedFunctionCount: impact.editedFunctionCount,
|
|
231
|
-
transitiveCallers: impact.transitiveCallers,
|
|
232
|
-
reachableExports: impact.reachableExports,
|
|
233
|
-
latencyMs: impact.latencyMs,
|
|
234
|
-
});
|
|
235
|
-
}
|
|
471
|
+
if (isMcpDebugEnabled()) {
|
|
472
|
+
logMcpDebug('function-indexing', scriptFiles[i], {
|
|
473
|
+
symbols: fileFunctions.length
|
|
474
|
+
});
|
|
236
475
|
}
|
|
237
476
|
}
|
|
238
|
-
catch (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
error:
|
|
477
|
+
catch (error) {
|
|
478
|
+
logMcpInfo('function-indexing', 'extract failed (continuing)', {
|
|
479
|
+
file: scriptFiles[i],
|
|
480
|
+
error: error instanceof Error ? error.message : String(error)
|
|
242
481
|
});
|
|
243
482
|
}
|
|
244
483
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
logMcpInfo('discovery', 'file discovery finished', {
|
|
270
|
-
projectRoot: path.resolve(root),
|
|
271
|
-
totalFiles: files.length,
|
|
272
|
-
corpusFiles: corpusFiles.length,
|
|
273
|
-
scope: scopeResultType,
|
|
274
|
-
indexFunctions: !!mergedOptions.indexFunctions
|
|
275
|
-
});
|
|
276
|
-
// Collect functions if enabled
|
|
277
|
-
let collectedFunctions = [];
|
|
278
|
-
const fileToFunctionsMap = new Map(); // Track functions per file for sync
|
|
279
|
-
if (mergedOptions.indexFunctions) {
|
|
280
|
-
reportProgress(mergedOptions, {
|
|
281
|
-
phase: 'function-indexing',
|
|
282
|
-
message: 'Collecting functions from files...'
|
|
484
|
+
}
|
|
485
|
+
return { collectedFunctions, fileToFunctionsMap };
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Sync style declarations, tokens, and class usage before the styles analyzer
|
|
489
|
+
* runs (Spec 10), mirroring the function index sync. Non-fatal: on failure the
|
|
490
|
+
* styles analyzer runs with whatever is already in the index.
|
|
491
|
+
*/
|
|
492
|
+
async function syncStyles(analyzers, root, files, isScoped) {
|
|
493
|
+
let styleConsumedFiles = [];
|
|
494
|
+
let styleContributingFiles;
|
|
495
|
+
if (analyzers.includes('styles')) {
|
|
496
|
+
try {
|
|
497
|
+
const styleDb = CodeIndexDB.getInstance(undefined, root);
|
|
498
|
+
await styleDb.initialize();
|
|
499
|
+
const styleSyncResult = await syncStyleIndex(styleDb.rawDb, files, root, { scoped: isScoped });
|
|
500
|
+
styleConsumedFiles = styleSyncResult.consumedFiles;
|
|
501
|
+
styleContributingFiles = styleSyncResult.contributingFiles;
|
|
502
|
+
logMcpInfo('style-index', 'style index sync complete', {
|
|
503
|
+
changed: styleSyncResult.changed,
|
|
504
|
+
skipped: styleSyncResult.skipped,
|
|
505
|
+
removed: styleSyncResult.removed,
|
|
506
|
+
errors: styleSyncResult.errors,
|
|
507
|
+
consumed: styleSyncResult.consumedFiles.length,
|
|
283
508
|
});
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
f.endsWith('.mjs') || f.endsWith('.cjs'));
|
|
290
|
-
logMcpInfo('function-indexing', 'extracting functions from script files', {
|
|
291
|
-
scriptFileCount: scriptFiles.length
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
511
|
+
// Non-fatal: styles analyzer will run with whatever is in the index
|
|
512
|
+
logMcpInfo('style-index', 'style index sync failed (continuing)', {
|
|
513
|
+
error: err instanceof Error ? err.message : String(err)
|
|
292
514
|
});
|
|
293
|
-
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return { styleConsumedFiles, styleContributingFiles };
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Assemble the Stage-2 visitors / Stage-3 reducers / Stage-4 derived reducers
|
|
521
|
+
* for this run. Spec 68 §15 — once every registry rule is migrated, the legacy
|
|
522
|
+
* pipeline's rule-emitting visitors/reducers are skipped (their findings are
|
|
523
|
+
* stripped to zero at the both-paths split); only the infrastructure with a
|
|
524
|
+
* side-effect the phase model reads from the index keeps running.
|
|
525
|
+
*/
|
|
526
|
+
function buildPipelineAdapters(analyzers) {
|
|
527
|
+
const pipelineVisitors = [];
|
|
528
|
+
const pipelineReducers = [];
|
|
529
|
+
const pipelineDerivedReducers = [];
|
|
530
|
+
let reactBundle;
|
|
531
|
+
let solidBundle;
|
|
532
|
+
// Always-on infrastructure: function-index visitor populates the
|
|
533
|
+
// `functions` table so conventions + cross-domain reducers have data
|
|
534
|
+
// even on a cold run with no prior index sync.
|
|
535
|
+
pipelineVisitors.push(createFunctionIndexVisitor());
|
|
536
|
+
// styles-css visitor — AST-extracts .css files into style_* tables (Spec 26 Phase 2)
|
|
537
|
+
if (analyzers.includes('styles'))
|
|
538
|
+
pipelineVisitors.push(createStylesCssVisitor());
|
|
539
|
+
// styles-source visitor — AST-extracts TS/JS CSS-in-JS into style_* tables,
|
|
540
|
+
// reusing the stage-1 parse (eliminates the style-index re-parse).
|
|
541
|
+
if (analyzers.includes('styles'))
|
|
542
|
+
pipelineVisitors.push(createStylesSourceVisitor());
|
|
543
|
+
// The DRY visitor's findings are all migrated and stripped, and its pair-seed
|
|
544
|
+
// side-effect now comes from the phase `code-block` fact (`seedDryPairs`, Spec
|
|
545
|
+
// 70 2b), so there is no legacy DRY visitor to register.
|
|
546
|
+
// The invariant reducer is pipeline-only (its rules come from
|
|
547
|
+
// `.codeauditor.json`, not the registry), so it always runs — it is the one
|
|
548
|
+
// legacy path that still emits non-migrated findings.
|
|
549
|
+
if (analyzers.includes('invariants'))
|
|
550
|
+
pipelineReducers.push(createInvariantsReducer());
|
|
551
|
+
// The styles reducer persists the styles-css visitor's facts into the
|
|
552
|
+
// `style_*` tables (the styles-source visitor writes its own). The phase
|
|
553
|
+
// `defined-classes` / `unread-style-sources` corpus producers read those
|
|
554
|
+
// tables, so this reducer must keep running even though its *findings* are
|
|
555
|
+
// migrated and stripped.
|
|
556
|
+
if (analyzers.includes('styles'))
|
|
557
|
+
pipelineReducers.push(createStylesReducer());
|
|
558
|
+
// The schema-code visitor's findings are migrated and stripped, but it must
|
|
559
|
+
// keep running for its one remaining live emit: the `reserved-word` finding
|
|
560
|
+
// off `checkNamingConventions` (unregistered emission, §13 disposition) and the
|
|
561
|
+
// ORM/DDL table catalog. Its `unresolved-query` diagnostic (Spec 58 R1) and its
|
|
562
|
+
// `unparseable` cannot-fire diagnostic (Spec 70 R2) are both re-derived
|
|
563
|
+
// corpus-side (Spec 70 1b / R2), so this visitor no longer emits either.
|
|
564
|
+
if (analyzers.includes('schema'))
|
|
565
|
+
pipelineVisitors.push(createSchemaCodeVisitor());
|
|
566
|
+
return {
|
|
567
|
+
pipelineVisitors,
|
|
568
|
+
pipelineReducers,
|
|
569
|
+
pipelineDerivedReducers,
|
|
570
|
+
reactBundle,
|
|
571
|
+
solidBundle,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Build the per-analyzer namespaced config passed to every pipeline stage. Each
|
|
576
|
+
* analyzer gets its own namespace; `_infra` holds shared infrastructure keys.
|
|
577
|
+
*/
|
|
578
|
+
function buildPipelineAnalyzerConfig(analyzers, mergedOptions, root, files, corpusFiles, provenanceTiming) {
|
|
579
|
+
const pipelineAnalyzerConfig = {};
|
|
580
|
+
for (const name of analyzers) {
|
|
581
|
+
pipelineAnalyzerConfig[name] = { ...(mergedOptions.analyzerConfigs?.[name] ?? {}) };
|
|
582
|
+
}
|
|
583
|
+
// Spec 70 R1 — the corpus's dialect is threaded to the data-access analyzer
|
|
584
|
+
// (whose SQL-content facts parse rather than regex) and to DB-receiver
|
|
585
|
+
// resolution. Explicit config (`databaseType`) overrides detection; otherwise
|
|
586
|
+
// the dialect is *detected* from the dependency manifest (pg/neon → postgres,
|
|
587
|
+
// better-sqlite3/D1 → sqlite, mysql2 → mysql). A null dialect means the SQL
|
|
588
|
+
// facts `cannot-fire` — and `sqlDialectReason` names *why* (undetermined or
|
|
589
|
+
// ambiguous) so the abstention is visible rather than silent.
|
|
590
|
+
const resolution = buildResolutionEnvironment(root, {
|
|
591
|
+
databaseType: mergedOptions.databaseType,
|
|
592
|
+
importVirtualModules: mergedOptions.importVirtualModules,
|
|
593
|
+
});
|
|
594
|
+
const sqlDialect = resolution.sqlDialect;
|
|
595
|
+
const sqlDialectReason = resolution.sqlDialectReason;
|
|
596
|
+
// Spec 62 Amendment B — the missing-org-filter Stage-4 reducer reads the
|
|
597
|
+
// data-access config namespace, so it inherits the data-access analyzer's
|
|
598
|
+
// config rather than a fresh empty namespace.
|
|
599
|
+
if (analyzers.includes('data-access')) {
|
|
600
|
+
pipelineAnalyzerConfig['data-access'] = {
|
|
601
|
+
...(pipelineAnalyzerConfig['data-access'] ?? {}),
|
|
602
|
+
dialect: sqlDialect,
|
|
603
|
+
};
|
|
604
|
+
pipelineAnalyzerConfig['data-access-org-filter'] = { ...(pipelineAnalyzerConfig['data-access'] ?? {}) };
|
|
605
|
+
}
|
|
606
|
+
// Pass invariant rules from .codeauditor.json into the invariants pipeline config.
|
|
607
|
+
if (analyzers.includes('invariants') && mergedOptions.rules) {
|
|
608
|
+
pipelineAnalyzerConfig['invariants'] = {
|
|
609
|
+
...(pipelineAnalyzerConfig['invariants'] ?? {}),
|
|
610
|
+
rules: mergedOptions.rules,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
// Cross-domain config lives at the top level (not analyzerConfigs)
|
|
614
|
+
if (analyzers.includes('cross-domain') && mergedOptions.crossDomain) {
|
|
615
|
+
pipelineAnalyzerConfig['cross-domain'] = {
|
|
616
|
+
...(pipelineAnalyzerConfig['cross-domain'] ?? {}),
|
|
617
|
+
...mergedOptions.crossDomain,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
// Schema config: all table discovery flows through visitors and facts.
|
|
621
|
+
if (analyzers.includes('schema')) {
|
|
622
|
+
const scConfig = mergedOptions.analyzerConfigs?.schema ?? {};
|
|
623
|
+
const schemaConfig = {
|
|
624
|
+
...(pipelineAnalyzerConfig['schema'] ?? {}),
|
|
625
|
+
sqlTagNames: scConfig.sqlTagNames ?? ['sql', 'db'],
|
|
626
|
+
dbBindingNames: scConfig.dbBindingNames ?? ['env.DB'],
|
|
627
|
+
fileGateGlobs: scConfig.fileGateGlobs,
|
|
628
|
+
maxQueriesPerFunction: scConfig.maxQueriesPerFunction,
|
|
629
|
+
validateQueryPatterns: scConfig.validateQueryPatterns,
|
|
630
|
+
checkNamingConventions: scConfig.checkNamingConventions,
|
|
631
|
+
tableSources: scConfig.tableSources,
|
|
632
|
+
dbWrapperNames: scConfig.dbWrapperNames,
|
|
633
|
+
detection: scConfig.detection,
|
|
634
|
+
schemas: scConfig.schemas,
|
|
635
|
+
knownTables: scConfig.knownTables,
|
|
636
|
+
// Spec 70 R2 — thread the corpus's named dialect so a provenanced static
|
|
637
|
+
// SQL argument that the dialect cannot parse (PRAGMA/VACUUM/ANALYZE) is
|
|
638
|
+
// reported as unreadable (cannot-fire), not silently "no tables".
|
|
639
|
+
sqlDialect,
|
|
640
|
+
// Spec 70 (detection) — the named reason when the dialect is null, so an
|
|
641
|
+
// undetermined/ambiguous corpus emits a "dialect undetermined" cannot-fire
|
|
642
|
+
// rather than reading the empty reference set as clean.
|
|
643
|
+
sqlDialectReason,
|
|
644
|
+
};
|
|
645
|
+
pipelineAnalyzerConfig['schema'] = schemaConfig;
|
|
646
|
+
// Pipeline resolves config by visitor name; the schema-* sub-visitors need
|
|
647
|
+
// the same namespace as the schema reducer.
|
|
648
|
+
for (const sub of ['schema-code', 'schema-sql', 'schema-prisma', 'schema-json']) {
|
|
649
|
+
pipelineAnalyzerConfig[sub] = schemaConfig;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// Global/infrastructure settings passed to all pipeline stages
|
|
653
|
+
pipelineAnalyzerConfig['_infra'] = {
|
|
654
|
+
pathProfiles: mergedOptions.pathProfiles,
|
|
655
|
+
projectRoot: root,
|
|
656
|
+
_provenanceTiming: provenanceTiming,
|
|
657
|
+
files,
|
|
658
|
+
corpusFiles,
|
|
659
|
+
importVirtualModules: resolution.importVirtualModules,
|
|
660
|
+
tsconfigAliases: resolution.tsconfigAliases,
|
|
661
|
+
packageEntryPoints: resolution.packageEntryPoints,
|
|
662
|
+
declaredTypePackages: resolution.declaredTypePackages,
|
|
663
|
+
// Spec 70 R1 — surfaced under `_infra` so `runPipeline` can hand the named
|
|
664
|
+
// dialect to the phase model's data-access producer without reading the
|
|
665
|
+
// data-access namespace.
|
|
666
|
+
sqlDialect,
|
|
667
|
+
// Spec 70 (detection) — the named reason when `sqlDialect` is null.
|
|
668
|
+
sqlDialectReason,
|
|
669
|
+
};
|
|
670
|
+
return pipelineAnalyzerConfig;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Assemble the `PipelineConfig` handed to `runPipeline`.
|
|
674
|
+
*/
|
|
675
|
+
function buildPipelineConfig(inputs) {
|
|
676
|
+
const { mergedOptions, root, files, isScoped, fileAccounting, styleConsumedFiles, styleContributingFiles, analyzers, auditIndex, pipelineAnalyzerConfig, pipelineVisitors, pipelineReducers, pipelineDerivedReducers, } = inputs;
|
|
677
|
+
return {
|
|
678
|
+
projectRoot: root,
|
|
679
|
+
explicitFiles: files,
|
|
680
|
+
visitors: pipelineVisitors,
|
|
681
|
+
reducers: pipelineReducers,
|
|
682
|
+
derivedReducers: pipelineDerivedReducers,
|
|
683
|
+
config: pipelineAnalyzerConfig,
|
|
684
|
+
abortSignal: mergedOptions.abortSignal,
|
|
685
|
+
progressCallback: (progress) => {
|
|
686
|
+
throwIfAborted(mergedOptions.abortSignal);
|
|
687
|
+
reportProgress(mergedOptions, progress);
|
|
688
|
+
},
|
|
689
|
+
isScoped,
|
|
690
|
+
fileAccounting,
|
|
691
|
+
consumedFilePaths: styleConsumedFiles,
|
|
692
|
+
styleContributingFiles,
|
|
693
|
+
onStage2Complete: async () => {
|
|
694
|
+
// Post-stage-2 setup: rebuild the call-graph from the functions table
|
|
695
|
+
// (populated by the function-index visitor).
|
|
696
|
+
if (auditIndex && analyzers.includes('conventions')) {
|
|
294
697
|
try {
|
|
295
|
-
|
|
296
|
-
throwIfAborted(mergedOptions.abortSignal);
|
|
297
|
-
}
|
|
298
|
-
if (i > 0 && i % 50 === 0) {
|
|
299
|
-
logMcpInfo('function-indexing', 'progress', {
|
|
300
|
-
current: i,
|
|
301
|
-
total: scriptFiles.length
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
const fileFunctions = await extractFunctionsFromFile(scriptFiles[i], {
|
|
305
|
-
unusedImportsConfig: mergedOptions.unusedImportsConfig
|
|
306
|
-
});
|
|
307
|
-
collectedFunctions.push(...fileFunctions);
|
|
308
|
-
fileToFunctionsMap.set(scriptFiles[i], fileFunctions); // Store for sync
|
|
309
|
-
reportProgress(mergedOptions, {
|
|
310
|
-
phase: 'function-indexing',
|
|
311
|
-
current: i + 1,
|
|
312
|
-
total: scriptFiles.length,
|
|
313
|
-
message: `Collected ${fileFunctions.length} items from ${scriptFiles[i]}`
|
|
314
|
-
});
|
|
315
|
-
if (isMcpDebugEnabled()) {
|
|
316
|
-
logMcpDebug('function-indexing', scriptFiles[i], {
|
|
317
|
-
symbols: fileFunctions.length
|
|
318
|
-
});
|
|
319
|
-
}
|
|
698
|
+
await auditIndex.graph.updateDependencyGraph();
|
|
320
699
|
}
|
|
321
|
-
catch (
|
|
322
|
-
logMcpInfo('
|
|
323
|
-
|
|
324
|
-
error: error instanceof Error ? error.message : String(error)
|
|
700
|
+
catch (err) {
|
|
701
|
+
logMcpInfo('analysis', 'updateDependencyGraph failed (non-fatal)', {
|
|
702
|
+
error: err instanceof Error ? err.message : String(err)
|
|
325
703
|
});
|
|
326
704
|
}
|
|
327
705
|
}
|
|
706
|
+
},
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Run the both-paths split (Spec 68 §11.1): strip migrated rules' legacy
|
|
711
|
+
* emission and re-emit phase findings into the correct analyzer buckets.
|
|
712
|
+
* Mutates `analyzerResults` and `pipelineAnalyzerConfig` (dry divergence).
|
|
713
|
+
*/
|
|
714
|
+
async function applyPhaseModelSplit(inputs) {
|
|
715
|
+
const { mergedOptions, root, files, isScoped, pipelineAnalyzerConfig, analyzerResults, pipelineIndexHandle } = inputs;
|
|
716
|
+
let phaseFindings = [];
|
|
717
|
+
let phaseIncompleteFacts = new Map();
|
|
718
|
+
let routeAttribution;
|
|
719
|
+
let receiverDiagnostics = [];
|
|
720
|
+
const { migrated } = splitRoutes();
|
|
721
|
+
routeAttribution = Object.fromEntries(attributeRoutes());
|
|
722
|
+
if (migrated.size > 0) {
|
|
723
|
+
// The `dry/diverging-clone` rule reads its knobs from `dry.divergence`.
|
|
724
|
+
const topLevelDivergence = mergedOptions.divergence;
|
|
725
|
+
if (topLevelDivergence && !pipelineAnalyzerConfig['dry']?.divergence) {
|
|
726
|
+
pipelineAnalyzerConfig['dry'] = { ...(pipelineAnalyzerConfig['dry'] ?? {}), divergence: topLevelDivergence };
|
|
328
727
|
}
|
|
329
|
-
|
|
330
|
-
const
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// In-scope files that contributed style data during the sync. Undefined when
|
|
340
|
-
// the sync did not run or failed — the styles reducer must not short-circuit
|
|
341
|
-
// on that (it falls back to full analysis), so we only assign on success.
|
|
342
|
-
let styleContributingFiles;
|
|
343
|
-
if (enabledAnalyzers.includes('styles')) {
|
|
344
|
-
try {
|
|
345
|
-
const styleDb = CodeIndexDB.getInstance(undefined, root);
|
|
346
|
-
await styleDb.initialize();
|
|
347
|
-
const styleSyncResult = await syncStyleIndex(styleDb.rawDb, files, root, { scoped: isScoped });
|
|
348
|
-
styleConsumedFiles = styleSyncResult.consumedFiles;
|
|
349
|
-
styleContributingFiles = styleSyncResult.contributingFiles;
|
|
350
|
-
logMcpInfo('style-index', 'style index sync complete', {
|
|
351
|
-
changed: styleSyncResult.changed,
|
|
352
|
-
skipped: styleSyncResult.skipped,
|
|
353
|
-
removed: styleSyncResult.removed,
|
|
354
|
-
errors: styleSyncResult.errors,
|
|
355
|
-
consumed: styleSyncResult.consumedFiles.length,
|
|
356
|
-
});
|
|
728
|
+
const thresholds = resolvePhaseThresholds(pipelineAnalyzerConfig);
|
|
729
|
+
const profiles = mergedOptions.pathProfiles;
|
|
730
|
+
const profileByFile = new Map();
|
|
731
|
+
const resolveFileProfile = (file) => {
|
|
732
|
+
let resolved = profileByFile.get(file);
|
|
733
|
+
if (!resolved) {
|
|
734
|
+
resolved = profiles?.length
|
|
735
|
+
? resolvePathProfile(file, root, profiles)
|
|
736
|
+
: { overrides: {}, excludeFromGate: false, excludeFromAnalysis: false, matchedProfileNames: [] };
|
|
737
|
+
profileByFile.set(file, resolved);
|
|
357
738
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
739
|
+
return resolved;
|
|
740
|
+
};
|
|
741
|
+
const phaseFiles = files.filter((f) => !resolveFileProfile(f).excludeFromAnalysis);
|
|
742
|
+
const infraConfig = (pipelineAnalyzerConfig['_infra'] ?? {});
|
|
743
|
+
const schemaPhaseConfig = (pipelineAnalyzerConfig['schema'] ?? {});
|
|
744
|
+
const externalTables = [];
|
|
745
|
+
for (const t of schemaPhaseConfig.knownTables ?? []) {
|
|
746
|
+
externalTables.push({ name: t, source: 'external-config', columns: [] });
|
|
747
|
+
}
|
|
748
|
+
for (const s of schemaPhaseConfig.schemas ?? []) {
|
|
749
|
+
for (const t of s.tables) {
|
|
750
|
+
externalTables.push({ name: t.name, source: `Schema: ${s.name}`, columns: t.columns.map((c) => c.name) });
|
|
363
751
|
}
|
|
364
752
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
753
|
+
const phaseResult = await runPhaseModel(phaseFiles, thresholds, {
|
|
754
|
+
projectRoot: root,
|
|
755
|
+
scoped: isScoped,
|
|
756
|
+
corpusFiles: infraConfig.corpusFiles,
|
|
757
|
+
importVirtualModules: infraConfig.importVirtualModules,
|
|
758
|
+
tsconfigAliases: infraConfig.tsconfigAliases,
|
|
759
|
+
packageEntryPoints: infraConfig.packageEntryPoints,
|
|
760
|
+
indexHandle: pipelineIndexHandle,
|
|
761
|
+
enabledRules: enabledMigratedRules(),
|
|
762
|
+
externalTables,
|
|
763
|
+
workerCount: resolveWorkerCount(),
|
|
764
|
+
sqlDialect: infraConfig.sqlDialect,
|
|
765
|
+
sqlDialectReason: infraConfig.sqlDialectReason,
|
|
766
|
+
declaredTypePackages: infraConfig.declaredTypePackages,
|
|
767
|
+
// Spec 70 2b — the diverging-clone write: seed `dry_pair_history` from the
|
|
768
|
+
// phase `code-block` fact (replacing `createDryVisitor`/`persistDryPairs`).
|
|
769
|
+
persistDryPairHistory: (facts) => {
|
|
770
|
+
const dryCfg = resolveBlockConfig(thresholds.get('dry/duplicate') ?? {});
|
|
771
|
+
const codeBlocks = facts.get('code-block') ?? [];
|
|
772
|
+
return persistDryPairs(seedDryPairs(codeBlocks, dryCfg), root);
|
|
773
|
+
},
|
|
385
774
|
});
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
//
|
|
396
|
-
let
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
775
|
+
phaseFindings = phaseResult.findings;
|
|
776
|
+
phaseIncompleteFacts = phaseResult.incompleteFacts;
|
|
777
|
+
// Spec 69 §10 + Part 2b — re-home the pre-pass's diagnostics (deleted from
|
|
778
|
+
// `pipeline.ts`) on the phase result. Cannot-fire: re-derive per file from
|
|
779
|
+
// `phaseResult.unprovenQueryReceivers` + `phaseResult.unresolvedImports`, keyed
|
|
780
|
+
// to the TS-family surface the legacy `schema-code` visitor covered (Go
|
|
781
|
+
// receivers/imports never reached it). Manifest-stale: the ecosystem-list
|
|
782
|
+
// self-check, unchanged from the deleted `buildPipelineResult` loop.
|
|
783
|
+
receiverDiagnostics = await buildReceiverDiagnostics(phaseResult.unprovenQueryReceivers, phaseResult.unresolvedImports, phaseResult.unresolvedQuerySites, phaseResult.unparseableSql, root);
|
|
784
|
+
// Strip the migrated rules' legacy emission from every analyzer result.
|
|
785
|
+
let strippedCount = 0;
|
|
786
|
+
for (const result of Object.values(analyzerResults)) {
|
|
787
|
+
const before = result.violations.length;
|
|
788
|
+
result.violations = result.violations.filter((v) => !migrated.has(v.rule));
|
|
789
|
+
strippedCount += before - result.violations.length;
|
|
401
790
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
791
|
+
// Re-emit phase findings as violations, bucketed by the rule's analyzer.
|
|
792
|
+
const byAnalyzer = new Map();
|
|
793
|
+
const ruleAnalyzerOverride = {
|
|
794
|
+
'table-naming-convention': 'schema-code',
|
|
795
|
+
'too-many-queries': 'schema-code',
|
|
796
|
+
};
|
|
797
|
+
for (const f of phaseFindings) {
|
|
798
|
+
const analyzer = ruleAnalyzerOverride[f.ruleId] ?? RULE_ANALYZER.get(f.ruleId) ?? 'phase';
|
|
799
|
+
const resolved = resolveFileProfile(f.file);
|
|
800
|
+
const profile = resolved.matchedProfileNames.length > 0
|
|
801
|
+
? resolved.matchedProfileNames[resolved.matchedProfileNames.length - 1]
|
|
802
|
+
: undefined;
|
|
803
|
+
const bucket = byAnalyzer.get(analyzer) ?? [];
|
|
804
|
+
bucket.push({
|
|
805
|
+
file: f.file,
|
|
806
|
+
rule: f.ruleId,
|
|
807
|
+
severity: f.severity,
|
|
808
|
+
message: f.message,
|
|
809
|
+
line: f.line,
|
|
810
|
+
column: f.column,
|
|
811
|
+
symbol: f.symbol,
|
|
812
|
+
resolution: f.resolution,
|
|
813
|
+
analyzer,
|
|
814
|
+
...(f.fix ? { fix: f.fix } : {}),
|
|
815
|
+
...(f.details !== undefined ? { details: f.details } : {}),
|
|
816
|
+
...(profile ? { profile } : {}),
|
|
817
|
+
...(resolved.excludeFromGate ? { gateExcluded: true } : {}),
|
|
407
818
|
});
|
|
819
|
+
byAnalyzer.set(analyzer, bucket);
|
|
408
820
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
query: (sql, params) => auditIndex.query(sql, params),
|
|
420
|
-
count: (table, where, params) => auditIndex.count(table, where, params),
|
|
421
|
-
tableHasRows: (table) => auditIndex.tableHasRows(table),
|
|
422
|
-
run: (sql, params) => auditIndex.rawDb.prepare(sql).run(...(params ?? [])),
|
|
423
|
-
exec: (sql) => auditIndex.rawDb.exec(sql),
|
|
424
|
-
getMeta: (key) => auditIndex.getMeta(key),
|
|
425
|
-
getUntestedTopDecile: (td) => auditIndex.getUntestedTopDecile(td),
|
|
426
|
-
rawDb: auditIndex.rawDb,
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
// ── 1. Build pipeline adapters ───────────────────────────────────────
|
|
430
|
-
const pipelineVisitors = [];
|
|
431
|
-
const pipelineReducers = [];
|
|
432
|
-
const pipelineDerivedReducers = [];
|
|
433
|
-
let dryBundle;
|
|
434
|
-
let reactBundle;
|
|
435
|
-
let solidBundle;
|
|
436
|
-
// Always-on infrastructure: function-index visitor populates the
|
|
437
|
-
// `functions` table so conventions + cross-domain reducers have data
|
|
438
|
-
// even on a cold run with no prior index sync.
|
|
439
|
-
pipelineVisitors.push(createFunctionIndexVisitor());
|
|
440
|
-
// styles-css visitor — AST-extracts .css files into style_* tables (Spec 26 Phase 2)
|
|
441
|
-
if (enabledAnalyzers.includes('styles'))
|
|
442
|
-
pipelineVisitors.push(createStylesCssVisitor());
|
|
443
|
-
// styles-source visitor — AST-extracts TS/JS CSS-in-JS into style_* tables,
|
|
444
|
-
// reusing the stage-1 parse (eliminates the style-index re-parse).
|
|
445
|
-
if (enabledAnalyzers.includes('styles'))
|
|
446
|
-
pipelineVisitors.push(createStylesSourceVisitor());
|
|
447
|
-
if (enabledAnalyzers.includes('solid')) {
|
|
448
|
-
solidBundle = createSolidVisitor();
|
|
449
|
-
pipelineVisitors.push(solidBundle.visitor);
|
|
821
|
+
for (const [analyzer, violations] of byAnalyzer) {
|
|
822
|
+
if (analyzerResults[analyzer]) {
|
|
823
|
+
analyzerResults[analyzer].violations.push(...violations);
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
analyzerResults[analyzer] = {
|
|
827
|
+
...makeEmptyAnalyzerResult(analyzer, phaseFiles.length),
|
|
828
|
+
violations,
|
|
829
|
+
};
|
|
830
|
+
}
|
|
450
831
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
832
|
+
// Every migrated analyzer must appear in the results map even when it
|
|
833
|
+
// emitted nothing this run.
|
|
834
|
+
for (const analyzer of new Set(RULE_ANALYZER.values())) {
|
|
835
|
+
if (!analyzerResults[analyzer]) {
|
|
836
|
+
analyzerResults[analyzer] = makeEmptyAnalyzerResult(analyzer, phaseFiles.length);
|
|
837
|
+
}
|
|
454
838
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
839
|
+
logMcpInfo('analysis', 'phase model (both paths)', {
|
|
840
|
+
migrated: migrated.size,
|
|
841
|
+
stripped: strippedCount,
|
|
842
|
+
reemitted: phaseFindings.length,
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
return { phaseFindings, phaseIncompleteFacts, routeAttribution, receiverDiagnostics };
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Spec 69 §10 + Part 2b — re-derive the pre-pass's coverage diagnostics from the
|
|
849
|
+
* phase result: the `cannot-fire` surface (unproven query receivers + unresolved
|
|
850
|
+
* DB-looking imports, keyed to the TS-family files the legacy `schema-code`
|
|
851
|
+
* visitor covered) and the `manifest-stale` ecosystem-list self-check. Replaces
|
|
852
|
+
* `collectCannotFireDiagnostics` (deleted from `pipelineAdapters.ts`) and the
|
|
853
|
+
* `manifest-staleness` loop (deleted from `buildPipelineResult`).
|
|
854
|
+
*/
|
|
855
|
+
async function buildReceiverDiagnostics(unprovenQueryReceivers, unresolvedImports, unresolvedQuerySites, unparseableSql, projectRoot) {
|
|
856
|
+
const diagnostics = [];
|
|
857
|
+
// Group the two signals by file, then per-file dedup so the call-site signal
|
|
858
|
+
// wins over the import-level signal — mirroring the deleted
|
|
859
|
+
// `collectCannotFireDiagnostics` (which ran per TS-family file inside the
|
|
860
|
+
// schema-code visitor, so Go files never reached it).
|
|
861
|
+
const files = new Set();
|
|
862
|
+
for (const u of unprovenQueryReceivers)
|
|
863
|
+
if (!u.file.endsWith('.go'))
|
|
864
|
+
files.add(u.file);
|
|
865
|
+
for (const u of unresolvedImports)
|
|
866
|
+
if (!u.importer.endsWith('.go'))
|
|
867
|
+
files.add(u.importer);
|
|
868
|
+
for (const file of files) {
|
|
869
|
+
const perFile = [];
|
|
870
|
+
const unresolvedHere = unresolvedImports.filter((u) => path.resolve(u.importer) === path.resolve(file));
|
|
871
|
+
if (unresolvedHere.length > 0) {
|
|
872
|
+
perFile.push(...checkUnresolvedReceiverImports(unresolvedHere.map((u) => ({ source: u.source, names: [...u.names] })), file));
|
|
462
873
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
pipelineVisitors.push(createSecurityVisitor());
|
|
467
|
-
if (enabledAnalyzers.includes('react')) {
|
|
468
|
-
reactBundle = createReactVisitor();
|
|
469
|
-
pipelineVisitors.push(reactBundle.visitor);
|
|
874
|
+
const unprovenHere = unprovenQueryReceivers.filter((u) => path.resolve(u.file) === path.resolve(file));
|
|
875
|
+
if (unprovenHere.length > 0) {
|
|
876
|
+
perFile.push(...checkUnprovenQueryReceivers(unprovenHere.map((u) => ({ receiver: u.receiver, method: u.method, line: u.line, reason: u.reason })), file));
|
|
470
877
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
pipelineVisitors.push(createSchemaJsonVisitor());
|
|
486
|
-
pipelineReducers.push(createSchemaReducer());
|
|
878
|
+
diagnostics.push(...dedupeCannotFireByReceiver(perFile));
|
|
879
|
+
}
|
|
880
|
+
// Spec 70 1b — the `unresolved-query` diagnostics (re-admitted DB-calls whose
|
|
881
|
+
// SQL is held in an unresolvable identifier). Emitted separately from
|
|
882
|
+
// `cannot-fire`: a distinct kind, never receiver-deduped (the legacy
|
|
883
|
+
// `schema-code` visitor emitted them verbatim, per file, in visitor order).
|
|
884
|
+
const unresolvedByFile = new Map();
|
|
885
|
+
for (const site of unresolvedQuerySites) {
|
|
886
|
+
if (site.file.endsWith('.go'))
|
|
887
|
+
continue;
|
|
888
|
+
let list = unresolvedByFile.get(site.file);
|
|
889
|
+
if (!list) {
|
|
890
|
+
list = [];
|
|
891
|
+
unresolvedByFile.set(site.file, list);
|
|
487
892
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
893
|
+
list.push(site);
|
|
894
|
+
}
|
|
895
|
+
for (const [file, sites] of unresolvedByFile) {
|
|
896
|
+
diagnostics.push(...checkUnresolvedQueries(sites.map((s) => ({ identifier: s.identifier, location: s.location })), file));
|
|
897
|
+
}
|
|
898
|
+
// Spec 70 R2 — the `unparseable` cannot-fire diagnostics. Emitted per file in
|
|
899
|
+
// fact order, mirroring the deleted legacy `schema-code` visitor, which ran
|
|
900
|
+
// `checkUnparseableSql` over every admitted tag/DB-call parse within the file.
|
|
901
|
+
const unparseableByFile = new Map();
|
|
902
|
+
for (const u of unparseableSql) {
|
|
903
|
+
if (u.file.endsWith('.go'))
|
|
904
|
+
continue;
|
|
905
|
+
let list = unparseableByFile.get(u.file);
|
|
906
|
+
if (!list) {
|
|
907
|
+
list = [];
|
|
908
|
+
unparseableByFile.set(u.file, list);
|
|
497
909
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
910
|
+
list.push(u);
|
|
911
|
+
}
|
|
912
|
+
for (const [file, records] of unparseableByFile) {
|
|
913
|
+
diagnostics.push(...checkUnparseableSql(records, file));
|
|
914
|
+
}
|
|
915
|
+
// Part 2b — the manifest staleness self-check, unchanged from the deleted
|
|
916
|
+
// `buildPipelineResult` loop: our hardcoded ecosystem lists vs. the project's
|
|
917
|
+
// manifest, emitted as a diagnostic (never a verdict input).
|
|
918
|
+
const manifest = await readProjectManifest(projectRoot);
|
|
919
|
+
for (const stale of computeManifestStaleness(manifest)) {
|
|
920
|
+
diagnostics.push({
|
|
921
|
+
analyzerName: 'data-access',
|
|
922
|
+
kind: 'manifest-stale',
|
|
923
|
+
message: `our list names '${stale.package}', this project doesn't depend on it`,
|
|
924
|
+
file: stale.manifestPath,
|
|
925
|
+
line: 0,
|
|
926
|
+
details: { ecosystem: stale.ecosystem, package: stale.package },
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
return diagnostics;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Persist seeded DRY pairs into `dry_pair_history` (Spec 13 R5 Phase 1) so the
|
|
933
|
+
* migrated `dry/diverging-clone` rule can read them through the index handle.
|
|
934
|
+
* The pairs come from `seedDryPairs` over the phase `code-block` fact (Spec 70
|
|
935
|
+
* 2b — the writer moved off the legacy DRY visitor). Runs BEFORE the phase
|
|
936
|
+
* model's `clone-pair-history` read. Advisory — non-fatal on failure.
|
|
937
|
+
* @param dryPairs The seeded DRY pairs to persist into `dry_pair_history`.
|
|
938
|
+
* @param root The project root used to open the code-index database handle.
|
|
939
|
+
* @returns Resolves when the advisory write completes (never rejects).
|
|
940
|
+
*/
|
|
941
|
+
export async function persistDryPairs(dryPairs, root) {
|
|
942
|
+
try {
|
|
943
|
+
if (dryPairs.length > 0) {
|
|
944
|
+
const indexDb = CodeIndexDB.getInstance(undefined, root);
|
|
945
|
+
await indexDb.initialize();
|
|
946
|
+
const dryPersistRunId = randomUUID();
|
|
947
|
+
const insertStmt = indexDb.rawDb.prepare(`
|
|
948
|
+
INSERT OR IGNORE INTO dry_pair_history
|
|
949
|
+
(pair_fingerprint, file1, symbol1, line1, content_hash1,
|
|
950
|
+
file2, symbol2, line2, content_hash2, similarity, timestamp, run_id)
|
|
951
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), ?)
|
|
952
|
+
`);
|
|
953
|
+
const tx = indexDb.rawDb.transaction(() => {
|
|
954
|
+
for (const pair of dryPairs) {
|
|
955
|
+
insertStmt.run(pair.pairFingerprint, pair.file1, pair.symbol1, pair.line1, pair.contentHash1, pair.file2, pair.symbol2, pair.line2, pair.contentHash2, pair.similarity, dryPersistRunId);
|
|
512
956
|
}
|
|
513
|
-
}
|
|
957
|
+
});
|
|
958
|
+
tx();
|
|
514
959
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
535
|
-
// Pass invariant rules from .codeauditor.json into the invariants pipeline config.
|
|
536
|
-
// The rules field lives at the top level of the loaded config (not under analyzerConfigs).
|
|
537
|
-
if (enabledAnalyzers.includes('invariants') && mergedOptions.rules) {
|
|
538
|
-
pipelineAnalyzerConfig['invariants'] = {
|
|
539
|
-
...(pipelineAnalyzerConfig['invariants'] ?? {}),
|
|
540
|
-
rules: mergedOptions.rules,
|
|
541
|
-
};
|
|
960
|
+
}
|
|
961
|
+
catch {
|
|
962
|
+
// dry_pair_history persistence is advisory — non-fatal
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Run the React analyzer's cross-component finalization (require-error-boundary
|
|
967
|
+
* / raw-element checks), appending extra violations into the `react` result.
|
|
968
|
+
*/
|
|
969
|
+
async function finalizeReact(reactBundle, analyzerResults, pipelineAnalyzerConfig) {
|
|
970
|
+
if (reactBundle) {
|
|
971
|
+
try {
|
|
972
|
+
const reactResult = analyzerResults['react'];
|
|
973
|
+
if (reactResult) {
|
|
974
|
+
// Pass the react namespace (not the whole namespaced config) so
|
|
975
|
+
// finalize reads `requireErrorBoundaries`/`rawElementCheck` at the
|
|
976
|
+
// same level the visitor's `context.config` does.
|
|
977
|
+
const extraViolations = await reactBundle.finalizeCrossComponent(pipelineAnalyzerConfig['react'] ?? {});
|
|
978
|
+
reactResult.violations.push(...extraViolations);
|
|
542
979
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
980
|
+
}
|
|
981
|
+
catch (err) {
|
|
982
|
+
logMcpInfo('analysis', 'react finalization failed (non-fatal)', {
|
|
983
|
+
error: err instanceof Error ? err.message : String(err)
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Build the pipeline, run it, and fold its outputs — plus the both-paths phase
|
|
990
|
+
* split and derived coverage — into a single result bundle for `run`.
|
|
991
|
+
*/
|
|
992
|
+
async function runPipelineStage(inputs) {
|
|
993
|
+
const { mergedOptions, root, files, corpusFiles, isScoped, fileAccounting, styleConsumedFiles, styleContributingFiles, analyzers, provenanceTiming, } = inputs;
|
|
994
|
+
const analyzerResults = {};
|
|
995
|
+
let indexFactsWritten = 0;
|
|
996
|
+
let writeIndexFactsMs = 0;
|
|
997
|
+
const { auditIndex, pipelineIndexHandle, indexSkipDiagnostic } = await initializeAuditIndex(root);
|
|
998
|
+
const { pipelineVisitors, pipelineReducers, pipelineDerivedReducers, reactBundle, solidBundle, } = buildPipelineAdapters(analyzers);
|
|
999
|
+
// ── Safeguard warnings ────────────────────────────────────────────────
|
|
1000
|
+
if (auditIndex && analyzers.includes('cross-domain')) {
|
|
1001
|
+
const suCount = auditIndex.rawSql.count('schema_usage');
|
|
1002
|
+
if (suCount === 0) {
|
|
1003
|
+
console.warn('[code-audit] ⚠ cross-domain analyzer requires schema_usage data. '
|
|
1004
|
+
+ 'This is populated during a full audit run by the schema analyzer. '
|
|
1005
|
+
+ 'If this warning persists, run a full "code-audit audit --path ." first.');
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
const hasPipelineAnalyzers = pipelineVisitors.length > 0
|
|
1009
|
+
|| pipelineReducers.length > 0
|
|
1010
|
+
|| pipelineDerivedReducers.length > 0;
|
|
1011
|
+
// Outputs hoisted so derived coverage (after the split) can read them.
|
|
1012
|
+
let phaseFindings = [];
|
|
1013
|
+
let phaseIncompleteFacts = new Map();
|
|
1014
|
+
let pipelineCoverage;
|
|
1015
|
+
let pipelineTableCatalog;
|
|
1016
|
+
let pipelineStageTiming;
|
|
1017
|
+
let pipelineSkippedFiles;
|
|
1018
|
+
let pipelineUnparsedFiles;
|
|
1019
|
+
let pipelineInputPresence;
|
|
1020
|
+
let pipelineRuleTiming;
|
|
1021
|
+
let pipelineFileAccounting;
|
|
1022
|
+
let pipelineDiagnostics;
|
|
1023
|
+
let pipelineTestCoverage;
|
|
1024
|
+
let pipelineDeadClusters;
|
|
1025
|
+
let pipelineSizeDistributions;
|
|
1026
|
+
let routeAttribution;
|
|
1027
|
+
let receiverDiagnostics = [];
|
|
1028
|
+
if (hasPipelineAnalyzers) {
|
|
1029
|
+
const pipelineAnalyzerConfig = buildPipelineAnalyzerConfig(analyzers, mergedOptions, root, files, corpusFiles, provenanceTiming);
|
|
1030
|
+
const pipelineConfig = buildPipelineConfig({
|
|
1031
|
+
mergedOptions, root, files, isScoped, fileAccounting,
|
|
1032
|
+
styleConsumedFiles, styleContributingFiles, analyzers,
|
|
1033
|
+
auditIndex, pipelineAnalyzerConfig, pipelineVisitors, pipelineReducers,
|
|
1034
|
+
pipelineDerivedReducers,
|
|
1035
|
+
});
|
|
1036
|
+
try {
|
|
1037
|
+
logMcpInfo('analysis', 'running pipeline', { visitorCount: pipelineVisitors.length, reducerCount: pipelineReducers.length, derivedReducerCount: pipelineDerivedReducers.length, fileCount: files.length });
|
|
1038
|
+
const pipelineResult = await runPipeline(pipelineConfig, pipelineIndexHandle);
|
|
1039
|
+
// §6.3 — the parent is the only index writer; surface its count and
|
|
1040
|
+
// wall-clock via stageTiming.
|
|
1041
|
+
const stageWriteCount = pipelineResult.metadata.stageTiming?.['index-fact-write-count'];
|
|
1042
|
+
const stageWriteMs = pipelineResult.metadata.stageTiming?.['index-fact-write'];
|
|
1043
|
+
if (typeof stageWriteCount === 'number')
|
|
1044
|
+
indexFactsWritten = stageWriteCount;
|
|
1045
|
+
if (typeof stageWriteMs === 'number')
|
|
1046
|
+
writeIndexFactsMs = stageWriteMs;
|
|
1047
|
+
// Pull in pipeline results
|
|
1048
|
+
for (const [name, ar] of Object.entries(pipelineResult.analyzerResults)) {
|
|
1049
|
+
analyzerResults[name] = ar;
|
|
1050
|
+
logMcpDebug('analysis', `pipeline: ${name} completed`, {
|
|
1051
|
+
violations: ar.violations?.length ?? 0,
|
|
1052
|
+
status: ar.status?.status,
|
|
1053
|
+
});
|
|
549
1054
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
1055
|
+
await finalizeReact(reactBundle, analyzerResults, pipelineAnalyzerConfig);
|
|
1056
|
+
const split = await applyPhaseModelSplit({
|
|
1057
|
+
mergedOptions, root, files, isScoped, pipelineAnalyzerConfig, analyzerResults, pipelineIndexHandle,
|
|
1058
|
+
});
|
|
1059
|
+
phaseFindings = split.phaseFindings;
|
|
1060
|
+
phaseIncompleteFacts = split.phaseIncompleteFacts;
|
|
1061
|
+
routeAttribution = split.routeAttribution;
|
|
1062
|
+
receiverDiagnostics = split.receiverDiagnostics;
|
|
1063
|
+
// Spec 68 §8 — derived coverage, computed AFTER the split so phaseFindings
|
|
1064
|
+
// holds the migrated rules' re-emitted findings.
|
|
1065
|
+
pipelineCoverage = deriveCoverage({
|
|
1066
|
+
rules: MIGRATED_RULES,
|
|
1067
|
+
findings: phaseFindings,
|
|
1068
|
+
presentFormats: presentFormatsOf(files),
|
|
1069
|
+
enabledRules: enabledMigratedRules(),
|
|
1070
|
+
incompleteFacts: phaseIncompleteFacts,
|
|
1071
|
+
groupOf: (ruleId) => RULE_ANALYZER.get(ruleId) ?? ruleId,
|
|
1072
|
+
});
|
|
1073
|
+
// Spec 29: extract table catalog from pipeline metadata for audit report
|
|
1074
|
+
pipelineTableCatalog = pipelineResult.metadata?.tableCatalog;
|
|
1075
|
+
pipelineStageTiming = pipelineResult.metadata?.stageTiming;
|
|
1076
|
+
pipelineSkippedFiles = pipelineResult.metadata?.skippedFiles;
|
|
1077
|
+
pipelineUnparsedFiles = pipelineResult.metadata?.unparsedFiles;
|
|
1078
|
+
pipelineInputPresence = pipelineResult.metadata?.inputPresence;
|
|
1079
|
+
// Spec 38 R2 — read the live accumulator rather than the `runPipeline`
|
|
1080
|
+
// snapshot: the phase model's `analyzeAll` records its per-rule timing
|
|
1081
|
+
// after `runPipeline` already snapshotted (empty) `ruleTiming`. The
|
|
1082
|
+
// accumulator was reset at `runPipeline` entry, so this is legacy + phase.
|
|
1083
|
+
pipelineRuleTiming = getRuleTimingSortedDesc();
|
|
1084
|
+
pipelineFileAccounting = pipelineResult.metadata?.fileAccounting;
|
|
1085
|
+
pipelineDiagnostics = [
|
|
1086
|
+
...(pipelineResult.metadata?.diagnostics ?? []),
|
|
1087
|
+
...receiverDiagnostics,
|
|
1088
|
+
];
|
|
1089
|
+
pipelineTestCoverage = pipelineResult.metadata?.testCoverage;
|
|
1090
|
+
pipelineDeadClusters = pipelineResult.metadata?.deadClusters;
|
|
1091
|
+
// Spec 60 R2 — aggregate the SOLID analyzer's raw size readings.
|
|
1092
|
+
if (solidBundle) {
|
|
1093
|
+
const samples = await solidBundle.getSizeSamples();
|
|
1094
|
+
pipelineSizeDistributions = computeSizeDistributions(samples);
|
|
586
1095
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
_provenanceTiming: provenanceTiming,
|
|
592
|
-
files,
|
|
593
|
-
// Spec 60.1 Correction 1 — unfiltered discovery list, used as the
|
|
594
|
-
// classifier's corpus set (see the computation above for why `files`
|
|
595
|
-
// is too narrow).
|
|
596
|
-
corpusFiles,
|
|
597
|
-
// Spec 60.1 — virtual-module list (config, default ['.blitz']) and the
|
|
598
|
-
// project's tsconfig `paths` + `baseUrl` (alias classification *and*
|
|
599
|
-
// resolution). Threaded here so the function-index visitor and the
|
|
600
|
-
// reachability reducer read them once per run rather than re-reading
|
|
601
|
-
// tsconfig per file.
|
|
602
|
-
importVirtualModules: mergedOptions.importVirtualModules ?? DEFAULT_VIRTUAL_MODULES,
|
|
603
|
-
tsconfigAliases: readTsconfigAliases(root),
|
|
604
|
-
// Files reachable only through package.json (main/module/types/bin/exports
|
|
605
|
-
// + their sibling facades) are entry points, not dead modules — knex's
|
|
606
|
-
// `knex.mjs` / `knex.d.mts` beside `main: knex.js` were flagged
|
|
607
|
-
// `unreferenced-module` because no in-tree import reaches them.
|
|
608
|
-
packageEntryPoints: readPackageEntryPoints(root).entryPaths,
|
|
609
|
-
};
|
|
610
|
-
const pipelineConfig = {
|
|
611
|
-
projectRoot: root,
|
|
612
|
-
explicitFiles: files,
|
|
613
|
-
visitors: pipelineVisitors,
|
|
614
|
-
reducers: pipelineReducers,
|
|
615
|
-
derivedReducers: pipelineDerivedReducers,
|
|
616
|
-
config: pipelineAnalyzerConfig,
|
|
617
|
-
abortSignal: mergedOptions.abortSignal,
|
|
618
|
-
progressCallback: (progress) => {
|
|
619
|
-
throwIfAborted(mergedOptions.abortSignal);
|
|
620
|
-
reportProgress(mergedOptions, progress);
|
|
621
|
-
},
|
|
622
|
-
isScoped,
|
|
623
|
-
fileAccounting,
|
|
624
|
-
consumedFilePaths: styleConsumedFiles,
|
|
625
|
-
styleContributingFiles,
|
|
626
|
-
onStage2Complete: async (ctx) => {
|
|
627
|
-
// Post-stage-2 setup: rebuild function_calls from the functions table
|
|
628
|
-
// (populated by the function-index visitor), then mine conventions.
|
|
629
|
-
if (auditIndex && enabledAnalyzers.includes('conventions')) {
|
|
630
|
-
try {
|
|
631
|
-
await auditIndex.updateDependencyGraph();
|
|
632
|
-
}
|
|
633
|
-
catch (err) {
|
|
634
|
-
logMcpInfo('analysis', 'updateDependencyGraph failed (non-fatal)', {
|
|
635
|
-
error: err instanceof Error ? err.message : String(err)
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
try {
|
|
639
|
-
// Convention mining reads source on demand via readFileSync
|
|
640
|
-
// (mineImportForm/mineExportShape have an internal fallback).
|
|
641
|
-
auditIndex.mineAllConventions(root);
|
|
642
|
-
}
|
|
643
|
-
catch (err) {
|
|
644
|
-
logMcpInfo('analysis', 'convention mining failed (non-fatal)', {
|
|
645
|
-
error: err instanceof Error ? err.message : String(err)
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
},
|
|
650
|
-
};
|
|
651
|
-
try {
|
|
652
|
-
logMcpInfo('analysis', 'running pipeline', { visitorCount: pipelineVisitors.length, reducerCount: pipelineReducers.length, derivedReducerCount: pipelineDerivedReducers.length, fileCount: files.length });
|
|
653
|
-
const pipelineResult = await runPipeline(pipelineConfig, pipelineIndexHandle);
|
|
654
|
-
// Write index facts (schema_usage from schema visitor, etc.)
|
|
655
|
-
if (pipelineIndexHandle && pipelineResult.indexFacts && pipelineResult.indexFacts.length > 0) {
|
|
656
|
-
writeIndexFactsToDb(pipelineIndexHandle, pipelineResult.indexFacts);
|
|
657
|
-
}
|
|
658
|
-
// Pull in pipeline results
|
|
659
|
-
for (const [name, ar] of Object.entries(pipelineResult.analyzerResults)) {
|
|
660
|
-
analyzerResults[name] = ar;
|
|
661
|
-
logMcpDebug('analysis', `pipeline: ${name} completed`, {
|
|
662
|
-
violations: ar.violations?.length ?? 0,
|
|
663
|
-
status: ar.status?.status,
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
// ── Schema sub-visitors: keep separate rows for visibility ────
|
|
667
|
-
// Schema runs as 4 Stage 2 visitors + 1 Stage 3 reducer. Each visitor
|
|
668
|
-
// keeps its own violations and status row so the CLI displays per-visitor
|
|
669
|
-
// file/fact counts. Violations are NOT merged — the reducer only carries
|
|
670
|
-
// cross-file violations (unknown-table, JSON schema validation).
|
|
671
|
-
// Sub-visitors are added to orderedAnalyzerResults below (line ~650).
|
|
672
|
-
// ── React finalization (cross-component checks) ──────────────────
|
|
673
|
-
if (reactBundle) {
|
|
674
|
-
try {
|
|
675
|
-
const reactResult = analyzerResults['react'];
|
|
676
|
-
if (reactResult) {
|
|
677
|
-
// Pass the react namespace (not the whole namespaced config) so
|
|
678
|
-
// finalize reads `requireErrorBoundaries`/`rawElementCheck` at the
|
|
679
|
-
// same level the visitor's `context.config` does.
|
|
680
|
-
const extraViolations = await reactBundle.finalizeCrossComponent(pipelineAnalyzerConfig['react'] ?? {});
|
|
681
|
-
reactResult.violations.push(...extraViolations);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
catch (err) {
|
|
685
|
-
logMcpInfo('analysis', 'react finalization failed (non-fatal)', {
|
|
686
|
-
error: err instanceof Error ? err.message : String(err)
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
// Spec 27 — build per-rule coverage from final analyzer results
|
|
691
|
-
// (computed AFTER react finalization so cross-component checks are included).
|
|
692
|
-
// Spec 33 Item 14 — thread the pipeline's input-presence snapshot so
|
|
693
|
-
// zero-violation rules promote from `unassessed` to `clean`/`notApplicable`.
|
|
694
|
-
pipelineCoverage = buildCoverageReport(analyzerResults, pipelineConfig, pipelineResult.metadata?.inputPresence, new Map((pipelineResult.metadata?.ruleApplicability ?? []).map((a) => [
|
|
695
|
-
a.ruleId,
|
|
696
|
-
{ applicable: a.applicable, reason: a.reason, kind: a.kind },
|
|
697
|
-
])));
|
|
698
|
-
// Spec 29: extract table catalog from pipeline metadata for audit report
|
|
699
|
-
pipelineTableCatalog = pipelineResult.metadata?.tableCatalog;
|
|
700
|
-
pipelineStageTiming = pipelineResult.metadata?.stageTiming;
|
|
701
|
-
pipelineSkippedFiles = pipelineResult.metadata?.skippedFiles;
|
|
702
|
-
pipelineUnparsedFiles = pipelineResult.metadata?.unparsedFiles;
|
|
703
|
-
pipelineInputPresence = pipelineResult.metadata?.inputPresence;
|
|
704
|
-
pipelineRuleTiming = pipelineResult.metadata?.ruleTiming;
|
|
705
|
-
pipelineFileAccounting = pipelineResult.metadata?.fileAccounting;
|
|
706
|
-
pipelineDiagnostics = pipelineResult.metadata?.diagnostics;
|
|
707
|
-
pipelineTestCoverage = pipelineResult.metadata?.testCoverage;
|
|
708
|
-
pipelineDeadClusters = pipelineResult.metadata?.deadClusters;
|
|
709
|
-
// Spec 60 R2 — aggregate the SOLID analyzer's raw size readings into
|
|
710
|
-
// per-measure distributions (median/p95/max + tail annotation).
|
|
711
|
-
if (solidBundle) {
|
|
712
|
-
const samples = await solidBundle.getSizeSamples();
|
|
713
|
-
pipelineSizeDistributions = computeSizeDistributions(samples);
|
|
714
|
-
}
|
|
1096
|
+
}
|
|
1097
|
+
catch (error) {
|
|
1098
|
+
if (error instanceof AuditAbortedError) {
|
|
1099
|
+
throw error;
|
|
715
1100
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
};
|
|
737
|
-
}
|
|
1101
|
+
// Pipeline failure — one error, reported once below. Populate a `notRun`
|
|
1102
|
+
// placeholder (NOT `visitor-ran` with zero files) for each pipeline analyzer
|
|
1103
|
+
// so the zero-files diagnostic does not fan this single failure out into N
|
|
1104
|
+
// "matched zero source files" warnings. Exclude infrastructure visitors
|
|
1105
|
+
// (function-index) that don't produce violations. The single `pipeline-error`
|
|
1106
|
+
// diagnostic below is the one signal the CLI gates on for a non-zero exit.
|
|
1107
|
+
const pipelineAnalyzerNames = new Set([
|
|
1108
|
+
...pipelineVisitors.map(v => v.name),
|
|
1109
|
+
...pipelineReducers.map(r => r.name),
|
|
1110
|
+
...pipelineDerivedReducers.map(r => r.name),
|
|
1111
|
+
]);
|
|
1112
|
+
pipelineAnalyzerNames.delete('function-index');
|
|
1113
|
+
for (const name of pipelineAnalyzerNames) {
|
|
1114
|
+
if (!analyzerResults[name]) {
|
|
1115
|
+
analyzerResults[name] = {
|
|
1116
|
+
violations: [],
|
|
1117
|
+
executionTime: 0,
|
|
1118
|
+
analyzerName: name,
|
|
1119
|
+
status: { status: 'notRun', reason: `pipeline error: ${error.message}` },
|
|
1120
|
+
};
|
|
738
1121
|
}
|
|
739
|
-
reportError(mergedOptions, error, 'pipeline');
|
|
740
1122
|
}
|
|
1123
|
+
pipelineDiagnostics = [{
|
|
1124
|
+
analyzerName: 'pipeline',
|
|
1125
|
+
kind: 'pipeline-error',
|
|
1126
|
+
message: error.message,
|
|
1127
|
+
}];
|
|
1128
|
+
reportError(mergedOptions, error, 'pipeline');
|
|
741
1129
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1130
|
+
}
|
|
1131
|
+
// An unavailable index (e.g. a future schema version) is reported as a
|
|
1132
|
+
// coverage diagnostic — never raised as a fatal — so the run still completes
|
|
1133
|
+
// with the exit code it would otherwise have had.
|
|
1134
|
+
if (indexSkipDiagnostic) {
|
|
1135
|
+
pipelineDiagnostics = [...(pipelineDiagnostics ?? []), indexSkipDiagnostic];
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
analyzerResults,
|
|
1139
|
+
phaseFindings,
|
|
1140
|
+
phaseIncompleteFacts,
|
|
1141
|
+
pipelineCoverage,
|
|
1142
|
+
pipelineTableCatalog,
|
|
1143
|
+
pipelineStageTiming,
|
|
1144
|
+
pipelineSkippedFiles,
|
|
1145
|
+
pipelineUnparsedFiles,
|
|
1146
|
+
pipelineInputPresence,
|
|
1147
|
+
pipelineRuleTiming,
|
|
1148
|
+
pipelineFileAccounting,
|
|
1149
|
+
pipelineDiagnostics,
|
|
1150
|
+
pipelineTestCoverage,
|
|
1151
|
+
pipelineDeadClusters,
|
|
1152
|
+
pipelineSizeDistributions,
|
|
1153
|
+
routeAttribution,
|
|
1154
|
+
indexFactsWritten,
|
|
1155
|
+
writeIndexFactsMs,
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Initialize the analyzer-facing CodeIndexDB and build the `IndexHandle` the
|
|
1160
|
+
* pipeline reducers read through. Both are optional: a failed init degrades to
|
|
1161
|
+
* `undefined` and the pipeline continues without index-backed facts. Hoisted out
|
|
1162
|
+
* of `runPipelineStage` so its body stays under the `function-length` ceiling.
|
|
1163
|
+
*/
|
|
1164
|
+
async function initializeAuditIndex(root) {
|
|
1165
|
+
let auditIndex;
|
|
1166
|
+
let indexSkipDiagnostic;
|
|
1167
|
+
try {
|
|
1168
|
+
auditIndex = CodeIndexDB.getInstance(undefined, root);
|
|
1169
|
+
await auditIndex.initialize();
|
|
1170
|
+
logMcpInfo('analysis', 'code index initialized for analyzers', { isInitialized: auditIndex.isInitialized, dbPath: auditIndex.dbPath });
|
|
1171
|
+
}
|
|
1172
|
+
catch (err) {
|
|
1173
|
+
// Fail-open (Spec 71 R7 shape): a failed init — including a *future* index
|
|
1174
|
+
// schema version — must not crash the audit. Drop the half-built singleton
|
|
1175
|
+
// (its concern modules are undefined after a failed createSchema) so every
|
|
1176
|
+
// downstream `if (auditIndex)` guard reads "no index", and record the skip
|
|
1177
|
+
// as a coverage diagnostic naming the cause so "indexed analysis skipped"
|
|
1178
|
+
// is visible in the report, not raised as a fatal.
|
|
1179
|
+
auditIndex = undefined;
|
|
1180
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1181
|
+
const context = err?.context;
|
|
1182
|
+
indexSkipDiagnostic = {
|
|
1183
|
+
analyzerName: 'code-index',
|
|
1184
|
+
kind: 'engine-error',
|
|
1185
|
+
message: `Indexed analysis skipped: ${message}`,
|
|
1186
|
+
...(context && typeof context.storedSchemaVersion === 'number' && typeof context.supportedSchemaVersion === 'number'
|
|
1187
|
+
? { details: { storedSchemaVersion: context.storedSchemaVersion, supportedSchemaVersion: context.supportedSchemaVersion } }
|
|
1188
|
+
: {}),
|
|
1189
|
+
};
|
|
1190
|
+
logMcpInfo('analysis', 'failed to initialize code index for analyzers (continuing)', { error: message });
|
|
1191
|
+
}
|
|
1192
|
+
// Build IndexHandle for pipeline reducers
|
|
1193
|
+
let pipelineIndexHandle;
|
|
1194
|
+
if (auditIndex) {
|
|
1195
|
+
pipelineIndexHandle = {
|
|
1196
|
+
query: (sql, params) => auditIndex.rawSql.query(sql, params),
|
|
1197
|
+
count: (table) => auditIndex.rawSql.count(table),
|
|
1198
|
+
tableHasRows: (table) => auditIndex.rawSql.tableHasRows(table),
|
|
1199
|
+
run: (sql, params) => auditIndex.rawDb.prepare(sql).run(...(params ?? [])),
|
|
1200
|
+
exec: (sql) => auditIndex.rawDb.exec(sql),
|
|
1201
|
+
getMeta: (key) => auditIndex.meta.getMeta(key),
|
|
1202
|
+
getUntestedTopDecile: (td) => auditIndex.coverage.getUntestedTopDecile(td),
|
|
1203
|
+
rawDb: auditIndex.rawDb,
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
return { auditIndex, pipelineIndexHandle, indexSkipDiagnostic };
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Build the ordered analyzer-results map from the raw pipeline output: filter
|
|
1210
|
+
* to truthy entries (so downstream consumers never see undefineds), and insert
|
|
1211
|
+
* the schema sub-visitors and the auto-registered `data-access-org-filter`
|
|
1212
|
+
* reducer in their display positions.
|
|
1213
|
+
*/
|
|
1214
|
+
function buildOrderedAnalyzerResults(analyzers, analyzerResults) {
|
|
1215
|
+
const ordered = {};
|
|
1216
|
+
for (const analyzerName of analyzers) {
|
|
1217
|
+
if (analyzerResults[analyzerName]) {
|
|
1218
|
+
ordered[analyzerName] = analyzerResults[analyzerName];
|
|
750
1219
|
}
|
|
751
|
-
//
|
|
752
|
-
//
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
}
|
|
758
|
-
// When schema is enabled, insert sub-visitor rows after the schema entry
|
|
759
|
-
// so the CLI table shows per-visitor file/fact counts.
|
|
760
|
-
if (analyzerName === 'schema') {
|
|
761
|
-
for (const subName of SCHEMA_SUB_VISITORS) {
|
|
762
|
-
if (analyzerResults[subName]) {
|
|
763
|
-
orderedAnalyzerResults[subName] = analyzerResults[subName];
|
|
764
|
-
}
|
|
1220
|
+
// When schema is enabled, insert sub-visitor rows after the schema entry
|
|
1221
|
+
// so the CLI table shows per-visitor file/fact counts.
|
|
1222
|
+
if (analyzerName === 'schema') {
|
|
1223
|
+
for (const subName of SCHEMA_SUB_VISITORS) {
|
|
1224
|
+
if (analyzerResults[subName]) {
|
|
1225
|
+
ordered[subName] = analyzerResults[subName];
|
|
765
1226
|
}
|
|
766
1227
|
}
|
|
767
|
-
// Spec 62 Amendment B — `data-access-org-filter` is auto-registered (never
|
|
768
|
-
// in enabledAnalyzers), so surface its Stage-4 row right after data-access.
|
|
769
|
-
if (analyzerName === 'data-access' && analyzerResults['data-access-org-filter']) {
|
|
770
|
-
orderedAnalyzerResults['data-access-org-filter'] = analyzerResults['data-access-org-filter'];
|
|
771
|
-
}
|
|
772
1228
|
}
|
|
773
|
-
//
|
|
774
|
-
//
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
// sub-visitor, collapsing schema-code/schema-sql/schema-prisma/schema-json
|
|
778
|
-
// into the parent 'schema'. Both surface in the baseline metadata as
|
|
779
|
-
// `unknown: 338` / an over-broad `schema` bucket. The result key IS the
|
|
780
|
-
// analyzer that emitted the finding, so stamp it here as the single source
|
|
781
|
-
// of truth — a no-op for every analyzer that already labels itself, and it
|
|
782
|
-
// also fixes any future analyzer that forgets. Runs before baseline
|
|
783
|
-
// classification (Spec 18) so fingerprints and analyzerCounts are correct.
|
|
784
|
-
// Spec 62 Amendment B — the `data-access-org-filter` reducer stamps its
|
|
785
|
-
// findings with `analyzer: 'data-access'` so the ledger group
|
|
786
|
-
// `data-access/missing-org-filter` stays stable across the Stage-2 → Stage-4
|
|
787
|
-
// move. The result key is otherwise the single source of truth (see above).
|
|
788
|
-
const analyzerFieldOverride = {
|
|
789
|
-
'data-access-org-filter': 'data-access',
|
|
790
|
-
};
|
|
791
|
-
for (const [resultName, result] of Object.entries(orderedAnalyzerResults)) {
|
|
792
|
-
const stampedAnalyzer = analyzerFieldOverride[resultName] ?? resultName;
|
|
793
|
-
for (const v of result.violations) {
|
|
794
|
-
v.analyzer = stampedAnalyzer;
|
|
795
|
-
}
|
|
1229
|
+
// Spec 62 Amendment B — `data-access-org-filter` is auto-registered (never
|
|
1230
|
+
// in analyzers), so surface its Stage-4 row right after data-access.
|
|
1231
|
+
if (analyzerName === 'data-access' && analyzerResults['data-access-org-filter']) {
|
|
1232
|
+
ordered['data-access-org-filter'] = analyzerResults['data-access-org-filter'];
|
|
796
1233
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1234
|
+
}
|
|
1235
|
+
return ordered;
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Stamp the `analyzer` field on every violation to its result key. Violations
|
|
1239
|
+
* are emitted by analyzers that don't always stamp `analyzer` correctly: the
|
|
1240
|
+
* react analyzer omits it entirely, schema sub-visitor helpers hardcode
|
|
1241
|
+
* `'schema'`, and the `data-access-org-filter` reducer deliberately labels
|
|
1242
|
+
* itself `data-access` to keep the ledger group stable. The result key is the
|
|
1243
|
+
* single source of truth.
|
|
1244
|
+
*/
|
|
1245
|
+
function stampAnalyzerFields(ordered) {
|
|
1246
|
+
const overrides = {
|
|
1247
|
+
'data-access-org-filter': 'data-access',
|
|
1248
|
+
};
|
|
1249
|
+
for (const [resultName, result] of Object.entries(ordered)) {
|
|
1250
|
+
const stamped = overrides[resultName] ?? resultName;
|
|
1251
|
+
for (const v of result.violations) {
|
|
1252
|
+
v.analyzer = stamped;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Spec 13 R2 — attach hotspot + reachability scores to every violation and
|
|
1258
|
+
* reorder within severity tiers (live code first, then higher hotspot, original
|
|
1259
|
+
* order as tiebreaker). Advisory — failure is non-fatal (no churn/hotspot data
|
|
1260
|
+
* is common on a first run).
|
|
1261
|
+
*/
|
|
1262
|
+
async function applyHotspotScoring(ordered, root) {
|
|
1263
|
+
try {
|
|
1264
|
+
const indexDb = CodeIndexDB.getInstance(undefined, root);
|
|
1265
|
+
await indexDb.initialize();
|
|
1266
|
+
const hotspotRows = indexDb.rawDb
|
|
1267
|
+
.prepare('SELECT target, type, score FROM hotspot_scores')
|
|
1268
|
+
.all();
|
|
1269
|
+
const hotspotByTarget = new Map();
|
|
1270
|
+
const hotspotByFile = new Map();
|
|
1271
|
+
for (const row of hotspotRows) {
|
|
1272
|
+
hotspotByTarget.set(row.target, row.score);
|
|
1273
|
+
if (row.type === 'file') {
|
|
1274
|
+
hotspotByFile.set(row.target, row.score);
|
|
820
1275
|
}
|
|
821
1276
|
}
|
|
822
|
-
|
|
823
|
-
|
|
1277
|
+
const reachabilityByFile = new Map();
|
|
1278
|
+
const reachRows = indexDb.rawDb
|
|
1279
|
+
.prepare("SELECT node_key, weight FROM graph_cache WHERE graph_type = 'reachability'")
|
|
1280
|
+
.all();
|
|
1281
|
+
for (const row of reachRows) {
|
|
1282
|
+
reachabilityByFile.set(row.node_key, row.weight);
|
|
824
1283
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
.
|
|
839
|
-
.all();
|
|
840
|
-
const hotspotByTarget = new Map();
|
|
841
|
-
const hotspotByFile = new Map();
|
|
842
|
-
for (const row of hotspotRows) {
|
|
843
|
-
hotspotByTarget.set(row.target, row.score);
|
|
844
|
-
if (row.type === 'file') {
|
|
845
|
-
hotspotByFile.set(row.target, row.score);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
// Build reachability lookup: file -> score (0 dead, 0.5 imported, 1 entry)
|
|
849
|
-
const reachabilityByFile = new Map();
|
|
850
|
-
const reachRows = indexDb.rawDb
|
|
851
|
-
.prepare("SELECT node_key, weight FROM graph_cache WHERE graph_type = 'reachability'")
|
|
852
|
-
.all();
|
|
853
|
-
for (const row of reachRows) {
|
|
854
|
-
reachabilityByFile.set(row.node_key, row.weight);
|
|
855
|
-
}
|
|
856
|
-
// Attach hotspot + reachability to each violation and reorder within each analyzer
|
|
857
|
-
for (const analyzerName of Object.keys(orderedAnalyzerResults)) {
|
|
858
|
-
const result = orderedAnalyzerResults[analyzerName];
|
|
859
|
-
const violations = result.violations;
|
|
860
|
-
for (const v of violations) {
|
|
861
|
-
const funcName = v.functionName;
|
|
862
|
-
const file = v.file;
|
|
863
|
-
// Try function-level hotspot first: "filePath::functionName"
|
|
864
|
-
const funcTarget = funcName ? `${file}::${funcName}` : undefined;
|
|
865
|
-
v.hotspot = (funcTarget ? hotspotByTarget.get(funcTarget) : undefined)
|
|
866
|
-
?? hotspotByFile.get(file)
|
|
867
|
-
?? 0;
|
|
868
|
-
// Files with no cross-language entities (no reachability data) get a
|
|
869
|
-
// neutral 0.5 so we never demote a finding we know nothing about.
|
|
870
|
-
v.reachability = reachabilityByFile.get(file) ?? 0.5;
|
|
871
|
-
}
|
|
872
|
-
// Reorder within severity tiers: reachability descending, then hotspot
|
|
873
|
-
// descending, preserving original order as tiebreaker.
|
|
874
|
-
const severityOrder = {
|
|
875
|
-
critical: 0,
|
|
876
|
-
severe: 1,
|
|
877
|
-
high: 2,
|
|
878
|
-
info: 3,
|
|
879
|
-
};
|
|
880
|
-
const indexed = violations.map((v, i) => ({ v, i }));
|
|
881
|
-
indexed.sort((a, b) => {
|
|
882
|
-
const sevA = severityOrder[a.v.severity] ?? 99;
|
|
883
|
-
const sevB = severityOrder[b.v.severity] ?? 99;
|
|
884
|
-
if (sevA !== sevB)
|
|
885
|
-
return sevA - sevB;
|
|
886
|
-
// Within same severity: live code first, dead code last
|
|
887
|
-
const reachA = a.v.reachability ?? 0.5;
|
|
888
|
-
const reachB = b.v.reachability ?? 0.5;
|
|
889
|
-
if (reachA !== reachB)
|
|
890
|
-
return reachB - reachA;
|
|
891
|
-
// Then higher hotspot first
|
|
892
|
-
const hsA = a.v.hotspot ?? 0;
|
|
893
|
-
const hsB = b.v.hotspot ?? 0;
|
|
894
|
-
if (hsA !== hsB)
|
|
895
|
-
return hsB - hsA;
|
|
896
|
-
// Tiebreaker: original order
|
|
897
|
-
return a.i - b.i;
|
|
898
|
-
});
|
|
899
|
-
result.violations = indexed.map(x => x.v);
|
|
1284
|
+
for (const analyzerName of Object.keys(ordered)) {
|
|
1285
|
+
const result = ordered[analyzerName];
|
|
1286
|
+
const violations = result.violations;
|
|
1287
|
+
for (const v of violations) {
|
|
1288
|
+
const funcName = v.symbol;
|
|
1289
|
+
const file = v.file;
|
|
1290
|
+
// Try function-level hotspot first: "filePath::symbol"
|
|
1291
|
+
const funcTarget = funcName ? `${file}::${funcName}` : undefined;
|
|
1292
|
+
v.hotspot = (funcTarget ? hotspotByTarget.get(funcTarget) : undefined)
|
|
1293
|
+
?? hotspotByFile.get(file)
|
|
1294
|
+
?? 0;
|
|
1295
|
+
// Files with no cross-language entities (no reachability data) get a
|
|
1296
|
+
// neutral 0.5 so we never demote a finding we know nothing about.
|
|
1297
|
+
v.reachability = reachabilityByFile.get(file) ?? 0.5;
|
|
900
1298
|
}
|
|
1299
|
+
const severityOrder = {
|
|
1300
|
+
critical: 0,
|
|
1301
|
+
severe: 1,
|
|
1302
|
+
high: 2,
|
|
1303
|
+
info: 3,
|
|
1304
|
+
};
|
|
1305
|
+
const indexed = violations.map((v, i) => ({ v, i }));
|
|
1306
|
+
indexed.sort((a, b) => {
|
|
1307
|
+
const sevA = severityOrder[a.v.severity] ?? 99;
|
|
1308
|
+
const sevB = severityOrder[b.v.severity] ?? 99;
|
|
1309
|
+
if (sevA !== sevB)
|
|
1310
|
+
return sevA - sevB;
|
|
1311
|
+
const reachA = a.v.reachability ?? 0.5;
|
|
1312
|
+
const reachB = b.v.reachability ?? 0.5;
|
|
1313
|
+
if (reachA !== reachB)
|
|
1314
|
+
return reachB - reachA;
|
|
1315
|
+
const hsA = a.v.hotspot ?? 0;
|
|
1316
|
+
const hsB = b.v.hotspot ?? 0;
|
|
1317
|
+
if (hsA !== hsB)
|
|
1318
|
+
return hsB - hsA;
|
|
1319
|
+
return a.i - b.i;
|
|
1320
|
+
});
|
|
1321
|
+
result.violations = indexed.map((x) => x.v);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
catch {
|
|
1325
|
+
// Hotspot/reachability reordering is advisory — failure is non-fatal
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Spec 18 R1 — classify the ordered findings against the committed baseline,
|
|
1330
|
+
* tagging each violation new/known and computing the baseline metadata. Runs
|
|
1331
|
+
* after the hook-contract guard so it classifies exactly the set that reaches
|
|
1332
|
+
* the report.
|
|
1333
|
+
*/
|
|
1334
|
+
function classifyBaselineFindings(ordered, mergedOptions, files, isScoped) {
|
|
1335
|
+
const projectRoot = path.resolve(mergedOptions.projectRoot || process.cwd());
|
|
1336
|
+
const baseline = loadBaseline(projectRoot);
|
|
1337
|
+
let baselineMetadata;
|
|
1338
|
+
if (baseline) {
|
|
1339
|
+
const allViolations = Object.values(ordered).flatMap((r) => r.violations);
|
|
1340
|
+
// For scoped runs, limit "fixed" to in-scope files
|
|
1341
|
+
const scopedFiles = isScoped ? files : undefined;
|
|
1342
|
+
const classified = matchFindings(allViolations, baseline, scopedFiles);
|
|
1343
|
+
// Tag violations with their baseline status
|
|
1344
|
+
for (const v of classified.new) {
|
|
1345
|
+
v.new = true;
|
|
1346
|
+
}
|
|
1347
|
+
for (const v of classified.known) {
|
|
1348
|
+
v.new = false;
|
|
901
1349
|
}
|
|
902
|
-
|
|
903
|
-
|
|
1350
|
+
baselineMetadata = {
|
|
1351
|
+
present: true,
|
|
1352
|
+
hash: hashBaseline(baseline),
|
|
1353
|
+
newCount: classified.new.length,
|
|
1354
|
+
fixedCount: classified.fixed.length,
|
|
1355
|
+
knownCount: classified.known.length,
|
|
1356
|
+
previousKnownCount: baseline.metadata.totalFindings,
|
|
1357
|
+
};
|
|
1358
|
+
logMcpInfo('baseline', 'baseline classification complete', {
|
|
1359
|
+
new: classified.new.length,
|
|
1360
|
+
fixed: classified.fixed.length,
|
|
1361
|
+
known: classified.known.length,
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
return { projectRoot, baselineMetadata };
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Build the final `AuditResult` from the ordered findings and the pipeline
|
|
1368
|
+
* bundle — summary, metadata assembly (coverage, baseline, diagnostics, the
|
|
1369
|
+
* Spec 60 size/test/distribution reports), and hook-contract-clean violations.
|
|
1370
|
+
*/
|
|
1371
|
+
function buildAuditResult(inputs) {
|
|
1372
|
+
const { ordered, files, analyzers, mergedOptions, isScoped, scopeResultType, indexFactsWritten, writeIndexFactsMs, startTime, startCpu, provenanceTiming, blastRadius, zeroFilesDiagnostics, pipelineDiagnostics, rejected, lintConfigDiagnostics, configPath, baselineMetadata, pipelineCoverage, routeAttribution, pipelineTableCatalog, pipelineStageTiming, thresholdChanges, thresholdSources, pipelineSkippedFiles, pipelineUnparsedFiles, skippedExtensions, pipelineInputPresence, pipelineRuleTiming, pipelineFileAccounting, pipelineTestCoverage, pipelineDeadClusters, pipelineSizeDistributions, collectedFunctions, fileToFunctionsMap, } = inputs;
|
|
1373
|
+
// Spec 69 §10 R4 — count query-shaped call sites whose DB receiver is
|
|
1374
|
+
// unproven, keyed by the analyzer that owns the receiver-resolution
|
|
1375
|
+
// instrument (`schema`). This is the site-level `cannot-fire` surface that
|
|
1376
|
+
// `summary.byAnalyzer[].unprovenSites` carries per analyzer.
|
|
1377
|
+
const unprovenSitesByAnalyzer = new Map();
|
|
1378
|
+
for (const d of pipelineDiagnostics ?? []) {
|
|
1379
|
+
if (d.kind !== 'cannot-fire' || d.analyzerName !== 'schema')
|
|
1380
|
+
continue;
|
|
1381
|
+
unprovenSitesByAnalyzer.set(d.analyzerName, (unprovenSitesByAnalyzer.get(d.analyzerName) ?? 0) + 1);
|
|
1382
|
+
}
|
|
1383
|
+
const summary = generateSummary(ordered, files.length, unprovenSitesByAnalyzer);
|
|
1384
|
+
return {
|
|
1385
|
+
timestamp: new Date(),
|
|
1386
|
+
summary,
|
|
1387
|
+
analyzerResults: ordered,
|
|
1388
|
+
recommendations: [],
|
|
1389
|
+
metadata: {
|
|
1390
|
+
auditDuration: Date.now() - startTime,
|
|
1391
|
+
auditCpuMs: cpuDurationMs(startCpu),
|
|
1392
|
+
filesAnalyzed: files.length,
|
|
1393
|
+
...(indexFactsWritten > 0 && { indexFactsWritten, writeIndexFactsMs }),
|
|
1394
|
+
analyzersRun: analyzers,
|
|
1395
|
+
...(isScoped && { analyzedFiles: files }),
|
|
1396
|
+
configUsed: mergedOptions,
|
|
1397
|
+
scope: scopeResultType,
|
|
1398
|
+
provenanceResolutionMs: provenanceTiming.totalMs,
|
|
1399
|
+
...(blastRadius && { blastRadius }),
|
|
1400
|
+
...((zeroFilesDiagnostics.length > 0 || (pipelineDiagnostics?.length ?? 0) > 0 || rejected.length > 0 || lintConfigDiagnostics.length > 0) && {
|
|
1401
|
+
diagnostics: [
|
|
1402
|
+
...zeroFilesDiagnostics,
|
|
1403
|
+
...(pipelineDiagnostics ?? []),
|
|
1404
|
+
...lintConfigDiagnostics,
|
|
1405
|
+
...rejected.map((entry) => ({
|
|
1406
|
+
analyzerName: 'config',
|
|
1407
|
+
kind: 'config-key-rejected',
|
|
1408
|
+
message: `${entry.key}: ${entry.value} (${entry.reason})`,
|
|
1409
|
+
...(configPath ? { file: configPath } : {}),
|
|
1410
|
+
details: { key: entry.key, value: entry.value, reason: entry.reason },
|
|
1411
|
+
})),
|
|
1412
|
+
],
|
|
1413
|
+
}),
|
|
1414
|
+
...(baselineMetadata && { baseline: baselineMetadata }),
|
|
1415
|
+
...(pipelineCoverage && { coverage: pipelineCoverage }),
|
|
1416
|
+
...(routeAttribution && { routeAttribution }),
|
|
1417
|
+
...(pipelineTableCatalog && { tableCatalog: pipelineTableCatalog }),
|
|
1418
|
+
...(pipelineStageTiming && { stageTiming: pipelineStageTiming }),
|
|
1419
|
+
...(thresholdChanges.length > 0 && { thresholdChanges }),
|
|
1420
|
+
...(thresholdSources.length > 0 && { thresholdSources }),
|
|
1421
|
+
...(pipelineSkippedFiles && pipelineSkippedFiles.length > 0 && { skippedFiles: pipelineSkippedFiles }),
|
|
1422
|
+
...(pipelineUnparsedFiles && pipelineUnparsedFiles.length > 0 && { unparsedFiles: pipelineUnparsedFiles }),
|
|
1423
|
+
...(skippedExtensions && skippedExtensions.length > 0 && { skippedExtensions }),
|
|
1424
|
+
...(pipelineInputPresence && { inputPresence: pipelineInputPresence }),
|
|
1425
|
+
...(pipelineRuleTiming && { ruleTiming: pipelineRuleTiming }),
|
|
1426
|
+
...(pipelineFileAccounting && { fileAccounting: pipelineFileAccounting }),
|
|
1427
|
+
...(pipelineTestCoverage && { testCoverage: pipelineTestCoverage }),
|
|
1428
|
+
...(pipelineDeadClusters && pipelineDeadClusters.length > 0 && { deadClusters: pipelineDeadClusters }),
|
|
1429
|
+
...(pipelineSizeDistributions && pipelineSizeDistributions.length > 0 && { sizeDistributions: pipelineSizeDistributions }),
|
|
1430
|
+
...(collectedFunctions.length > 0 && {
|
|
1431
|
+
collectedFunctions,
|
|
1432
|
+
fileToFunctionsMap: Object.fromEntries(fileToFunctionsMap)
|
|
1433
|
+
})
|
|
904
1434
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Spec 11 R1 — fire-and-forget write to the findings ledger. Non-fatal: the
|
|
1439
|
+
* ledger is advisory, so a failed write never invalidates the audit result.
|
|
1440
|
+
* Forked shard workers set `writeToLedger:false` (handled by the caller's
|
|
1441
|
+
* guard here) so the parent run is the single writer.
|
|
1442
|
+
*/
|
|
1443
|
+
function fireLedgerWrite(inputs) {
|
|
1444
|
+
if (inputs.mergedOptions.writeToLedger === false) {
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
void (async () => {
|
|
913
1448
|
try {
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
const indexDb = CodeIndexDB.getInstance(undefined, root);
|
|
921
|
-
await indexDb.initialize();
|
|
922
|
-
// Get all distinct pair fingerprints
|
|
923
|
-
const fingerprints = indexDb.rawDb
|
|
924
|
-
.prepare('SELECT DISTINCT pair_fingerprint FROM dry_pair_history')
|
|
925
|
-
.all();
|
|
926
|
-
if (fingerprints.length > 0) {
|
|
927
|
-
const getRows = indexDb.rawDb.prepare(`
|
|
928
|
-
SELECT similarity, timestamp
|
|
929
|
-
FROM dry_pair_history
|
|
930
|
-
WHERE pair_fingerprint = ?
|
|
931
|
-
ORDER BY timestamp ASC
|
|
932
|
-
`);
|
|
933
|
-
const divergingViolations = [];
|
|
934
|
-
for (const { pair_fingerprint: fp } of fingerprints) {
|
|
935
|
-
const rows = getRows.all(fp);
|
|
936
|
-
if (rows.length < requiredDeclines + 1)
|
|
937
|
-
continue;
|
|
938
|
-
// Check last `requiredDeclines` consecutive pairs for decline
|
|
939
|
-
let consecutiveDeclines = 0;
|
|
940
|
-
for (let i = rows.length - requiredDeclines; i < rows.length; i++) {
|
|
941
|
-
if (rows[i].similarity < rows[i - 1].similarity - threshold) {
|
|
942
|
-
consecutiveDeclines++;
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
if (consecutiveDeclines >= requiredDeclines) {
|
|
946
|
-
// Get file/line info from the last row
|
|
947
|
-
const lastRow = indexDb.rawDb.prepare(`
|
|
948
|
-
SELECT file1, file2, line1, line2 FROM dry_pair_history
|
|
949
|
-
WHERE pair_fingerprint = ?
|
|
950
|
-
ORDER BY timestamp DESC LIMIT 1
|
|
951
|
-
`).get(fp);
|
|
952
|
-
if (lastRow) {
|
|
953
|
-
const currentSim = rows[rows.length - 1].similarity;
|
|
954
|
-
const prevSim = rows[rows.length - 2].similarity;
|
|
955
|
-
const drop = Math.round((prevSim - currentSim) * 1000) / 1000;
|
|
956
|
-
divergingViolations.push({
|
|
957
|
-
file: lastRow.file1,
|
|
958
|
-
line: lastRow.line1,
|
|
959
|
-
severity: 'severe',
|
|
960
|
-
message: `Clone pair has diverged: similarity dropped ${drop} (from ${prevSim.toFixed(3)} to ${currentSim.toFixed(3)}) across ${requiredDeclines} consecutive runs (pair: ${fp.slice(0, 12)}…). Review ${lastRow.file1}:${lastRow.line1} and ${lastRow.file2}:${lastRow.line2} for diverged logic.`,
|
|
961
|
-
analyzer: 'dry',
|
|
962
|
-
rule: 'dry/diverging-clone',
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
if (divergingViolations.length > 0) {
|
|
968
|
-
// Append to the dry analyzer result, or create one if none exists
|
|
969
|
-
if (orderedAnalyzerResults['dry']) {
|
|
970
|
-
orderedAnalyzerResults['dry'].violations.push(...divergingViolations);
|
|
971
|
-
}
|
|
972
|
-
else {
|
|
973
|
-
orderedAnalyzerResults['dry'] = {
|
|
974
|
-
violations: divergingViolations,
|
|
975
|
-
status: makeVisitorStatus(0),
|
|
976
|
-
executionTime: 0,
|
|
977
|
-
analyzerName: 'dry',
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
}
|
|
1449
|
+
const indexDb = CodeIndexDB.getInstance(undefined, inputs.root);
|
|
1450
|
+
await indexDb.initialize();
|
|
1451
|
+
const violations = Object.values(inputs.ordered).flatMap((ar) => ar.violations);
|
|
1452
|
+
const scopeStr = Array.isArray(inputs.scope) ? `files:${inputs.scope.length}` : (inputs.scope ?? 'all');
|
|
1453
|
+
return writeAuditToLedger(indexDb.rawDb, detectRunInput(process.argv.slice(2).join(' '), inputs.options.surface ?? 'cli', scopeStr, inputs.root, TOOL_VERSION), violations, Date.now() - inputs.startTime, 0, // exit status TBD — updateLedgerRunStatus by CLI after return
|
|
1454
|
+
{ coverage: inputs.result.metadata.coverage });
|
|
983
1455
|
}
|
|
984
|
-
catch {
|
|
985
|
-
//
|
|
1456
|
+
catch (_err) {
|
|
1457
|
+
// ledger write is non-fatal — audit result is still valid
|
|
1458
|
+
return null;
|
|
986
1459
|
}
|
|
1460
|
+
})();
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Create an audit runner with the given options
|
|
1464
|
+
*
|
|
1465
|
+
* @param options - Audit runner options (project root, analyzer config, thresholds).
|
|
1466
|
+
*/
|
|
1467
|
+
export function createAuditRunner(options = {}) {
|
|
1468
|
+
/**
|
|
1469
|
+
* Load configuration from file
|
|
1470
|
+
*/
|
|
1471
|
+
async function loadConfiguration(configPath) {
|
|
1472
|
+
const { config } = await loadConfig({ configPath, projectRoot: path.dirname(configPath) });
|
|
1473
|
+
return { ...options, ...config };
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Run the audit
|
|
1477
|
+
*/
|
|
1478
|
+
async function run(runOptions) {
|
|
1479
|
+
const { mergedOptions, configPath, rejected, lintConfigDiagnostics, thresholdChanges, thresholdSources, } = await resolveRunConfig(options, runOptions);
|
|
1480
|
+
const startTime = Date.now();
|
|
1481
|
+
// CPU-time clock for the load-independent gate metric (Spec 38 R3 / Spec 43
|
|
1482
|
+
// R2-R3): wall clock conflates a slow rule with a slow machine; CPU time
|
|
1483
|
+
// isolates the rule cost. Captured alongside the wall clock so both stay in
|
|
1484
|
+
// the same scope (everything from scope resolution through result creation).
|
|
1485
|
+
const startCpu = process.cpuUsage();
|
|
1486
|
+
const root = path.resolve(mergedOptions.projectRoot || process.cwd());
|
|
1487
|
+
// One-time notice for a legacy in-repo index location (`.code-index` /
|
|
1488
|
+
// `node_modules/.cache/code-auditor`) left by a pre-move build — reported once,
|
|
1489
|
+
// never deleted (see legacyIndexNotice.ts).
|
|
1490
|
+
notifyLegacyIndexLocation(root);
|
|
1491
|
+
// Spec 44 — file accounting accumulator, owned by the run. Threaded through
|
|
1492
|
+
// discovery (full `all` scope) and the pipeline (stage 1/2) so every touched
|
|
1493
|
+
// file lands in exactly one terminal state and the balance can be asserted.
|
|
1494
|
+
const fileAccounting = new FileAccounting();
|
|
1495
|
+
const { files, changedFunctions, skippedExtensions, blastRadius, corpusFiles, scope, isScoped, scopeResultType, } = await discoverAuditFiles(mergedOptions, root, fileAccounting);
|
|
1496
|
+
const { collectedFunctions, fileToFunctionsMap } = await collectIndexedFunctions(mergedOptions, files);
|
|
1497
|
+
// Run analyzers
|
|
1498
|
+
// Spec 68 §15 — no selection model: the full pipeline always runs. `indexOnly`
|
|
1499
|
+
// skips analysis wholesale (the index-only harness); config cannot pick a subset.
|
|
1500
|
+
const analyzers = mergedOptions.indexOnly ? [] : [...RUN_ANALYZERS];
|
|
1501
|
+
logMcpInfo('analysis', 'enabled analyzers', {
|
|
1502
|
+
names: analyzers,
|
|
1503
|
+
fileCount: files.length,
|
|
1504
|
+
scope: scopeResultType
|
|
1505
|
+
});
|
|
1506
|
+
// Spec 21: Shared timing accumulator for provenance resolution.
|
|
1507
|
+
const provenanceTiming = { totalMs: 0 };
|
|
1508
|
+
const { styleConsumedFiles, styleContributingFiles } = await syncStyles(analyzers, root, files, isScoped);
|
|
1509
|
+
const pipeline = await runPipelineStage({
|
|
1510
|
+
mergedOptions, root, files, corpusFiles, isScoped, fileAccounting,
|
|
1511
|
+
styleConsumedFiles, styleContributingFiles, analyzers, provenanceTiming,
|
|
1512
|
+
});
|
|
1513
|
+
const { analyzerResults, phaseFindings, phaseIncompleteFacts, pipelineCoverage, pipelineTableCatalog, pipelineStageTiming, pipelineSkippedFiles, pipelineUnparsedFiles, pipelineInputPresence, pipelineRuleTiming, pipelineFileAccounting, pipelineDiagnostics, pipelineTestCoverage, pipelineDeadClusters, pipelineSizeDistributions, routeAttribution, indexFactsWritten, writeIndexFactsMs, } = pipeline;
|
|
1514
|
+
// ── Zero-files diagnostic ─────────────────────────────────────────────
|
|
1515
|
+
// Extracted to runZeroFilesDiagnostics() for testability. Runs against the
|
|
1516
|
+
// raw analyzerResults before truthiness filtering so the "no result" pass
|
|
1517
|
+
// catches analyzers skipped by the registry, abort, or handoff exceptions.
|
|
1518
|
+
const hasGoFiles = files.some((f) => f.endsWith('.go'));
|
|
1519
|
+
const zeroFilesDiagnostics = runZeroFilesDiagnostics(analyzers, analyzerResults, files.length, hasGoFiles);
|
|
1520
|
+
for (const w of zeroFilesDiagnostics) {
|
|
1521
|
+
console.warn(w.message);
|
|
1522
|
+
}
|
|
1523
|
+
// Build ordered results — filter to truthy entries so downstream consumers
|
|
1524
|
+
// (DRY pair persistence, baseline, report generation) don't see undefineds.
|
|
1525
|
+
const orderedAnalyzerResults = buildOrderedAnalyzerResults(analyzers, analyzerResults);
|
|
1526
|
+
// Normalize the analyzer field on every violation to its result key (the
|
|
1527
|
+
// single source of truth — see stampAnalyzerFields).
|
|
1528
|
+
stampAnalyzerFields(orderedAnalyzerResults);
|
|
1529
|
+
// Spec 13 R2 — hotspot scoring & finding reordering (advisory).
|
|
1530
|
+
await applyHotspotScoring(orderedAnalyzerResults, root);
|
|
987
1531
|
// Hook-contract guard: no violation may carry an empty file path, line 0, or
|
|
988
1532
|
// missing line. A sentinel violation with file:'' broke the Claude Code hook's
|
|
989
|
-
// JSON consumer (Spec 15 regression)
|
|
990
|
-
// again (Spec 22 alarm #1).
|
|
991
|
-
//
|
|
992
|
-
//
|
|
993
|
-
// Runs BEFORE result construction so the CLI/MCP surfaces never see bad
|
|
994
|
-
// violations. Operates on the originals (via splice), not a copy.
|
|
1533
|
+
// JSON consumer (Spec 15 regression); line:0 from CrossDomainAnalyzer broke it
|
|
1534
|
+
// again (Spec 22 alarm #1). Permanent — every analyzer code path must produce
|
|
1535
|
+
// properly anchored violations. Operates on the originals (via splice).
|
|
995
1536
|
for (const ar of Object.values(orderedAnalyzerResults)) {
|
|
996
1537
|
validateHookContract(ar.violations);
|
|
997
1538
|
}
|
|
998
|
-
//
|
|
999
|
-
//
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
timestamp: new Date(),
|
|
1039
|
-
summary,
|
|
1040
|
-
analyzerResults: orderedAnalyzerResults,
|
|
1041
|
-
recommendations: [],
|
|
1042
|
-
metadata: {
|
|
1043
|
-
auditDuration: Date.now() - startTime,
|
|
1044
|
-
filesAnalyzed: files.length,
|
|
1045
|
-
analyzersRun: enabledAnalyzers,
|
|
1046
|
-
...(isScoped && { analyzedFiles: files }),
|
|
1047
|
-
configUsed: mergedOptions,
|
|
1048
|
-
scope: scopeResultType,
|
|
1049
|
-
provenanceResolutionMs: provenanceTiming.totalMs,
|
|
1050
|
-
...(blastRadius && { blastRadius }),
|
|
1051
|
-
...((zeroFilesDiagnostics.length > 0 || (pipelineDiagnostics?.length ?? 0) > 0 || rejected.length > 0 || lintConfigDiagnostics.length > 0) && {
|
|
1052
|
-
diagnostics: [
|
|
1053
|
-
...zeroFilesDiagnostics,
|
|
1054
|
-
...(pipelineDiagnostics ?? []),
|
|
1055
|
-
...lintConfigDiagnostics,
|
|
1056
|
-
...rejected.map((entry) => ({
|
|
1057
|
-
analyzerName: 'config',
|
|
1058
|
-
kind: 'config-key-rejected',
|
|
1059
|
-
message: `${entry.key}: ${entry.value} (${entry.reason})`,
|
|
1060
|
-
...(configPath ? { file: configPath } : {}),
|
|
1061
|
-
details: { key: entry.key, value: entry.value, reason: entry.reason },
|
|
1062
|
-
})),
|
|
1063
|
-
],
|
|
1064
|
-
}),
|
|
1065
|
-
...(baselineMetadata && { baseline: baselineMetadata }),
|
|
1066
|
-
...(pipelineCoverage && { coverage: pipelineCoverage }),
|
|
1067
|
-
...(pipelineTableCatalog && { tableCatalog: pipelineTableCatalog }),
|
|
1068
|
-
...(pipelineStageTiming && { stageTiming: pipelineStageTiming }),
|
|
1069
|
-
...(thresholdChanges.length > 0 && { thresholdChanges }),
|
|
1070
|
-
...(thresholdSources.length > 0 && { thresholdSources }),
|
|
1071
|
-
...(pipelineSkippedFiles && pipelineSkippedFiles.length > 0 && { skippedFiles: pipelineSkippedFiles }),
|
|
1072
|
-
...(pipelineUnparsedFiles && pipelineUnparsedFiles.length > 0 && { unparsedFiles: pipelineUnparsedFiles }),
|
|
1073
|
-
...(skippedExtensions && skippedExtensions.length > 0 && { skippedExtensions }),
|
|
1074
|
-
...(pipelineInputPresence && { inputPresence: pipelineInputPresence }),
|
|
1075
|
-
...(pipelineRuleTiming && { ruleTiming: pipelineRuleTiming }),
|
|
1076
|
-
...(pipelineFileAccounting && { fileAccounting: pipelineFileAccounting }),
|
|
1077
|
-
...(pipelineTestCoverage && { testCoverage: pipelineTestCoverage }),
|
|
1078
|
-
...(pipelineDeadClusters && pipelineDeadClusters.length > 0 && { deadClusters: pipelineDeadClusters }),
|
|
1079
|
-
...(pipelineSizeDistributions && pipelineSizeDistributions.length > 0 && { sizeDistributions: pipelineSizeDistributions }),
|
|
1080
|
-
...(collectedFunctions.length > 0 && {
|
|
1081
|
-
collectedFunctions,
|
|
1082
|
-
fileToFunctionsMap: Object.fromEntries(fileToFunctionsMap)
|
|
1083
|
-
})
|
|
1084
|
-
}
|
|
1085
|
-
};
|
|
1539
|
+
// Baseline classification (Spec 18 R1) — after the hook-contract guard so it
|
|
1540
|
+
// classifies exactly the violation set that reaches the report.
|
|
1541
|
+
const { projectRoot, baselineMetadata } = classifyBaselineFindings(orderedAnalyzerResults, mergedOptions, files, isScoped);
|
|
1542
|
+
const result = buildAuditResult({
|
|
1543
|
+
ordered: orderedAnalyzerResults,
|
|
1544
|
+
files,
|
|
1545
|
+
analyzers,
|
|
1546
|
+
mergedOptions,
|
|
1547
|
+
isScoped,
|
|
1548
|
+
scopeResultType,
|
|
1549
|
+
indexFactsWritten,
|
|
1550
|
+
writeIndexFactsMs,
|
|
1551
|
+
startTime,
|
|
1552
|
+
startCpu,
|
|
1553
|
+
provenanceTiming,
|
|
1554
|
+
blastRadius,
|
|
1555
|
+
zeroFilesDiagnostics,
|
|
1556
|
+
pipelineDiagnostics,
|
|
1557
|
+
rejected,
|
|
1558
|
+
lintConfigDiagnostics,
|
|
1559
|
+
configPath,
|
|
1560
|
+
baselineMetadata,
|
|
1561
|
+
pipelineCoverage,
|
|
1562
|
+
routeAttribution,
|
|
1563
|
+
pipelineTableCatalog,
|
|
1564
|
+
pipelineStageTiming,
|
|
1565
|
+
thresholdChanges,
|
|
1566
|
+
thresholdSources,
|
|
1567
|
+
pipelineSkippedFiles,
|
|
1568
|
+
pipelineUnparsedFiles,
|
|
1569
|
+
skippedExtensions,
|
|
1570
|
+
pipelineInputPresence,
|
|
1571
|
+
pipelineRuleTiming,
|
|
1572
|
+
pipelineFileAccounting,
|
|
1573
|
+
pipelineTestCoverage,
|
|
1574
|
+
pipelineDeadClusters,
|
|
1575
|
+
pipelineSizeDistributions,
|
|
1576
|
+
collectedFunctions,
|
|
1577
|
+
fileToFunctionsMap,
|
|
1578
|
+
});
|
|
1086
1579
|
// Report completion
|
|
1087
1580
|
reportProgress(mergedOptions, {
|
|
1088
1581
|
phase: 'reporting',
|
|
1089
1582
|
message: 'Generating reports...'
|
|
1090
1583
|
});
|
|
1091
|
-
if (handoffRemaining.length > 0) {
|
|
1092
|
-
throw new AuditHandoffError(`${handoffRemaining.length} file(s) deferred to the next worker chunk`, result, handoffRemaining);
|
|
1093
|
-
}
|
|
1094
1584
|
// Spec 57 — apply committed dismissals: mark matching findings dismissed
|
|
1095
1585
|
// (they never gate) and record the dismissed count in the summary (never
|
|
1096
1586
|
// subtracted from totalViolations).
|
|
1097
1587
|
applyDismissals(result, projectRoot);
|
|
1098
1588
|
// Spec 11 R1 — write to findings ledger (non-fatal: ledger is advisory).
|
|
1099
|
-
|
|
1100
|
-
// single ledger writer (see AuditRunnerOptions.writeToLedger).
|
|
1101
|
-
if (mergedOptions.writeToLedger !== false) {
|
|
1102
|
-
void (async () => {
|
|
1103
|
-
try {
|
|
1104
|
-
const indexDb = CodeIndexDB.getInstance(undefined, root);
|
|
1105
|
-
await indexDb.initialize();
|
|
1106
|
-
const violations = Object.values(orderedAnalyzerResults).flatMap(ar => ar.violations);
|
|
1107
|
-
const scopeStr = Array.isArray(scope) ? `files:${scope.length}` : (scope ?? 'all');
|
|
1108
|
-
return writeAuditToLedger(indexDb.rawDb, detectRunInput(process.argv.slice(2).join(' '), options.surface ?? 'cli', scopeStr, root, TOOL_VERSION), violations, Date.now() - startTime, 0, // exit status TBD — updateLedgerRunStatus by CLI after return
|
|
1109
|
-
{ coverage: result.metadata.coverage });
|
|
1110
|
-
}
|
|
1111
|
-
catch (_err) {
|
|
1112
|
-
// ledger write is non-fatal — audit result is still valid
|
|
1113
|
-
return null;
|
|
1114
|
-
}
|
|
1115
|
-
})();
|
|
1116
|
-
}
|
|
1589
|
+
fireLedgerWrite({ mergedOptions, root, options, scope, ordered: orderedAnalyzerResults, result, startTime });
|
|
1117
1590
|
return result;
|
|
1118
1591
|
}
|
|
1119
1592
|
/**
|
|
@@ -1219,9 +1692,18 @@ function resolveGitScopeFiles(options, ref) {
|
|
|
1219
1692
|
/**
|
|
1220
1693
|
* Resolve files scope: paths can be file paths or globs.
|
|
1221
1694
|
* Absolute paths are used directly; relative paths are resolved
|
|
1222
|
-
* against the project root; globs use discoverFiles.
|
|
1695
|
+
* against the project root; globs use discoverFiles. A direct path whose
|
|
1696
|
+
* extension no analyzer claims (nor a raw/markup extension the pipeline reads
|
|
1697
|
+
* directly) is dropped — see `getSourceExtensions` — so a prose edit never
|
|
1698
|
+
* reaches the pipeline as a "dark analyzer" and trips the zero-files gate.
|
|
1699
|
+
*
|
|
1700
|
+
* @param options - The merged audit-runner options (`projectRoot`, `excludePaths`,
|
|
1701
|
+
* `fileExtensions`).
|
|
1702
|
+
* @param scopeFiles - Candidate paths or globs to resolve into an absolute file
|
|
1703
|
+
* set.
|
|
1704
|
+
* @returns The sorted set of absolute paths that are in audit scope.
|
|
1223
1705
|
*/
|
|
1224
|
-
async function resolveFilesScope(options, scopeFiles) {
|
|
1706
|
+
export async function resolveFilesScope(options, scopeFiles) {
|
|
1225
1707
|
const rootDir = path.resolve(options.projectRoot || process.cwd());
|
|
1226
1708
|
const result = new Set();
|
|
1227
1709
|
for (const item of scopeFiles) {
|
|
@@ -1237,8 +1719,20 @@ async function resolveFilesScope(options, scopeFiles) {
|
|
|
1237
1719
|
result.add(m);
|
|
1238
1720
|
}
|
|
1239
1721
|
else {
|
|
1240
|
-
// Direct file path — skip files that don't exist
|
|
1722
|
+
// Direct file path — skip files that don't exist, and files no analyzer
|
|
1723
|
+
// understands. A file whose extension is not claimed by the language
|
|
1724
|
+
// registry (nor a raw/markup extension the pipeline reads directly) was
|
|
1725
|
+
// never in scope: it must not enter `files` and trip the zero-files gate
|
|
1726
|
+
// as a "dark analyzer", which would turn a prose edit into an exit-2 hook
|
|
1727
|
+
// failure. The Spec 32 parse-failure gate is untouched — a claimed-but-
|
|
1728
|
+
// unparseable file (a `.ts` that fails to parse) still reaches the
|
|
1729
|
+
// pipeline and stays loud. The gate is the registry union (via
|
|
1730
|
+
// `getSourceExtensions`), not a hand-maintained extension list, so a parser
|
|
1731
|
+
// added to the registry is in scope here without a second edit.
|
|
1241
1732
|
const resolved = path.isAbsolute(item) ? item : path.resolve(rootDir, item);
|
|
1733
|
+
const ext = path.extname(resolved).toLowerCase();
|
|
1734
|
+
if (!getSourceExtensions().has(ext))
|
|
1735
|
+
continue;
|
|
1242
1736
|
try {
|
|
1243
1737
|
await fs.stat(resolved);
|
|
1244
1738
|
result.add(resolved);
|
|
@@ -1251,27 +1745,21 @@ async function resolveFilesScope(options, scopeFiles) {
|
|
|
1251
1745
|
return [...result].sort();
|
|
1252
1746
|
}
|
|
1253
1747
|
/**
|
|
1254
|
-
*
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
return options.enabledAnalyzers;
|
|
1263
|
-
}
|
|
1264
|
-
const allAnalyzers = Object.keys(registry);
|
|
1265
|
-
return allAnalyzers;
|
|
1266
|
-
}
|
|
1267
|
-
/**
|
|
1268
|
-
* Generate audit summary
|
|
1748
|
+
* Generate audit summary.
|
|
1749
|
+
*
|
|
1750
|
+
* Exported for the Spec 69 §10 R4 pin: `summary.byAnalyzer[].unprovenSites`
|
|
1751
|
+
* must carry the site-level cannot-fire count per analyzer.
|
|
1752
|
+
*
|
|
1753
|
+
* @param analyzerResults per-analyzer results, keyed by analyzer name
|
|
1754
|
+
* @param filesAnalyzed total number of files processed this run
|
|
1755
|
+
* @param unprovenSitesByAnalyzer per-analyzer count of unproven (cannot-fire) sites
|
|
1269
1756
|
*/
|
|
1270
|
-
function generateSummary(analyzerResults, filesAnalyzed) {
|
|
1757
|
+
export function generateSummary(analyzerResults, filesAnalyzed, unprovenSitesByAnalyzer = new Map()) {
|
|
1271
1758
|
let totalViolations = 0;
|
|
1272
1759
|
let criticalIssues = 0;
|
|
1273
1760
|
let severe = 0;
|
|
1274
1761
|
let high = 0;
|
|
1762
|
+
let heldOut = 0;
|
|
1275
1763
|
const violationsByCategory = {};
|
|
1276
1764
|
const byAnalyzer = {};
|
|
1277
1765
|
for (const [analyzer, result] of Object.entries(analyzerResults)) {
|
|
@@ -1279,6 +1767,8 @@ function generateSummary(analyzerResults, filesAnalyzed) {
|
|
|
1279
1767
|
for (const violation of result.violations) {
|
|
1280
1768
|
totalViolations++;
|
|
1281
1769
|
analyzerViolations++;
|
|
1770
|
+
if (violation.gateExcluded)
|
|
1771
|
+
heldOut++;
|
|
1282
1772
|
switch (violation.severity) {
|
|
1283
1773
|
case 'critical':
|
|
1284
1774
|
criticalIssues++;
|
|
@@ -1297,6 +1787,7 @@ function generateSummary(analyzerResults, filesAnalyzed) {
|
|
|
1297
1787
|
violations: analyzerViolations,
|
|
1298
1788
|
filesProcessed: getFilesProcessed(result.status),
|
|
1299
1789
|
fatalErrors: result.errors ? result.errors.length : 0,
|
|
1790
|
+
unprovenSites: unprovenSitesByAnalyzer.get(analyzer) ?? 0,
|
|
1300
1791
|
};
|
|
1301
1792
|
}
|
|
1302
1793
|
// Compute top issues from violationsByCategory
|
|
@@ -1304,6 +1795,12 @@ function generateSummary(analyzerResults, filesAnalyzed) {
|
|
|
1304
1795
|
.sort(([, a], [, b]) => b - a)
|
|
1305
1796
|
.slice(0, 5)
|
|
1306
1797
|
.map(([type, count]) => ({ type, count }));
|
|
1798
|
+
// Spec 69 §10 R4 follow-up — the site-level cannot-fire total, summed across
|
|
1799
|
+
// analyzers, so the headline can state findings + unproven sites + held-out
|
|
1800
|
+
// readings together without re-deriving from per-analyzer buckets.
|
|
1801
|
+
let unprovenSites = 0;
|
|
1802
|
+
for (const n of unprovenSitesByAnalyzer.values())
|
|
1803
|
+
unprovenSites += n;
|
|
1307
1804
|
return {
|
|
1308
1805
|
totalFiles: filesAnalyzed,
|
|
1309
1806
|
totalViolations,
|
|
@@ -1312,7 +1809,9 @@ function generateSummary(analyzerResults, filesAnalyzed) {
|
|
|
1312
1809
|
high,
|
|
1313
1810
|
violationsByCategory,
|
|
1314
1811
|
byAnalyzer,
|
|
1315
|
-
topIssues
|
|
1812
|
+
topIssues,
|
|
1813
|
+
unprovenSites,
|
|
1814
|
+
heldOut
|
|
1316
1815
|
};
|
|
1317
1816
|
}
|
|
1318
1817
|
/**
|
|
@@ -1405,17 +1904,23 @@ function mergeLintUnderProject(lintFragment, projectConfig) {
|
|
|
1405
1904
|
*
|
|
1406
1905
|
* Pass 2: Every analyzer with a result entry but filesProcessed === 0 and no
|
|
1407
1906
|
* errors fires a warning — the analyzer ran but matched zero source files.
|
|
1907
|
+
*
|
|
1908
|
+
* @param analyzers - Names of the enabled analyzers to check against the results.
|
|
1909
|
+
* @param analyzerResults - Map of analyzer name to its audit result.
|
|
1910
|
+
* @param totalFiles - Total files processed; a zero total suppresses the warning as expected.
|
|
1911
|
+
* @param hasGoFiles - Whether the corpus contained `.go` files (false excuses a skipped `go` analyzer).
|
|
1912
|
+
* @returns Diagnostic warnings for analyzers that produced no result or matched zero files.
|
|
1408
1913
|
*/
|
|
1409
|
-
export function runZeroFilesDiagnostics(
|
|
1914
|
+
export function runZeroFilesDiagnostics(analyzers, analyzerResults, totalFiles, hasGoFiles) {
|
|
1410
1915
|
// When there were zero files to process, zero-files is expected, not a bug.
|
|
1411
1916
|
if (totalFiles === 0)
|
|
1412
1917
|
return [];
|
|
1413
1918
|
const warnings = [];
|
|
1414
1919
|
// Pass 1: enabled but absent from results
|
|
1415
|
-
for (const analyzerName of
|
|
1920
|
+
for (const analyzerName of analyzers) {
|
|
1416
1921
|
if (!analyzerResults[analyzerName]) {
|
|
1417
|
-
// The `go` analyzer is a polyglot subprocess
|
|
1418
|
-
//
|
|
1922
|
+
// The `go` analyzer is a polyglot subprocess that runs only when `.go`
|
|
1923
|
+
// files exist. On a TypeScript-only corpus
|
|
1419
1924
|
// it is legitimately notApplicable, not a dropped analyzer (defect #50) —
|
|
1420
1925
|
// zero `.go` files means there was nothing for it to run on.
|
|
1421
1926
|
if (analyzerName === 'go' && hasGoFiles === false)
|
|
@@ -1428,23 +1933,28 @@ export function runZeroFilesDiagnostics(enabledAnalyzers, analyzerResults, total
|
|
|
1428
1933
|
});
|
|
1429
1934
|
}
|
|
1430
1935
|
}
|
|
1431
|
-
// Pass 2: filesProcessed = 0 — a dark-analyzer failure
|
|
1432
|
-
//
|
|
1433
|
-
//
|
|
1434
|
-
//
|
|
1435
|
-
//
|
|
1936
|
+
// Pass 2: filesProcessed = 0 — a dark-analyzer failure. Visitors with declared
|
|
1937
|
+
// extensions that matched zero files in the corpus are benign (converted to
|
|
1938
|
+
// notRun by the pipeline), but a visitor that was dispatched files and still
|
|
1939
|
+
// shows visitor-ran + 0 files is broken. When it errored, name the file(s) and
|
|
1940
|
+
// error(s) — "check file extensions" is the wrong cause for a parse/visit error
|
|
1941
|
+
// and would send the reader to look at configuration when a file failed.
|
|
1436
1942
|
for (const [analyzerName, result] of Object.entries(analyzerResults)) {
|
|
1437
1943
|
if (analyzerName === 'go' && hasGoFiles === false)
|
|
1438
1944
|
continue;
|
|
1439
1945
|
if (isVisitorStatus(result.status) && getFilesProcessed(result.status) === 0) {
|
|
1440
|
-
const
|
|
1441
|
-
const errDetail =
|
|
1946
|
+
const errs = result.errors;
|
|
1947
|
+
const errDetail = errs && errs.length > 0
|
|
1948
|
+
? ` — ${errs.length} file error(s): ${errs.slice(0, 3).map((e) => `${e.file}: ${e.error}`).join('; ')}${errs.length > 3 ? ' …' : ''}`
|
|
1949
|
+
: '';
|
|
1442
1950
|
warnings.push({
|
|
1443
1951
|
analyzerName,
|
|
1444
1952
|
kind: 'zero-files',
|
|
1445
|
-
message:
|
|
1446
|
-
|
|
1447
|
-
|
|
1953
|
+
message: errDetail
|
|
1954
|
+
? `⚠️ ${analyzerName} analyzer: filesProcessed = 0${errDetail}.`
|
|
1955
|
+
: `⚠️ ${analyzerName} analyzer: filesProcessed = 0. ` +
|
|
1956
|
+
`The analyzer ran but matched zero source files. Check file extensions, ` +
|
|
1957
|
+
`scanner configuration, and project structure.`,
|
|
1448
1958
|
});
|
|
1449
1959
|
}
|
|
1450
1960
|
}
|