code-auditor-mcp 4.1.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +374 -0
- package/README.md +4 -4
- package/configs/hhra-compat.json +0 -8
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts +1 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js +7 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts +2 -8
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js +4 -4
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts +1 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts +2 -2
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts +1 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.js.map +1 -1
- package/dist/analyzers/applicability.d.ts +42 -0
- package/dist/analyzers/applicability.d.ts.map +1 -1
- package/dist/analyzers/applicability.js +74 -0
- package/dist/analyzers/applicability.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +18 -24
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +30 -23
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +113 -76
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +2 -0
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +8 -3
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +210 -148
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +23 -28
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/handleIdentification.d.ts +356 -0
- package/dist/analyzers/handleIdentification.d.ts.map +1 -0
- package/dist/analyzers/handleIdentification.js +424 -0
- package/dist/analyzers/handleIdentification.js.map +1 -0
- package/dist/analyzers/manifestStaleness.d.ts +64 -0
- package/dist/analyzers/manifestStaleness.d.ts.map +1 -0
- package/dist/analyzers/manifestStaleness.js +115 -0
- package/dist/analyzers/manifestStaleness.js.map +1 -0
- package/dist/analyzers/orgFilterTiers.d.ts +73 -1
- package/dist/analyzers/orgFilterTiers.d.ts.map +1 -1
- package/dist/analyzers/orgFilterTiers.js +166 -1
- package/dist/analyzers/orgFilterTiers.js.map +1 -1
- package/dist/analyzers/provenance.d.ts +251 -57
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +1109 -429
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts +2 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +2 -0
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/receiverResolution.d.ts +183 -0
- package/dist/analyzers/receiverResolution.d.ts.map +1 -0
- package/dist/analyzers/receiverResolution.js +540 -0
- package/dist/analyzers/receiverResolution.js.map +1 -0
- package/dist/analyzers/receiverRoot.d.ts +328 -0
- package/dist/analyzers/receiverRoot.d.ts.map +1 -0
- package/dist/analyzers/receiverRoot.js +990 -0
- package/dist/analyzers/receiverRoot.js.map +1 -0
- package/dist/analyzers/ruleRegistry.d.ts +12 -68
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +32 -342
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/ruleTiming.d.ts +10 -0
- package/dist/analyzers/ruleTiming.d.ts.map +1 -1
- package/dist/analyzers/ruleTiming.js +20 -0
- package/dist/analyzers/ruleTiming.js.map +1 -1
- package/dist/analyzers/sqlLiteral.d.ts +76 -0
- package/dist/analyzers/sqlLiteral.d.ts.map +1 -0
- package/dist/analyzers/sqlLiteral.js +419 -0
- package/dist/analyzers/sqlLiteral.js.map +1 -0
- package/dist/analyzers/tsEcosystem.d.ts +115 -0
- package/dist/analyzers/tsEcosystem.d.ts.map +1 -0
- package/dist/analyzers/tsEcosystem.js +231 -0
- package/dist/analyzers/tsEcosystem.js.map +1 -0
- package/dist/analyzers/tsExpressionDescriptor.d.ts +156 -0
- package/dist/analyzers/tsExpressionDescriptor.d.ts.map +1 -0
- package/dist/analyzers/tsExpressionDescriptor.js +374 -0
- package/dist/analyzers/tsExpressionDescriptor.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +6 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +107 -44
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +1 -12
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +24 -101
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +287 -49
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +2130 -512
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +2 -18
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +0 -33
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +20 -143
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +4 -3
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +36 -13
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.js +6 -0
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts +15 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js +25 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +21 -32
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +166 -196
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/functionConcerns.d.ts +3 -0
- package/dist/analyzers/universal/functionConcerns.d.ts.map +1 -1
- package/dist/analyzers/universal/functionConcerns.js +4 -1
- package/dist/analyzers/universal/functionConcerns.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +310 -69
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +709 -383
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/config.d.ts +3 -14
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/config.js +3 -16
- package/dist/analyzers/universal/schema/config.js.map +1 -1
- package/dist/analyzers/universal/schema/discovery.d.ts +22 -1
- package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/discovery.js +36 -20
- package/dist/analyzers/universal/schema/discovery.js.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.js +13 -3
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
- package/dist/analyzers/universal/schema/migrations.d.ts +187 -27
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/migrations.js +443 -186
- package/dist/analyzers/universal/schema/migrations.js.map +1 -1
- package/dist/analyzers/universal/schema/types.d.ts +17 -3
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/violations.js +1 -1
- package/dist/analyzers/universal/schema/violations.js.map +1 -1
- package/dist/analyzers/universal/styleScale.d.ts +52 -0
- package/dist/analyzers/universal/styleScale.d.ts.map +1 -0
- package/dist/analyzers/universal/styleScale.js +95 -0
- package/dist/analyzers/universal/styleScale.js.map +1 -0
- package/dist/applyDataDirEnv.js +1 -1
- package/dist/applyDataDirEnv.js.map +1 -1
- package/dist/auditRouter.d.ts +26 -30
- package/dist/auditRouter.d.ts.map +1 -1
- package/dist/auditRouter.js +373 -143
- package/dist/auditRouter.js.map +1 -1
- package/dist/auditRunner.d.ts +64 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +1537 -1051
- package/dist/auditRunner.js.map +1 -1
- package/dist/baseline.d.ts +30 -3
- package/dist/baseline.d.ts.map +1 -1
- package/dist/baseline.js +70 -9
- package/dist/baseline.js.map +1 -1
- package/dist/cli.js +997 -812
- package/dist/cli.js.map +1 -1
- package/dist/codeIndex/analyzerConfig.d.ts +60 -0
- package/dist/codeIndex/analyzerConfig.d.ts.map +1 -0
- package/dist/codeIndex/analyzerConfig.js +98 -0
- package/dist/codeIndex/analyzerConfig.js.map +1 -0
- package/dist/codeIndex/auditResults.d.ts +39 -0
- package/dist/codeIndex/auditResults.d.ts.map +1 -0
- package/dist/codeIndex/auditResults.js +99 -0
- package/dist/codeIndex/auditResults.js.map +1 -0
- package/dist/codeIndex/codeMap.d.ts +59 -0
- package/dist/codeIndex/codeMap.d.ts.map +1 -0
- package/dist/codeIndex/codeMap.js +72 -0
- package/dist/codeIndex/codeMap.js.map +1 -0
- package/dist/codeIndex/conventions.d.ts +33 -0
- package/dist/codeIndex/conventions.d.ts.map +1 -0
- package/dist/codeIndex/conventions.js +84 -0
- package/dist/codeIndex/conventions.js.map +1 -0
- package/dist/codeIndex/coverage.d.ts +78 -0
- package/dist/codeIndex/coverage.d.ts.map +1 -0
- package/dist/codeIndex/coverage.js +164 -0
- package/dist/codeIndex/coverage.js.map +1 -0
- package/dist/codeIndex/dependencyGraph.d.ts +97 -0
- package/dist/codeIndex/dependencyGraph.d.ts.map +1 -0
- package/dist/codeIndex/dependencyGraph.js +241 -0
- package/dist/codeIndex/dependencyGraph.js.map +1 -0
- package/dist/codeIndex/functionIndex.d.ts +111 -0
- package/dist/codeIndex/functionIndex.d.ts.map +1 -0
- package/dist/codeIndex/functionIndex.js +306 -0
- package/dist/codeIndex/functionIndex.js.map +1 -0
- package/dist/codeIndex/inferredReceivers.d.ts +32 -0
- package/dist/codeIndex/inferredReceivers.d.ts.map +1 -0
- package/dist/codeIndex/inferredReceivers.js +41 -0
- package/dist/codeIndex/inferredReceivers.js.map +1 -0
- package/dist/codeIndex/meta.d.ts +30 -0
- package/dist/codeIndex/meta.d.ts.map +1 -0
- package/dist/codeIndex/meta.js +37 -0
- package/dist/codeIndex/meta.js.map +1 -0
- package/dist/codeIndex/migrations.d.ts +96 -0
- package/dist/codeIndex/migrations.d.ts.map +1 -0
- package/dist/codeIndex/migrations.js +1302 -0
- package/dist/codeIndex/migrations.js.map +1 -0
- package/dist/codeIndex/projectTasks.d.ts +54 -0
- package/dist/codeIndex/projectTasks.d.ts.map +1 -0
- package/dist/codeIndex/projectTasks.js +81 -0
- package/dist/codeIndex/projectTasks.js.map +1 -0
- package/dist/codeIndex/provenance.d.ts +47 -0
- package/dist/codeIndex/provenance.d.ts.map +1 -0
- package/dist/codeIndex/provenance.js +48 -0
- package/dist/codeIndex/provenance.js.map +1 -0
- package/dist/codeIndex/rawSql.d.ts +54 -0
- package/dist/codeIndex/rawSql.d.ts.map +1 -0
- package/dist/codeIndex/rawSql.js +71 -0
- package/dist/codeIndex/rawSql.js.map +1 -0
- package/dist/codeIndex/schema.d.ts +99 -0
- package/dist/codeIndex/schema.d.ts.map +1 -0
- package/dist/codeIndex/schema.js +174 -0
- package/dist/codeIndex/schema.js.map +1 -0
- package/dist/codeIndex/search.d.ts +40 -0
- package/dist/codeIndex/search.d.ts.map +1 -0
- package/dist/codeIndex/search.js +316 -0
- package/dist/codeIndex/search.js.map +1 -0
- package/dist/codeIndex/shared.d.ts +27 -0
- package/dist/codeIndex/shared.d.ts.map +1 -0
- package/dist/codeIndex/shared.js +46 -0
- package/dist/codeIndex/shared.js.map +1 -0
- package/dist/codeIndex/sqliteCollection.d.ts +81 -0
- package/dist/codeIndex/sqliteCollection.d.ts.map +1 -0
- package/dist/codeIndex/sqliteCollection.js +250 -0
- package/dist/codeIndex/sqliteCollection.js.map +1 -0
- package/dist/codeIndex/whitelist.d.ts +59 -0
- package/dist/codeIndex/whitelist.d.ts.map +1 -0
- package/dist/codeIndex/whitelist.js +188 -0
- package/dist/codeIndex/whitelist.js.map +1 -0
- package/dist/codeIndexDB.d.ts +130 -277
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +364 -2748
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/codeIndexService.d.ts +87 -12
- package/dist/codeIndexService.d.ts.map +1 -1
- package/dist/codeIndexService.js +110 -34
- package/dist/codeIndexService.js.map +1 -1
- package/dist/componentScanner.d.ts +35 -0
- package/dist/componentScanner.d.ts.map +1 -1
- package/dist/componentScanner.js +114 -80
- package/dist/componentScanner.js.map +1 -1
- package/dist/config/configGeneratePath.d.ts +4 -0
- package/dist/config/configGeneratePath.d.ts.map +1 -1
- package/dist/config/configGeneratePath.js +4 -0
- package/dist/config/configGeneratePath.js.map +1 -1
- package/dist/config/configLoader.d.ts +16 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +26 -23
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +11 -7
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +21 -20
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +17 -2
- package/dist/config/effectiveConfig.d.ts.map +1 -1
- package/dist/config/effectiveConfig.js +32 -25
- package/dist/config/effectiveConfig.js.map +1 -1
- package/dist/config/lintConfigReader.d.ts +6 -0
- package/dist/config/lintConfigReader.d.ts.map +1 -1
- package/dist/config/lintConfigReader.js +6 -0
- package/dist/config/lintConfigReader.js.map +1 -1
- package/dist/config/staticObjectExtract.d.ts +4 -0
- package/dist/config/staticObjectExtract.d.ts.map +1 -1
- package/dist/config/staticObjectExtract.js +7 -3
- package/dist/config/staticObjectExtract.js.map +1 -1
- package/dist/config/thresholdRationales.d.ts +2 -0
- package/dist/config/thresholdRationales.d.ts.map +1 -1
- package/dist/config/thresholdRationales.js +5 -2
- package/dist/config/thresholdRationales.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +10 -5
- package/dist/constants.js.map +1 -1
- package/dist/conventions/conventionMiner.d.ts +105 -2
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +334 -259
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/coverage/coverageService.js +4 -4
- package/dist/coverage/coverageService.js.map +1 -1
- package/dist/daemon/args.d.ts +3 -0
- package/dist/daemon/args.d.ts.map +1 -1
- package/dist/daemon/args.js +3 -0
- package/dist/daemon/args.js.map +1 -1
- package/dist/daemon/core.d.ts +33 -27
- package/dist/daemon/core.d.ts.map +1 -1
- package/dist/daemon/core.js +198 -162
- package/dist/daemon/core.js.map +1 -1
- package/dist/daemon/lspServer.d.ts +8 -0
- package/dist/daemon/lspServer.d.ts.map +1 -1
- package/dist/daemon/lspServer.js +8 -0
- package/dist/daemon/lspServer.js.map +1 -1
- package/dist/daemon/main.d.ts +2 -0
- package/dist/daemon/main.d.ts.map +1 -1
- package/dist/daemon/main.js +2 -0
- package/dist/daemon/main.js.map +1 -1
- package/dist/daemon/resolve.d.ts +22 -7
- package/dist/daemon/resolve.d.ts.map +1 -1
- package/dist/daemon/resolve.js +29 -8
- package/dist/daemon/resolve.js.map +1 -1
- package/dist/daemon/socketClient.d.ts +10 -1
- package/dist/daemon/socketClient.d.ts.map +1 -1
- package/dist/daemon/socketClient.js +10 -1
- package/dist/daemon/socketClient.js.map +1 -1
- package/dist/daemon/socketServer.d.ts +13 -0
- package/dist/daemon/socketServer.d.ts.map +1 -1
- package/dist/daemon/socketServer.js +13 -0
- package/dist/daemon/socketServer.js.map +1 -1
- package/dist/dataPaths.d.ts +27 -8
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +28 -44
- package/dist/dataPaths.js.map +1 -1
- package/dist/dismissals.d.ts +22 -1
- package/dist/dismissals.d.ts.map +1 -1
- package/dist/dismissals.js +22 -1
- package/dist/dismissals.js.map +1 -1
- package/dist/enforcement/gate.d.ts +1 -0
- package/dist/enforcement/gate.d.ts.map +1 -1
- package/dist/enforcement/gate.js +1 -0
- package/dist/enforcement/gate.js.map +1 -1
- package/dist/fingerprint.d.ts +6 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +46 -1
- package/dist/fingerprint.js.map +1 -1
- package/dist/functionScanner.d.ts +30 -1
- package/dist/functionScanner.d.ts.map +1 -1
- package/dist/functionScanner.js +258 -256
- package/dist/functionScanner.js.map +1 -1
- package/dist/generators/AWSQConfigGenerator.d.ts +23 -0
- package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
- package/dist/generators/AWSQConfigGenerator.js +23 -0
- package/dist/generators/AWSQConfigGenerator.js.map +1 -1
- package/dist/generators/BaseConfigGenerator.d.ts +7 -0
- package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.js +7 -0
- package/dist/generators/BaseConfigGenerator.js.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.d.ts +23 -0
- package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.js +23 -0
- package/dist/generators/ClaudeConfigGenerator.js.map +1 -1
- package/dist/generators/ClineConfigGenerator.d.ts +23 -0
- package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClineConfigGenerator.js +23 -0
- package/dist/generators/ClineConfigGenerator.js.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.d.ts +23 -0
- package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.js +23 -0
- package/dist/generators/CodeiumConfigGenerator.js.map +1 -1
- package/dist/generators/CodexConfigGenerator.d.ts +23 -0
- package/dist/generators/CodexConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodexConfigGenerator.js +23 -0
- package/dist/generators/CodexConfigGenerator.js.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.d.ts +9 -0
- package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.js +9 -0
- package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
- package/dist/generators/ContinueConfigGenerator.d.ts +23 -0
- package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ContinueConfigGenerator.js +23 -0
- package/dist/generators/ContinueConfigGenerator.js.map +1 -1
- package/dist/generators/CopilotConfigGenerator.d.ts +23 -0
- package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CopilotConfigGenerator.js +23 -0
- package/dist/generators/CopilotConfigGenerator.js.map +1 -1
- package/dist/generators/CursorConfigGenerator.d.ts +23 -0
- package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CursorConfigGenerator.js +23 -0
- package/dist/generators/CursorConfigGenerator.js.map +1 -1
- package/dist/generators/GeminiConfigGenerator.d.ts +23 -0
- package/dist/generators/GeminiConfigGenerator.d.ts.map +1 -1
- package/dist/generators/GeminiConfigGenerator.js +23 -0
- package/dist/generators/GeminiConfigGenerator.js.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.d.ts +23 -0
- package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.js +23 -0
- package/dist/generators/VSCodeConfigGenerator.js.map +1 -1
- package/dist/generators/ZCodeConfigGenerator.d.ts +23 -0
- package/dist/generators/ZCodeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ZCodeConfigGenerator.js +23 -0
- package/dist/generators/ZCodeConfigGenerator.js.map +1 -1
- package/dist/graph/blastRadius.d.ts.map +1 -1
- package/dist/graph/blastRadius.js +12 -7
- package/dist/graph/blastRadius.js.map +1 -1
- package/dist/graph/callGraph.d.ts +31 -0
- package/dist/graph/callGraph.d.ts.map +1 -1
- package/dist/graph/callGraph.js +62 -32
- package/dist/graph/callGraph.js.map +1 -1
- package/dist/graph/importClassification.d.ts +28 -0
- package/dist/graph/importClassification.d.ts.map +1 -1
- package/dist/graph/importClassification.js +57 -0
- package/dist/graph/importClassification.js.map +1 -1
- package/dist/graph/importGraph.d.ts +38 -0
- package/dist/graph/importGraph.d.ts.map +1 -1
- package/dist/graph/importGraph.js +38 -0
- package/dist/graph/importGraph.js.map +1 -1
- package/dist/graph/outputFormatter.d.ts +12 -4
- package/dist/graph/outputFormatter.d.ts.map +1 -1
- package/dist/graph/outputFormatter.js +12 -62
- package/dist/graph/outputFormatter.js.map +1 -1
- package/dist/hooks/codex.d.ts +8 -1
- package/dist/hooks/codex.d.ts.map +1 -1
- package/dist/hooks/codex.js +7 -0
- package/dist/hooks/codex.js.map +1 -1
- package/dist/hooks/core.d.ts +6 -0
- package/dist/hooks/core.d.ts.map +1 -1
- package/dist/hooks/core.js +6 -1
- package/dist/hooks/core.js.map +1 -1
- package/dist/hooks/cursor.d.ts +11 -1
- package/dist/hooks/cursor.d.ts.map +1 -1
- package/dist/hooks/cursor.js +10 -0
- package/dist/hooks/cursor.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/installConfig.d.ts +23 -5
- package/dist/installConfig.d.ts.map +1 -1
- package/dist/installConfig.js +23 -5
- package/dist/installConfig.js.map +1 -1
- package/dist/installer.d.ts +6 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +6 -0
- package/dist/installer.js.map +1 -1
- package/dist/invariants/ruleEngine.d.ts +3 -0
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +156 -119
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/invariants/ruleValidator.d.ts +3 -0
- package/dist/invariants/ruleValidator.d.ts.map +1 -1
- package/dist/invariants/ruleValidator.js +185 -178
- package/dist/invariants/ruleValidator.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +4 -1
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +8 -19
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +8 -0
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +55 -5
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +1 -1
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +4 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +37 -3
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +11 -14
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/go/analyzer-darwin-amd64 +0 -0
- package/dist/languages/go/analyzer-darwin-arm64 +0 -0
- package/dist/languages/go/analyzer-linux-amd64 +0 -0
- package/dist/languages/go/analyzer-linux-arm64 +0 -0
- package/dist/languages/go/analyzer-src/analyzer.go +2 -0
- package/dist/languages/go/analyzer-src/dataaccess.go +388 -0
- package/dist/languages/go/analyzer-src/types.go +8 -0
- package/dist/languages/go/analyzer-windows-amd64.exe +0 -0
- package/dist/languages/go/database-packages.json +8 -0
- package/dist/languages/go/goResolution.d.ts +229 -0
- package/dist/languages/go/goResolution.d.ts.map +1 -0
- package/dist/languages/go/goResolution.js +799 -0
- package/dist/languages/go/goResolution.js.map +1 -0
- package/dist/languages/go/stdlib-packages.json +353 -0
- package/dist/languages/go/tenantInputs.d.ts +37 -0
- package/dist/languages/go/tenantInputs.d.ts.map +1 -0
- package/dist/languages/go/tenantInputs.js +121 -0
- package/dist/languages/go/tenantInputs.js.map +1 -0
- package/dist/languages/index.d.ts +1 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +5 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/json/JsonAdapter.d.ts +177 -0
- package/dist/languages/json/JsonAdapter.d.ts.map +1 -0
- package/dist/languages/json/JsonAdapter.js +531 -0
- package/dist/languages/json/JsonAdapter.js.map +1 -0
- package/dist/languages/locationIndex.d.ts +41 -0
- package/dist/languages/locationIndex.d.ts.map +1 -0
- package/dist/languages/locationIndex.js +42 -0
- package/dist/languages/locationIndex.js.map +1 -0
- package/dist/languages/sql/dialectDetection.d.ts +57 -0
- package/dist/languages/sql/dialectDetection.d.ts.map +1 -0
- package/dist/languages/sql/dialectDetection.js +135 -0
- package/dist/languages/sql/dialectDetection.js.map +1 -0
- package/dist/languages/sql/sqlAst.d.ts +494 -0
- package/dist/languages/sql/sqlAst.d.ts.map +1 -0
- package/dist/languages/sql/sqlAst.js +1817 -0
- package/dist/languages/sql/sqlAst.js.map +1 -0
- package/dist/languages/testConventions.d.ts +12 -1
- package/dist/languages/testConventions.d.ts.map +1 -1
- package/dist/languages/testConventions.js +13 -1
- package/dist/languages/testConventions.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/converter.js +2 -2
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/languages/tree-sitter/parser.d.ts +3 -4
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +10 -4
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/tree-sitter/rawNode.d.ts +3 -0
- package/dist/languages/tree-sitter/rawNode.d.ts.map +1 -1
- package/dist/languages/tree-sitter/rawNode.js +3 -0
- package/dist/languages/tree-sitter/rawNode.js.map +1 -1
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +36 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +168 -45
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/languages/typescript/database-packages.json +19 -0
- package/dist/ledger.d.ts +118 -2
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +167 -45
- package/dist/ledger.js.map +1 -1
- package/dist/legacyIndexNotice.d.ts +32 -0
- package/dist/legacyIndexNotice.d.ts.map +1 -0
- package/dist/legacyIndexNotice.js +89 -0
- package/dist/legacyIndexNotice.js.map +1 -0
- package/dist/mcp-tools/discoveryQueries.d.ts +55 -0
- package/dist/mcp-tools/discoveryQueries.d.ts.map +1 -0
- package/dist/mcp-tools/discoveryQueries.js +157 -0
- package/dist/mcp-tools/discoveryQueries.js.map +1 -0
- package/dist/mcp-tools/projectTasks.d.ts +9 -123
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +180 -170
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp-tools/whitelistTools.d.ts +20 -0
- package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
- package/dist/mcp-tools/whitelistTools.js +20 -0
- package/dist/mcp-tools/whitelistTools.js.map +1 -1
- package/dist/mcp-tools/workflowGuide.d.ts +9 -0
- package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
- package/dist/mcp-tools/workflowGuide.js +13 -5
- package/dist/mcp-tools/workflowGuide.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts +78 -0
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js +203 -340
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp-ui-simple.d.ts +2 -0
- package/dist/mcp-ui-simple.d.ts.map +1 -1
- package/dist/mcp-ui-simple.js +12 -5
- package/dist/mcp-ui-simple.js.map +1 -1
- package/dist/mcp.d.ts +6 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +832 -851
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts +29 -33
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +102 -791
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/mcpAutoIndex.d.ts +5 -0
- package/dist/mcpAutoIndex.d.ts.map +1 -1
- package/dist/mcpAutoIndex.js +7 -2
- package/dist/mcpAutoIndex.js.map +1 -1
- package/dist/mcpDiagnostics.d.ts +7 -0
- package/dist/mcpDiagnostics.d.ts.map +1 -1
- package/dist/mcpDiagnostics.js +34 -9
- package/dist/mcpDiagnostics.js.map +1 -1
- package/dist/mcpToolErrors.d.ts +16 -0
- package/dist/mcpToolErrors.d.ts.map +1 -1
- package/dist/mcpToolErrors.js +16 -0
- package/dist/mcpToolErrors.js.map +1 -1
- package/dist/nextFile.d.ts +6 -0
- package/dist/nextFile.d.ts.map +1 -1
- package/dist/nextFile.js +6 -0
- package/dist/nextFile.js.map +1 -1
- package/dist/nextFileIncremental.d.ts +44 -4
- package/dist/nextFileIncremental.d.ts.map +1 -1
- package/dist/nextFileIncremental.js +47 -7
- package/dist/nextFileIncremental.js.map +1 -1
- package/dist/phase/batchFunctions.d.ts +38 -0
- package/dist/phase/batchFunctions.d.ts.map +1 -0
- package/dist/phase/batchFunctions.js +64 -0
- package/dist/phase/batchFunctions.js.map +1 -0
- package/dist/phase/checks.d.ts +39 -0
- package/dist/phase/checks.d.ts.map +1 -0
- package/dist/phase/checks.js +48 -0
- package/dist/phase/checks.js.map +1 -0
- package/dist/phase/codeBlocks.d.ts +25 -0
- package/dist/phase/codeBlocks.d.ts.map +1 -0
- package/dist/phase/codeBlocks.js +268 -0
- package/dist/phase/codeBlocks.js.map +1 -0
- package/dist/phase/colorMath.d.ts +41 -0
- package/dist/phase/colorMath.d.ts.map +1 -0
- package/dist/phase/colorMath.js +109 -0
- package/dist/phase/colorMath.js.map +1 -0
- package/dist/phase/config.d.ts +29 -0
- package/dist/phase/config.d.ts.map +1 -0
- package/dist/phase/config.js +54 -0
- package/dist/phase/config.js.map +1 -0
- package/dist/phase/consumed.d.ts +47 -0
- package/dist/phase/consumed.d.ts.map +1 -0
- package/dist/phase/consumed.js +22 -0
- package/dist/phase/consumed.js.map +1 -0
- package/dist/phase/conventionMining.d.ts +59 -0
- package/dist/phase/conventionMining.d.ts.map +1 -0
- package/dist/phase/conventionMining.js +152 -0
- package/dist/phase/conventionMining.js.map +1 -0
- package/dist/phase/coverage.d.ts +65 -0
- package/dist/phase/coverage.d.ts.map +1 -0
- package/dist/phase/coverage.js +121 -0
- package/dist/phase/coverage.js.map +1 -0
- package/dist/phase/dag.d.ts +49 -0
- package/dist/phase/dag.d.ts.map +1 -0
- package/dist/phase/dag.js +120 -0
- package/dist/phase/dag.js.map +1 -0
- package/dist/phase/dataAccessCalls.d.ts +33 -0
- package/dist/phase/dataAccessCalls.d.ts.map +1 -0
- package/dist/phase/dataAccessCalls.js +42 -0
- package/dist/phase/dataAccessCalls.js.map +1 -0
- package/dist/phase/dataAccessCallsCandidates.d.ts +25 -0
- package/dist/phase/dataAccessCallsCandidates.d.ts.map +1 -0
- package/dist/phase/dataAccessCallsCandidates.js +27 -0
- package/dist/phase/dataAccessCallsCandidates.js.map +1 -0
- package/dist/phase/dynamicSql.d.ts +18 -0
- package/dist/phase/dynamicSql.d.ts.map +1 -0
- package/dist/phase/dynamicSql.js +20 -0
- package/dist/phase/dynamicSql.js.map +1 -0
- package/dist/phase/exportForm.d.ts +23 -0
- package/dist/phase/exportForm.d.ts.map +1 -0
- package/dist/phase/exportForm.js +29 -0
- package/dist/phase/exportForm.js.map +1 -0
- package/dist/phase/exportSymbols.d.ts +21 -0
- package/dist/phase/exportSymbols.d.ts.map +1 -0
- package/dist/phase/exportSymbols.js +28 -0
- package/dist/phase/exportSymbols.js.map +1 -0
- package/dist/phase/fileHeader.d.ts +15 -0
- package/dist/phase/fileHeader.d.ts.map +1 -0
- package/dist/phase/fileHeader.js +33 -0
- package/dist/phase/fileHeader.js.map +1 -0
- package/dist/phase/fileImports.d.ts +39 -0
- package/dist/phase/fileImports.d.ts.map +1 -0
- package/dist/phase/fileImports.js +138 -0
- package/dist/phase/fileImports.js.map +1 -0
- package/dist/phase/fileSymbols.d.ts +25 -0
- package/dist/phase/fileSymbols.d.ts.map +1 -0
- package/dist/phase/fileSymbols.js +302 -0
- package/dist/phase/fileSymbols.js.map +1 -0
- package/dist/phase/functionBodies.d.ts +28 -0
- package/dist/phase/functionBodies.d.ts.map +1 -0
- package/dist/phase/functionBodies.js +67 -0
- package/dist/phase/functionBodies.js.map +1 -0
- package/dist/phase/functionIndex.d.ts +28 -0
- package/dist/phase/functionIndex.d.ts.map +1 -0
- package/dist/phase/functionIndex.js +152 -0
- package/dist/phase/functionIndex.js.map +1 -0
- package/dist/phase/goFunctionAnalysis.d.ts +47 -0
- package/dist/phase/goFunctionAnalysis.d.ts.map +1 -0
- package/dist/phase/goFunctionAnalysis.js +291 -0
- package/dist/phase/goFunctionAnalysis.js.map +1 -0
- package/dist/phase/goFunctions.d.ts +38 -0
- package/dist/phase/goFunctions.d.ts.map +1 -0
- package/dist/phase/goFunctions.js +154 -0
- package/dist/phase/goFunctions.js.map +1 -0
- package/dist/phase/goImports.d.ts +29 -0
- package/dist/phase/goImports.d.ts.map +1 -0
- package/dist/phase/goImports.js +68 -0
- package/dist/phase/goImports.js.map +1 -0
- package/dist/phase/goPackageBindings.d.ts +22 -0
- package/dist/phase/goPackageBindings.d.ts.map +1 -0
- package/dist/phase/goPackageBindings.js +30 -0
- package/dist/phase/goPackageBindings.js.map +1 -0
- package/dist/phase/goSwitches.d.ts +28 -0
- package/dist/phase/goSwitches.d.ts.map +1 -0
- package/dist/phase/goSwitches.js +52 -0
- package/dist/phase/goSwitches.js.map +1 -0
- package/dist/phase/importForm.d.ts +20 -0
- package/dist/phase/importForm.d.ts.map +1 -0
- package/dist/phase/importForm.js +27 -0
- package/dist/phase/importForm.js.map +1 -0
- package/dist/phase/importSpecifiers.d.ts +20 -0
- package/dist/phase/importSpecifiers.d.ts.map +1 -0
- package/dist/phase/importSpecifiers.js +30 -0
- package/dist/phase/importSpecifiers.js.map +1 -0
- package/dist/phase/imports.d.ts +18 -0
- package/dist/phase/imports.d.ts.map +1 -0
- package/dist/phase/imports.js +25 -0
- package/dist/phase/imports.js.map +1 -0
- package/dist/phase/jsonDocument.d.ts +26 -0
- package/dist/phase/jsonDocument.d.ts.map +1 -0
- package/dist/phase/jsonDocument.js +36 -0
- package/dist/phase/jsonDocument.js.map +1 -0
- package/dist/phase/localBinding.d.ts +33 -0
- package/dist/phase/localBinding.d.ts.map +1 -0
- package/dist/phase/localBinding.js +208 -0
- package/dist/phase/localBinding.js.map +1 -0
- package/dist/phase/loopQueries.d.ts +31 -0
- package/dist/phase/loopQueries.d.ts.map +1 -0
- package/dist/phase/loopQueries.js +44 -0
- package/dist/phase/loopQueries.js.map +1 -0
- package/dist/phase/loopQueryCandidates.d.ts +26 -0
- package/dist/phase/loopQueryCandidates.d.ts.map +1 -0
- package/dist/phase/loopQueryCandidates.js +28 -0
- package/dist/phase/loopQueryCandidates.js.map +1 -0
- package/dist/phase/oracles.d.ts +216 -0
- package/dist/phase/oracles.d.ts.map +1 -0
- package/dist/phase/oracles.js +484 -0
- package/dist/phase/oracles.js.map +1 -0
- package/dist/phase/phaseModel.d.ts +190 -0
- package/dist/phase/phaseModel.d.ts.map +1 -0
- package/dist/phase/phaseModel.js +468 -0
- package/dist/phase/phaseModel.js.map +1 -0
- package/dist/phase/producers.d.ts +450 -0
- package/dist/phase/producers.d.ts.map +1 -0
- package/dist/phase/producers.js +1027 -0
- package/dist/phase/producers.js.map +1 -0
- package/dist/phase/querySiteCandidates.d.ts +27 -0
- package/dist/phase/querySiteCandidates.d.ts.map +1 -0
- package/dist/phase/querySiteCandidates.js +66 -0
- package/dist/phase/querySiteCandidates.js.map +1 -0
- package/dist/phase/querySites.d.ts +30 -0
- package/dist/phase/querySites.d.ts.map +1 -0
- package/dist/phase/querySites.js +93 -0
- package/dist/phase/querySites.js.map +1 -0
- package/dist/phase/reachability.d.ts +61 -0
- package/dist/phase/reachability.d.ts.map +1 -0
- package/dist/phase/reachability.js +104 -0
- package/dist/phase/reachability.js.map +1 -0
- package/dist/phase/reactComponents.d.ts +33 -0
- package/dist/phase/reactComponents.d.ts.map +1 -0
- package/dist/phase/reactComponents.js +47 -0
- package/dist/phase/reactComponents.js.map +1 -0
- package/dist/phase/receiverActivity.d.ts +35 -0
- package/dist/phase/receiverActivity.d.ts.map +1 -0
- package/dist/phase/receiverActivity.js +66 -0
- package/dist/phase/receiverActivity.js.map +1 -0
- package/dist/phase/receiverConsumers.d.ts +200 -0
- package/dist/phase/receiverConsumers.d.ts.map +1 -0
- package/dist/phase/receiverConsumers.js +1025 -0
- package/dist/phase/receiverConsumers.js.map +1 -0
- package/dist/phase/receiverProvenance.d.ts +97 -0
- package/dist/phase/receiverProvenance.d.ts.map +1 -0
- package/dist/phase/receiverProvenance.js +337 -0
- package/dist/phase/receiverProvenance.js.map +1 -0
- package/dist/phase/routing.d.ts +47 -0
- package/dist/phase/routing.d.ts.map +1 -0
- package/dist/phase/routing.js +62 -0
- package/dist/phase/routing.js.map +1 -0
- package/dist/phase/rules/conventions.d.ts +61 -0
- package/dist/phase/rules/conventions.d.ts.map +1 -0
- package/dist/phase/rules/conventions.js +403 -0
- package/dist/phase/rules/conventions.js.map +1 -0
- package/dist/phase/rules/crossDomain.d.ts +102 -0
- package/dist/phase/rules/crossDomain.d.ts.map +1 -0
- package/dist/phase/rules/crossDomain.js +918 -0
- package/dist/phase/rules/crossDomain.js.map +1 -0
- package/dist/phase/rules/dataAccess.d.ts +62 -0
- package/dist/phase/rules/dataAccess.d.ts.map +1 -0
- package/dist/phase/rules/dataAccess.js +480 -0
- package/dist/phase/rules/dataAccess.js.map +1 -0
- package/dist/phase/rules/dependencyGraph.d.ts +44 -0
- package/dist/phase/rules/dependencyGraph.d.ts.map +1 -0
- package/dist/phase/rules/dependencyGraph.js +260 -0
- package/dist/phase/rules/dependencyGraph.js.map +1 -0
- package/dist/phase/rules/documentation.d.ts +45 -0
- package/dist/phase/rules/documentation.d.ts.map +1 -0
- package/dist/phase/rules/documentation.js +340 -0
- package/dist/phase/rules/documentation.js.map +1 -0
- package/dist/phase/rules/dry.d.ts +109 -0
- package/dist/phase/rules/dry.d.ts.map +1 -0
- package/dist/phase/rules/dry.js +803 -0
- package/dist/phase/rules/dry.js.map +1 -0
- package/dist/phase/rules/fileDocumentation.d.ts +36 -0
- package/dist/phase/rules/fileDocumentation.d.ts.map +1 -0
- package/dist/phase/rules/fileDocumentation.js +134 -0
- package/dist/phase/rules/fileDocumentation.js.map +1 -0
- package/dist/phase/rules/functionBodies.d.ts +33 -0
- package/dist/phase/rules/functionBodies.d.ts.map +1 -0
- package/dist/phase/rules/functionBodies.js +71 -0
- package/dist/phase/rules/functionBodies.js.map +1 -0
- package/dist/phase/rules/goRules.d.ts +49 -0
- package/dist/phase/rules/goRules.d.ts.map +1 -0
- package/dist/phase/rules/goRules.js +209 -0
- package/dist/phase/rules/goRules.js.map +1 -0
- package/dist/phase/rules/querySites.d.ts +29 -0
- package/dist/phase/rules/querySites.d.ts.map +1 -0
- package/dist/phase/rules/querySites.js +94 -0
- package/dist/phase/rules/querySites.js.map +1 -0
- package/dist/phase/rules/react.d.ts +38 -0
- package/dist/phase/rules/react.d.ts.map +1 -0
- package/dist/phase/rules/react.js +202 -0
- package/dist/phase/rules/react.js.map +1 -0
- package/dist/phase/rules/registry.d.ts +170 -0
- package/dist/phase/rules/registry.d.ts.map +1 -0
- package/dist/phase/rules/registry.js +103 -0
- package/dist/phase/rules/registry.js.map +1 -0
- package/dist/phase/rules/schema.d.ts +79 -0
- package/dist/phase/rules/schema.d.ts.map +1 -0
- package/dist/phase/rules/schema.js +273 -0
- package/dist/phase/rules/schema.js.map +1 -0
- package/dist/phase/rules/schemaJson.d.ts +32 -0
- package/dist/phase/rules/schemaJson.d.ts.map +1 -0
- package/dist/phase/rules/schemaJson.js +81 -0
- package/dist/phase/rules/schemaJson.js.map +1 -0
- package/dist/phase/rules/schemaValidator.d.ts +38 -0
- package/dist/phase/rules/schemaValidator.d.ts.map +1 -0
- package/dist/phase/rules/schemaValidator.js +107 -0
- package/dist/phase/rules/schemaValidator.js.map +1 -0
- package/dist/phase/rules/secrets.d.ts +34 -0
- package/dist/phase/rules/secrets.d.ts.map +1 -0
- package/dist/phase/rules/secrets.js +208 -0
- package/dist/phase/rules/secrets.js.map +1 -0
- package/dist/phase/rules/security.d.ts +30 -0
- package/dist/phase/rules/security.d.ts.map +1 -0
- package/dist/phase/rules/security.js +85 -0
- package/dist/phase/rules/security.js.map +1 -0
- package/dist/phase/rules/securityDefects.d.ts +37 -0
- package/dist/phase/rules/securityDefects.d.ts.map +1 -0
- package/dist/phase/rules/securityDefects.js +167 -0
- package/dist/phase/rules/securityDefects.js.map +1 -0
- package/dist/phase/rules/solid.d.ts +61 -0
- package/dist/phase/rules/solid.d.ts.map +1 -0
- package/dist/phase/rules/solid.js +562 -0
- package/dist/phase/rules/solid.js.map +1 -0
- package/dist/phase/rules/styles.d.ts +77 -0
- package/dist/phase/rules/styles.d.ts.map +1 -0
- package/dist/phase/rules/styles.js +868 -0
- package/dist/phase/rules/styles.js.map +1 -0
- package/dist/phase/rules/unreferencedModule.d.ts +26 -0
- package/dist/phase/rules/unreferencedModule.d.ts.map +1 -0
- package/dist/phase/rules/unreferencedModule.js +61 -0
- package/dist/phase/rules/unreferencedModule.js.map +1 -0
- package/dist/phase/runner.d.ts +549 -0
- package/dist/phase/runner.d.ts.map +1 -0
- package/dist/phase/runner.js +1098 -0
- package/dist/phase/runner.js.map +1 -0
- package/dist/phase/schemaCode.d.ts +34 -0
- package/dist/phase/schemaCode.d.ts.map +1 -0
- package/dist/phase/schemaCode.js +53 -0
- package/dist/phase/schemaCode.js.map +1 -0
- package/dist/phase/schemaObjects.d.ts +37 -0
- package/dist/phase/schemaObjects.d.ts.map +1 -0
- package/dist/phase/schemaObjects.js +139 -0
- package/dist/phase/schemaObjects.js.map +1 -0
- package/dist/phase/schemaUsage.d.ts +30 -0
- package/dist/phase/schemaUsage.d.ts.map +1 -0
- package/dist/phase/schemaUsage.js +99 -0
- package/dist/phase/schemaUsage.js.map +1 -0
- package/dist/phase/schemaUsageCandidates.d.ts +32 -0
- package/dist/phase/schemaUsageCandidates.d.ts.map +1 -0
- package/dist/phase/schemaUsageCandidates.js +165 -0
- package/dist/phase/schemaUsageCandidates.js.map +1 -0
- package/dist/phase/schemaValidations.d.ts +34 -0
- package/dist/phase/schemaValidations.d.ts.map +1 -0
- package/dist/phase/schemaValidations.js +53 -0
- package/dist/phase/schemaValidations.js.map +1 -0
- package/dist/phase/secretCandidates.d.ts +28 -0
- package/dist/phase/secretCandidates.d.ts.map +1 -0
- package/dist/phase/secretCandidates.js +132 -0
- package/dist/phase/secretCandidates.js.map +1 -0
- package/dist/phase/securityCandidates.d.ts +29 -0
- package/dist/phase/securityCandidates.d.ts.map +1 -0
- package/dist/phase/securityCandidates.js +469 -0
- package/dist/phase/securityCandidates.js.map +1 -0
- package/dist/phase/seeded-defects.d.ts +18 -0
- package/dist/phase/seeded-defects.d.ts.map +1 -0
- package/dist/phase/seeded-defects.js +61 -0
- package/dist/phase/seeded-defects.js.map +1 -0
- package/dist/phase/stringLiterals.d.ts +24 -0
- package/dist/phase/stringLiterals.d.ts.map +1 -0
- package/dist/phase/stringLiterals.js +35 -0
- package/dist/phase/stringLiterals.js.map +1 -0
- package/dist/phase/stylesCss.d.ts +25 -0
- package/dist/phase/stylesCss.d.ts.map +1 -0
- package/dist/phase/stylesCss.js +34 -0
- package/dist/phase/stylesCss.js.map +1 -0
- package/dist/phase/stylesMarkup.d.ts +31 -0
- package/dist/phase/stylesMarkup.d.ts.map +1 -0
- package/dist/phase/stylesMarkup.js +48 -0
- package/dist/phase/stylesMarkup.js.map +1 -0
- package/dist/phase/stylesSource.d.ts +36 -0
- package/dist/phase/stylesSource.d.ts.map +1 -0
- package/dist/phase/stylesSource.js +50 -0
- package/dist/phase/stylesSource.js.map +1 -0
- package/dist/phase/typeDeclarations.d.ts +33 -0
- package/dist/phase/typeDeclarations.d.ts.map +1 -0
- package/dist/phase/typeDeclarations.js +76 -0
- package/dist/phase/typeDeclarations.js.map +1 -0
- package/dist/phase/types.d.ts +2191 -0
- package/dist/phase/types.d.ts.map +1 -0
- package/dist/phase/types.js +27 -0
- package/dist/phase/types.js.map +1 -0
- package/dist/phase/withinFileProvenance.d.ts +50 -0
- package/dist/phase/withinFileProvenance.d.ts.map +1 -0
- package/dist/phase/withinFileProvenance.js +112 -0
- package/dist/phase/withinFileProvenance.js.map +1 -0
- package/dist/pipeline.d.ts +37 -30
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +131 -326
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +104 -21
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +1145 -988
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/pipelineTypes.d.ts +21 -0
- package/dist/pipelineTypes.d.ts.map +1 -1
- package/dist/pipelineTypes.js +21 -10
- package/dist/pipelineTypes.js.map +1 -1
- package/dist/presets/presets.d.ts +9 -2
- package/dist/presets/presets.d.ts.map +1 -1
- package/dist/presets/presets.js +15 -26
- package/dist/presets/presets.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts +14 -3
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +14 -3
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts +5 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +5 -1
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts +9 -2
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +19 -5
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/reportGenerator.d.ts +9 -2
- package/dist/reporting/reportGenerator.d.ts.map +1 -1
- package/dist/reporting/reportGenerator.js +9 -2
- package/dist/reporting/reportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts +14 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +14 -0
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/reporting/sizeDistribution.d.ts +19 -2
- package/dist/reporting/sizeDistribution.d.ts.map +1 -1
- package/dist/reporting/sizeDistribution.js +23 -5
- package/dist/reporting/sizeDistribution.js.map +1 -1
- package/dist/ruleAliases.d.ts +6 -8
- package/dist/ruleAliases.d.ts.map +1 -1
- package/dist/ruleAliases.js +29 -4
- package/dist/ruleAliases.js.map +1 -1
- package/dist/search/QueryParser.d.ts +1 -0
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +32 -91
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts +19 -20
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js +214 -194
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/services/ProjectTaskRepository.d.ts +65 -3
- package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
- package/dist/services/ProjectTaskRepository.js +94 -25
- package/dist/services/ProjectTaskRepository.js.map +1 -1
- package/dist/services/auditJobService.d.ts +17 -1
- package/dist/services/auditJobService.d.ts.map +1 -1
- package/dist/services/auditJobService.js +16 -0
- package/dist/services/auditJobService.js.map +1 -1
- package/dist/services/fileAccounting.d.ts +43 -5
- package/dist/services/fileAccounting.d.ts.map +1 -1
- package/dist/services/fileAccounting.js +48 -9
- package/dist/services/fileAccounting.js.map +1 -1
- package/dist/services/projectTaskHelpers.d.ts +45 -0
- package/dist/services/projectTaskHelpers.d.ts.map +1 -1
- package/dist/services/projectTaskHelpers.js +45 -0
- package/dist/services/projectTaskHelpers.js.map +1 -1
- package/dist/services/whitelistService.d.ts +40 -5
- package/dist/services/whitelistService.d.ts.map +1 -1
- package/dist/services/whitelistService.js +87 -46
- package/dist/services/whitelistService.js.map +1 -1
- package/dist/sqlite/betterSqlite3.d.ts +35 -1
- package/dist/sqlite/betterSqlite3.d.ts.map +1 -1
- package/dist/sqlite/betterSqlite3.js +35 -1
- package/dist/sqlite/betterSqlite3.js.map +1 -1
- package/dist/sqlite/driver.d.ts +23 -0
- package/dist/sqlite/driver.d.ts.map +1 -1
- package/dist/sqlite/driver.js +23 -0
- package/dist/sqlite/driver.js.map +1 -1
- package/dist/sqlite/nodeSqlite.d.ts +35 -1
- package/dist/sqlite/nodeSqlite.d.ts.map +1 -1
- package/dist/sqlite/nodeSqlite.js +35 -1
- package/dist/sqlite/nodeSqlite.js.map +1 -1
- package/dist/structuralSignature.d.ts +9 -0
- package/dist/structuralSignature.d.ts.map +1 -1
- package/dist/structuralSignature.js +9 -0
- package/dist/structuralSignature.js.map +1 -1
- package/dist/styles/cssAstExtractor.d.ts +15 -0
- package/dist/styles/cssAstExtractor.d.ts.map +1 -1
- package/dist/styles/cssAstExtractor.js +15 -0
- package/dist/styles/cssAstExtractor.js.map +1 -1
- package/dist/styles/normalizer.d.ts +6 -0
- package/dist/styles/normalizer.d.ts.map +1 -1
- package/dist/styles/normalizer.js +6 -0
- package/dist/styles/normalizer.js.map +1 -1
- package/dist/styles/styleExtractor.d.ts +29 -2
- package/dist/styles/styleExtractor.d.ts.map +1 -1
- package/dist/styles/styleExtractor.js +67 -48
- package/dist/styles/styleExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.d.ts +8 -0
- package/dist/styles/styleIndexer.d.ts.map +1 -1
- package/dist/styles/styleIndexer.js +135 -138
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +12 -0
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +12 -0
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/styles/tailwindExpander.d.ts +3 -0
- package/dist/styles/tailwindExpander.d.ts.map +1 -1
- package/dist/styles/tailwindExpander.js +8 -4
- package/dist/styles/tailwindExpander.js.map +1 -1
- package/dist/styles/tailwindProbe.d.ts +9 -0
- package/dist/styles/tailwindProbe.d.ts.map +1 -1
- package/dist/styles/tailwindProbe.js +181 -166
- package/dist/styles/tailwindProbe.js.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts +13 -0
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +13 -0
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/symbols.d.ts +13 -11
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +14 -21
- package/dist/symbols.js.map +1 -1
- package/dist/telemetry.d.ts +31 -3
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +31 -3
- package/dist/telemetry.js.map +1 -1
- package/dist/tool-registry.d.ts +37 -1
- package/dist/tool-registry.d.ts.map +1 -1
- package/dist/tool-registry.js +37 -1
- package/dist/tool-registry.js.map +1 -1
- package/dist/types.d.ts +106 -92
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -16
- package/dist/types.js.map +1 -1
- package/dist/ui/InteractivePrompts.d.ts +12 -0
- package/dist/ui/InteractivePrompts.d.ts.map +1 -1
- package/dist/ui/InteractivePrompts.js +12 -0
- package/dist/ui/InteractivePrompts.js.map +1 -1
- package/dist/utils/astParser.d.ts +15 -0
- package/dist/utils/astParser.d.ts.map +1 -1
- package/dist/utils/astParser.js +15 -0
- package/dist/utils/astParser.js.map +1 -1
- package/dist/utils/astUtils.d.ts +60 -0
- package/dist/utils/astUtils.d.ts.map +1 -1
- package/dist/utils/astUtils.js +350 -362
- package/dist/utils/astUtils.js.map +1 -1
- package/dist/utils/contentHash.d.ts +22 -0
- package/dist/utils/contentHash.d.ts.map +1 -0
- package/dist/utils/contentHash.js +26 -0
- package/dist/utils/contentHash.js.map +1 -0
- package/dist/utils/dependencyExtractor.d.ts +28 -0
- package/dist/utils/dependencyExtractor.d.ts.map +1 -1
- package/dist/utils/dependencyExtractor.js +28 -1
- package/dist/utils/dependencyExtractor.js.map +1 -1
- package/dist/utils/errorMessage.d.ts +10 -0
- package/dist/utils/errorMessage.d.ts.map +1 -0
- package/dist/utils/errorMessage.js +12 -0
- package/dist/utils/errorMessage.js.map +1 -0
- package/dist/utils/fileDiscovery.d.ts +61 -2
- package/dist/utils/fileDiscovery.d.ts.map +1 -1
- package/dist/utils/fileDiscovery.js +131 -23
- package/dist/utils/fileDiscovery.js.map +1 -1
- package/dist/utils/reactDetection.d.ts +34 -5
- package/dist/utils/reactDetection.d.ts.map +1 -1
- package/dist/utils/reactDetection.js +73 -52
- package/dist/utils/reactDetection.js.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/workers/auditWorker.js +7 -3
- package/dist/workers/auditWorker.js.map +1 -1
- package/examples/.auditrc.example.json +0 -8
- package/examples/.auditrc.json +0 -11
- package/examples/.code-auditor.json +0 -2
- package/examples/nextjs.auditrc.json +0 -8
- package/examples/node-api.auditrc.json +0 -7
- package/examples/react.auditrc.json +0 -6
- package/examples/tolerant-solid.auditrc.json +0 -5
- package/package.json +14 -8
- package/plugin/.claude-plugin/manifest.json +16 -0
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/hooks.json +6 -2
- package/plugin/scripts/hook-audit.sh +41 -14
- package/plugin/scripts/hook-common.sh +50 -5
- package/plugin/scripts/hook-drift-check.sh +72 -0
- package/plugin/scripts/hook-self-audit.sh +17 -12
- package/plugin/skills/code-auditor/SKILL.md +1 -1
- package/server.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,6 @@ import './native-bootstrap.js';
|
|
|
7
7
|
import { Command } from 'commander';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { createAuditRunner } from './auditRunner.js';
|
|
10
|
-
import { runAuditDispatch } from './auditRouter.js';
|
|
11
10
|
import { promises as fs } from 'fs';
|
|
12
11
|
import { createInterface } from 'readline';
|
|
13
12
|
import { dirname, isAbsolute, join, relative, resolve } from 'path';
|
|
@@ -23,7 +22,6 @@ import { fingerprint, buildFingerprintInput } from './fingerprint.js';
|
|
|
23
22
|
import { buildDismissalEntry, upsertDismissal } from './dismissals.js';
|
|
24
23
|
import { resolveTelemetryConfig, signatureForFinding, buildTelemetryPayload, formatTelemetryPreview, sendTelemetry, languageHint } from './telemetry.js';
|
|
25
24
|
import { getInstallId } from './installConfig.js';
|
|
26
|
-
import { ALL_ANALYZERS } from './analyzers/ruleRegistry.js';
|
|
27
25
|
import { computeGatingDecision } from './enforcement/gate.js';
|
|
28
26
|
import { BLOCKING_SEVERITIES, SEVERITIES } from './types.js';
|
|
29
27
|
import { rankFilesByPriority, orderFindingsWithinFile } from './nextFile.js';
|
|
@@ -36,6 +34,18 @@ const program = new Command();
|
|
|
36
34
|
// `--fail-on advisory` must fail loudly and name the new tier, not emit a generic
|
|
37
35
|
// "must be one of" list that leaves the user guessing what `advisory` became.
|
|
38
36
|
const LEGACY_SEVERITY_NAMES = { advisory: 'high' };
|
|
37
|
+
// CLI option descriptions, hoisted so the 30+ subcommands' repeated
|
|
38
|
+
// option() / -p,--path descriptions share one source.
|
|
39
|
+
const OPTION_OUTPUT_AS_JSON = 'Output as JSON';
|
|
40
|
+
const OPTION_PROJECT_PATH = 'Project path';
|
|
41
|
+
const OPTION_PROJECT_ROOT_PATH = 'Project root path';
|
|
42
|
+
const OPTION_PROJECT_ROOT = 'Project root';
|
|
43
|
+
const OPTION_CONFIGURATION_NAME = 'Configuration name';
|
|
44
|
+
const UNKNOWN_ERROR = 'Unknown error';
|
|
45
|
+
// "file:line" suffix for a finding row; empty when the finding has no line.
|
|
46
|
+
function lineSuffix(line) {
|
|
47
|
+
return line ? `:${line}` : '';
|
|
48
|
+
}
|
|
39
49
|
// Spec 44 R4 — file accounting summary line + `--explain-skipped` breakdown.
|
|
40
50
|
// The full per-reason file lists live in the JSON report (`metadata.fileAccounting`);
|
|
41
51
|
// the CLI preview is bounded to the first ~20 files per reason.
|
|
@@ -88,23 +98,456 @@ program
|
|
|
88
98
|
.name('code-auditor')
|
|
89
99
|
.description('TypeScript/JavaScript code quality auditor with AI tool integration')
|
|
90
100
|
.version(`${PACKAGE_VERSION} (sqlite: ${describeSqliteBackend()})`);
|
|
101
|
+
/**
|
|
102
|
+
* Collapse a receiver-resolution reason to a single line for the cannot-fire
|
|
103
|
+
* panel. Reasons embed the receiver text (which can span the SQL of a chained
|
|
104
|
+
* `db.prepare("...").bind(...)`), so newlines must be folded and the result
|
|
105
|
+
* truncated to stay legible in the per-file rollup.
|
|
106
|
+
*/
|
|
107
|
+
function sanitizeReason(reason) {
|
|
108
|
+
if (!reason)
|
|
109
|
+
return '';
|
|
110
|
+
const collapsed = reason.replace(/\s+/g, ' ').trim();
|
|
111
|
+
return collapsed.length > 220 ? `${collapsed.slice(0, 217)}…` : collapsed;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Print the pre-delta diagnostic panels: coverage, skipped analyzers, ignored
|
|
115
|
+
* config keys, coverage gaps, test coverage, size distributions, and dead
|
|
116
|
+
* clusters. Pure reporting over `result.metadata`.
|
|
117
|
+
*/
|
|
118
|
+
function printAuditDiagnostics(result) {
|
|
119
|
+
// ── Coverage panel leads the report (Spec 47 R2) ─────────────
|
|
120
|
+
const coverage = result.metadata?.coverage;
|
|
121
|
+
// Spec 69 §10 R4 — the site-level cannot-fire count (unproven DB-access
|
|
122
|
+
// receiver sites) sits beside the rule-level states, so one line reports
|
|
123
|
+
// both the per-rule verdict and the per-call-site gap the affected rules
|
|
124
|
+
// may have under-reported on.
|
|
125
|
+
const unprovenSiteCount = (result.metadata?.diagnostics ?? [])
|
|
126
|
+
.filter((d) => d.kind === 'cannot-fire' && d.analyzerName === 'schema')
|
|
127
|
+
.length;
|
|
128
|
+
if (coverage && coverage.length > 0) {
|
|
129
|
+
const covFired = coverage.filter(c => c.state === 'fired').length;
|
|
130
|
+
const covClean = coverage.filter(c => c.state === 'clean').length;
|
|
131
|
+
const covIncomplete = coverage.filter(c => c.state === 'incomplete').length;
|
|
132
|
+
const covNotApplicable = coverage.filter(c => c.state === 'notApplicable').length;
|
|
133
|
+
const covCannotFire = coverage.filter(c => c.state === 'cannot-fire').length;
|
|
134
|
+
console.log(chalk.gray(`── Coverage panel ── ${covFired} fired · ${covClean} clean · ` +
|
|
135
|
+
`${covIncomplete} incomplete · ${covNotApplicable} not-applicable · ` +
|
|
136
|
+
`${covCannotFire} cannot-fire · ${unprovenSiteCount} unproven sites`));
|
|
137
|
+
}
|
|
138
|
+
// ── Skipped analyzers (Spec 61) ──────────────────────────────
|
|
139
|
+
const skippedAnalyzers = (result.metadata?.diagnostics ?? []).filter((d) => d.kind === 'go-toolchain-missing' ||
|
|
140
|
+
d.kind === 'go-analyzer-wrong-arch' ||
|
|
141
|
+
d.kind === 'go-analyzer-build-failed');
|
|
142
|
+
if (skippedAnalyzers.length > 0) {
|
|
143
|
+
console.log(chalk.yellow(`── Skipped analyzers ── ${skippedAnalyzers.length}`));
|
|
144
|
+
for (const d of skippedAnalyzers) {
|
|
145
|
+
const name = d.analyzerName || 'go';
|
|
146
|
+
console.log(chalk.yellow(` ${name} [${d.kind}] — ${d.message}`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// ── Ignored config keys (Spec 61 R1.4) ─────────────────────────
|
|
150
|
+
const ignoredConfigKeys = (result.metadata?.diagnostics ?? []).filter((d) => d.kind === 'config-key-rejected');
|
|
151
|
+
if (ignoredConfigKeys.length > 0) {
|
|
152
|
+
console.log(chalk.yellow(`── Ignored config keys ── ${ignoredConfigKeys.length}`));
|
|
153
|
+
for (const d of ignoredConfigKeys) {
|
|
154
|
+
console.log(chalk.yellow(` ${d.message}`));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ── Cannot-fire sites (Spec 69 §10) ───────────────────────────
|
|
158
|
+
// The cannot-fire verdict surface is the release bar: every DB-access call
|
|
159
|
+
// site whose receiver could not be proven a handle must be *visible with its
|
|
160
|
+
// reason*, never capped or folded into a silent clean. Grouped by file so a
|
|
161
|
+
// reader can see exactly which files the table-reference and data-access
|
|
162
|
+
// rules may have under-reported on.
|
|
163
|
+
const allDiagnostics = result.metadata?.diagnostics ?? [];
|
|
164
|
+
const cannotFireSites = allDiagnostics.filter((d) => d.kind === 'cannot-fire');
|
|
165
|
+
if (cannotFireSites.length > 0) {
|
|
166
|
+
const byFile = new Map();
|
|
167
|
+
for (const d of cannotFireSites) {
|
|
168
|
+
const key = d.file ?? '(unknown)';
|
|
169
|
+
const entry = byFile.get(key) ?? { lines: [], reasons: new Set() };
|
|
170
|
+
if (typeof d.line === 'number')
|
|
171
|
+
entry.lines.push(d.line);
|
|
172
|
+
const rawReason = typeof d.details?.reason === 'string' ? d.details.reason : d.message;
|
|
173
|
+
const reason = sanitizeReason(rawReason);
|
|
174
|
+
if (reason)
|
|
175
|
+
entry.reasons.add(reason);
|
|
176
|
+
byFile.set(key, entry);
|
|
177
|
+
}
|
|
178
|
+
console.log(chalk.yellow(`── Cannot-fire sites ── ${cannotFireSites.length} unproven DB-access call sites across ${byFile.size} files`));
|
|
179
|
+
console.log(chalk.yellow(` Table-reference and data-access rules may report clean on access they could not observe.`));
|
|
180
|
+
for (const [file, entry] of [...byFile.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
181
|
+
const reasons = [...entry.reasons];
|
|
182
|
+
const shown = reasons.slice(0, 2).join(' │ ');
|
|
183
|
+
const more = reasons.length > 2 ? ` (+${reasons.length - 2} more reasons)` : '';
|
|
184
|
+
const loc = entry.lines.length === 1 ? ` (line ${entry.lines[0]})` : '';
|
|
185
|
+
console.log(chalk.yellow(` ${file} — ${entry.lines.length} site(s)${loc}: ${shown}${more}`));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// ── Coverage gaps (Spec 58 follow-up) ─────────────────────────
|
|
189
|
+
const otherCoverageGaps = allDiagnostics.filter((d) => d.kind === 'unresolved-query' || d.kind === 'unresolved-dynamic-import' || d.kind === 'undefined-class-not-found');
|
|
190
|
+
if (otherCoverageGaps.length > 0) {
|
|
191
|
+
const byKind = {};
|
|
192
|
+
for (const d of otherCoverageGaps)
|
|
193
|
+
byKind[d.kind] = (byKind[d.kind] ?? 0) + 1;
|
|
194
|
+
const totals = Object.entries(byKind)
|
|
195
|
+
.map(([kind, n]) => `${n} ${kind}`)
|
|
196
|
+
.join(' · ');
|
|
197
|
+
console.log(chalk.gray(`── Coverage gaps ── ${totals}`));
|
|
198
|
+
for (const d of otherCoverageGaps.slice(0, 20)) {
|
|
199
|
+
const loc = d.file ? `${d.file}${typeof d.line === 'number' ? `:${d.line}` : ''}` : '(unknown)';
|
|
200
|
+
console.log(chalk.gray(` ${loc} [${d.kind}] — ${d.message}`));
|
|
201
|
+
}
|
|
202
|
+
if (otherCoverageGaps.length > 20) {
|
|
203
|
+
console.log(chalk.gray(` … and ${otherCoverageGaps.length - 20} more`));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// ── Spec 60 R1 — test coverage (reporting, not detection) ─────
|
|
207
|
+
const testCoverage = result.metadata?.testCoverage;
|
|
208
|
+
if (testCoverage) {
|
|
209
|
+
const pct = testCoverage.total > 0
|
|
210
|
+
? ((testCoverage.tested / testCoverage.total) * 100).toFixed(1)
|
|
211
|
+
: '0.0';
|
|
212
|
+
let line = `tested ${testCoverage.tested}/${testCoverage.total} (${pct}%)` +
|
|
213
|
+
` · untested-live ${testCoverage.untestedLive}` +
|
|
214
|
+
` · untested-dead ${testCoverage.untestedDead}`;
|
|
215
|
+
if (testCoverage.deadDrop > 0) {
|
|
216
|
+
line += ` (was ${testCoverage.deadPreException} pre-exception; −${testCoverage.deadDrop} entry points exempted)`;
|
|
217
|
+
}
|
|
218
|
+
console.log(chalk.gray(`── Test coverage ── ${line}`));
|
|
219
|
+
}
|
|
220
|
+
// ── Spec 60 R2 — size distributions (median / p95 / max) ──────
|
|
221
|
+
const sizeDistributions = result.metadata?.sizeDistributions;
|
|
222
|
+
if (sizeDistributions && sizeDistributions.length > 0) {
|
|
223
|
+
console.log(chalk.gray(`── Size distributions ──`));
|
|
224
|
+
for (const d of sizeDistributions) {
|
|
225
|
+
const fmt = (n) => Number.isInteger(n) ? String(n) : n.toFixed(1);
|
|
226
|
+
const tail = d.maxEntry
|
|
227
|
+
? ` (${d.maxEntry.name}${d.maxEntry.fileType ? ` · ${d.maxEntry.fileType}` : ''} · ${d.maxEntry.entityType})`
|
|
228
|
+
: '';
|
|
229
|
+
const pop = d.population ? `${d.population}, n=${d.count}` : `n=${d.count}`;
|
|
230
|
+
console.log(chalk.gray(` ${d.measure} [${pop}]: median ${fmt(d.median)} · p95 ${fmt(d.p95)} · max ${fmt(d.max)}${tail}`));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// ── Spec 60 R3 — dead-and-duplicated clusters ─────────────────
|
|
234
|
+
const deadClusters = result.metadata?.deadClusters;
|
|
235
|
+
if (deadClusters && deadClusters.length > 0) {
|
|
236
|
+
console.log(chalk.gray(`── Dead-and-duplicated ── ${deadClusters.length} clusters`));
|
|
237
|
+
for (const c of deadClusters.slice(0, 10)) {
|
|
238
|
+
console.log(chalk.gray(` ${c.count} × ${c.basename}`));
|
|
239
|
+
}
|
|
240
|
+
if (deadClusters.length > 10) {
|
|
241
|
+
console.log(chalk.gray(` … and ${deadClusters.length - 10} more clusters`));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Print the findings summary: the delta view when a baseline exists, or the
|
|
247
|
+
* absolute summary otherwise. Recomputes the dismissed/gating suffixes so the
|
|
248
|
+
* summary is self-contained.
|
|
249
|
+
*/
|
|
250
|
+
function printFindingSummary(result, violations, baseline, options) {
|
|
251
|
+
// Spec 57 — dismissed count is reported alongside the total, never
|
|
252
|
+
// subtracted from it ("43 findings, 3 dismissed").
|
|
253
|
+
const dismissedCount = result.summary.dismissed ?? 0;
|
|
254
|
+
const dismissedSuffix = dismissedCount > 0 ? `, ${dismissedCount} dismissed` : '';
|
|
255
|
+
const gateExcludedCount = violations.filter((v) => v.gateExcluded).length;
|
|
256
|
+
const gatingCount = (severity) => violations.filter((v) => v.severity === severity && !v.gateExcluded && !v.dismissed).length;
|
|
257
|
+
const findingsSuffix = `${dismissedSuffix}${gateExcludedCount > 0 ? ` (${gateExcludedCount.toLocaleString()} excluded from gate)` : ''}`;
|
|
258
|
+
const summaryLine = (label, total, severity) => gateExcludedCount > 0
|
|
259
|
+
? `${label}: ${total} (${gatingCount(severity)} gating)`
|
|
260
|
+
: `${label}: ${total}`;
|
|
261
|
+
// ── Delta output (Spec 18 R2) ─────────────────────────────────
|
|
262
|
+
if (baseline && !options.full) {
|
|
263
|
+
const newViolations = violations.filter((v) => v.new === true);
|
|
264
|
+
const knownCount = baseline.knownCount ?? 0;
|
|
265
|
+
const fixedCount = baseline.fixedCount ?? 0;
|
|
266
|
+
const previousKnown = baseline.previousKnownCount ?? 0;
|
|
267
|
+
const currentDebt = newViolations.length + knownCount;
|
|
268
|
+
const debtDelta = currentDebt - previousKnown;
|
|
269
|
+
const trendIcon = debtDelta > 0 ? '↑' : debtDelta < 0 ? '↓' : '→';
|
|
270
|
+
const trendLabel = debtDelta > 0
|
|
271
|
+
? `(debt increased since last baseline)`
|
|
272
|
+
: debtDelta < 0
|
|
273
|
+
? `(debt decreased since last baseline)`
|
|
274
|
+
: '(unchanged)';
|
|
275
|
+
console.log(`\n📊 Delta: +${newViolations.length} new · −${fixedCount} fixed · ${knownCount} known ${trendIcon} ${trendLabel}`);
|
|
276
|
+
if (newViolations.length > 0) {
|
|
277
|
+
console.log(chalk.gray(`\n── New readings (${newViolations.length}) ──────────────────────────`));
|
|
278
|
+
for (const v of newViolations) {
|
|
279
|
+
const icon = v.severity === 'critical' ? '🔴' :
|
|
280
|
+
v.severity === 'severe' ? '🟠' : '🟡';
|
|
281
|
+
console.log(`${icon} ${chalk.bold(v.file)}${lineSuffix(v.line)} [${v.severity}] ${v.message}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
console.log(chalk.green('\n✓ No new readings since last baseline.'));
|
|
286
|
+
if (knownCount > 0) {
|
|
287
|
+
console.log(chalk.gray(` ${knownCount} known reading(s) are still open — recorded, not resolved.`));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Debt by analyzer
|
|
291
|
+
console.log(chalk.gray(`\n── Debt by Analyzer ──────────────────────────`));
|
|
292
|
+
const analyzerCounts = {};
|
|
293
|
+
for (const v of violations) {
|
|
294
|
+
const a = v.analyzer || 'unknown';
|
|
295
|
+
if (!analyzerCounts[a])
|
|
296
|
+
analyzerCounts[a] = { known: 0, new: 0 };
|
|
297
|
+
if (v.new === false)
|
|
298
|
+
analyzerCounts[a].known++;
|
|
299
|
+
else if (v.new === true)
|
|
300
|
+
analyzerCounts[a].new++;
|
|
301
|
+
}
|
|
302
|
+
for (const [analyzer, counts] of Object.entries(analyzerCounts).sort()) {
|
|
303
|
+
const newPart = counts.new > 0 ? ` (+${counts.new})` : '';
|
|
304
|
+
console.log(`${analyzer}: ${counts.known.toLocaleString()} known${newPart}`);
|
|
305
|
+
}
|
|
306
|
+
// Top files
|
|
307
|
+
console.log(chalk.gray(`\n── Top Files ─────────────────────────────────`));
|
|
308
|
+
const fileCounts = new Map();
|
|
309
|
+
for (const v of violations) {
|
|
310
|
+
const f = v.file || '';
|
|
311
|
+
fileCounts.set(f, (fileCounts.get(f) || 0) + 1);
|
|
312
|
+
}
|
|
313
|
+
const topFiles = [...fileCounts.entries()]
|
|
314
|
+
.sort((a, b) => b[1] - a[1])
|
|
315
|
+
.slice(0, 5);
|
|
316
|
+
for (const [file, count] of topFiles) {
|
|
317
|
+
console.log(`${file} — ${count} reading${count !== 1 ? 's' : ''}`);
|
|
318
|
+
}
|
|
319
|
+
console.log(chalk.gray(`\n💡 Run ${chalk.cyan('code-audit --full')} to see all ${currentDebt.toLocaleString()} readings.`));
|
|
320
|
+
}
|
|
321
|
+
else if (!baseline) {
|
|
322
|
+
// No baseline: current behavior + hint
|
|
323
|
+
console.log(`\nFound ${result.summary.totalViolations} findings${findingsSuffix}`);
|
|
324
|
+
console.log(summaryLine('Critical', result.summary.criticalIssues, 'critical'));
|
|
325
|
+
console.log(summaryLine('Severe', result.summary.severe, 'severe'));
|
|
326
|
+
console.log(summaryLine('High', result.summary.high, 'high'));
|
|
327
|
+
console.log(chalk.gray(`\nEvery reading is a defect — severity is urgency, the order to act.`));
|
|
328
|
+
console.log(chalk.gray(`\n💡 Run ${chalk.cyan('code-audit baseline')} to adopt the ratchet and track changes over time.`));
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
// --full with baseline: full itemized inventory (current behavior)
|
|
332
|
+
console.log(`\nFound ${result.summary.totalViolations} findings${findingsSuffix}`);
|
|
333
|
+
console.log(summaryLine('Critical', result.summary.criticalIssues, 'critical'));
|
|
334
|
+
console.log(summaryLine('Severe', result.summary.severe, 'severe'));
|
|
335
|
+
console.log(summaryLine('High', result.summary.high, 'high'));
|
|
336
|
+
console.log(chalk.gray(`\nEvery reading is a defect — severity is urgency, the order to act.`));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Print unparsed-file failures and skipped-extension notice.
|
|
341
|
+
*/
|
|
342
|
+
function printUnparsedAndSkipped(result) {
|
|
343
|
+
// Spec 32 — unparsed files are never silent.
|
|
344
|
+
const unparsedFiles = result.metadata?.unparsedFiles ?? [];
|
|
345
|
+
if (unparsedFiles.length > 0) {
|
|
346
|
+
console.error(chalk.red(`\n⚠️ ${unparsedFiles.length} file${unparsedFiles.length !== 1 ? 's' : ''} failed to parse:`));
|
|
347
|
+
for (const u of unparsedFiles.slice(0, 20)) {
|
|
348
|
+
console.error(` ${u.filePath} — ${u.reason}`);
|
|
349
|
+
}
|
|
350
|
+
if (unparsedFiles.length > 20) {
|
|
351
|
+
console.error(` … and ${unparsedFiles.length - 20} more`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Spec 43 R5 follow-up — extensions present on disk that discovery skipped.
|
|
355
|
+
const skippedExtensions = result.metadata?.skippedExtensions ?? [];
|
|
356
|
+
if (skippedExtensions.length > 0) {
|
|
357
|
+
console.log(chalk.gray(`\n── Not analyzed (skipped extensions) ────────`));
|
|
358
|
+
const parts = skippedExtensions.map(s => `${s.ext} (${s.count})`);
|
|
359
|
+
console.log(` ${parts.join(', ')}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Print per-analyzer pipeline activity and the detailed coverage breakdown.
|
|
364
|
+
*/
|
|
365
|
+
function printPipelineStages(result, options) {
|
|
366
|
+
if (options.json)
|
|
367
|
+
return;
|
|
368
|
+
const analyzerFiles = [];
|
|
369
|
+
for (const [name, ar] of Object.entries(result.analyzerResults)) {
|
|
370
|
+
const vCount = ar.violations.length;
|
|
371
|
+
if (isVisitorStatus(ar.status)) {
|
|
372
|
+
analyzerFiles.push(`${name} [visitor-ran]: ${getFilesProcessed(ar.status)} files, ${vCount} violations`);
|
|
373
|
+
}
|
|
374
|
+
else if (isReducerStatus(ar.status)) {
|
|
375
|
+
analyzerFiles.push(`${name} [reducer-ran]: ${getFactsConsumed(ar.status)} facts, ${vCount} violations`);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
analyzerFiles.push(`${name} [notRun]: ${vCount} violations`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (analyzerFiles.length > 0) {
|
|
382
|
+
console.log(chalk.gray(`\n── Pipeline Stages ──────────────────────────`));
|
|
383
|
+
console.log(analyzerFiles.join('\n'));
|
|
384
|
+
}
|
|
385
|
+
// ── Coverage (Spec 27) ──────────────────────────────────────────
|
|
386
|
+
const coverage = result.metadata?.coverage;
|
|
387
|
+
if (coverage && coverage.length > 0) {
|
|
388
|
+
const fired = coverage.filter(c => c.state === 'fired');
|
|
389
|
+
const clean = coverage.filter(c => c.state === 'clean');
|
|
390
|
+
const incomplete = coverage.filter(c => c.state === 'incomplete');
|
|
391
|
+
const notApplicable = coverage.filter(c => c.state === 'notApplicable');
|
|
392
|
+
const cannotFire = coverage.filter(c => c.state === 'cannot-fire');
|
|
393
|
+
const firedCount = fired.reduce((s, c) => s + c.count, 0);
|
|
394
|
+
console.log(chalk.gray(`\n── Coverage ─────────────────────────────────`));
|
|
395
|
+
console.log(` ${fired.length} fired (${firedCount.toLocaleString()} violations), ` +
|
|
396
|
+
`${clean.length} clean, ` +
|
|
397
|
+
`${incomplete.length} incomplete, ` +
|
|
398
|
+
`${notApplicable.length} notApplicable, ` +
|
|
399
|
+
`${cannotFire.length} cannot-fire ` +
|
|
400
|
+
`(${coverage.length} rules registered)`);
|
|
401
|
+
if (cannotFire.length > 0) {
|
|
402
|
+
console.log(chalk.yellow(` ── Cannot Fire (broken in the tool) ──`));
|
|
403
|
+
for (const c of cannotFire) {
|
|
404
|
+
console.log(` ${c.ruleId}: ${c.reason ?? 'unknown'}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (notApplicable.length > 0) {
|
|
408
|
+
console.log(chalk.gray(` ── Not Applicable ──`));
|
|
409
|
+
for (const c of notApplicable) {
|
|
410
|
+
console.log(` ${c.ruleId}: ${c.reason ?? 'unknown'}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (clean.length > 0) {
|
|
414
|
+
console.log(chalk.gray(` ── Clean ──`));
|
|
415
|
+
for (const c of clean) {
|
|
416
|
+
console.log(` ${c.ruleId} (0)`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (incomplete.length > 0) {
|
|
420
|
+
console.log(chalk.gray(` ── Incomplete (missing facts for some files) ──`));
|
|
421
|
+
for (const c of incomplete) {
|
|
422
|
+
console.log(` ${c.ruleId} (0)`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Generate and write the formatted report when --format is specified.
|
|
429
|
+
*/
|
|
430
|
+
async function writeReport(result, options) {
|
|
431
|
+
if (!options.format)
|
|
432
|
+
return;
|
|
433
|
+
const validFormats = ['html', 'json', 'csv', 'sarif'];
|
|
434
|
+
if (!validFormats.includes(options.format)) {
|
|
435
|
+
console.error(chalk.red(`Unknown format: "${options.format}". Must be one of: ${validFormats.join(', ')}`));
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
const { generateReport } = await import('./reporting/reportGenerator.js');
|
|
439
|
+
const { readVersionControlProvenance } = await import('./reporting/sarifReportGenerator.js');
|
|
440
|
+
const rootDir = resolve(options.path || process.cwd());
|
|
441
|
+
const config = options.format === 'sarif'
|
|
442
|
+
? { rootDir, ...readVersionControlProvenance(rootDir) }
|
|
443
|
+
: { rootDir };
|
|
444
|
+
const report = generateReport(result, options.format, config);
|
|
445
|
+
const ext = options.format === 'sarif' ? 'sarif' : options.format;
|
|
446
|
+
if (options.output) {
|
|
447
|
+
const outputDir = resolve(options.output);
|
|
448
|
+
const reportPath = join(outputDir, `audit-report.${ext}`);
|
|
449
|
+
let exists = false;
|
|
450
|
+
try {
|
|
451
|
+
await fs.access(reportPath);
|
|
452
|
+
exists = true;
|
|
453
|
+
}
|
|
454
|
+
catch { /* ok */ }
|
|
455
|
+
if (exists && !options.overwrite) {
|
|
456
|
+
console.error(chalk.red(`Refusing to overwrite existing report ${reportPath} — pass --overwrite to replace it.`));
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
await fs.mkdir(dirname(reportPath), { recursive: true });
|
|
460
|
+
await fs.writeFile(reportPath, report, 'utf-8');
|
|
461
|
+
console.log(chalk.green(`\nReport written to ${reportPath}`));
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
// No output path — default to stdout.
|
|
465
|
+
process.stdout.write(report + '\n');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Apply the exit-code gates: fail-on-regression, zero-files, parse-failure,
|
|
470
|
+
* and --fail-on severity thresholds.
|
|
471
|
+
*/
|
|
472
|
+
function applyFailOnGates(result, violations, baseline, options, failOnSeverity) {
|
|
473
|
+
// --fail-on-regression: compare total high debt to baseline snapshot
|
|
474
|
+
if (baseline && options.failOnRegression) {
|
|
475
|
+
const currentDebt = violations.filter((v) => v.new || v.new === false).length;
|
|
476
|
+
const snapshotDebt = baseline.previousKnownCount ?? 0;
|
|
477
|
+
if (currentDebt > snapshotDebt) {
|
|
478
|
+
console.error(chalk.red(`Debt regression: ${currentDebt - snapshotDebt} readings added without re-baselining.`));
|
|
479
|
+
process.exit(2);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Pipeline-failure gate — the audit pipeline itself threw (schema drift,
|
|
483
|
+
// DB write failure, …). One error, reported once by the runner; this exits
|
|
484
|
+
// non-zero (broken tool) so a swallowed pipeline throw can never read as a
|
|
485
|
+
// clean pass.
|
|
486
|
+
{
|
|
487
|
+
const diagnostics = result.metadata?.diagnostics ?? [];
|
|
488
|
+
const pipelineErrors = diagnostics.filter((d) => d.kind === 'pipeline-error');
|
|
489
|
+
if (pipelineErrors.length > 0) {
|
|
490
|
+
console.error(`Pipeline failure: ${pipelineErrors[0].message}`);
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// Zero-files gate — any enabled analyzer matching zero source files is a
|
|
495
|
+
// dark-analyzer failure; fail the run so the bug can't hide.
|
|
496
|
+
{
|
|
497
|
+
const diagnostics = result.metadata?.diagnostics ?? [];
|
|
498
|
+
const zeroFileWarnings = diagnostics.filter((d) => d.kind === 'zero-files');
|
|
499
|
+
if (zeroFileWarnings.length > 0) {
|
|
500
|
+
const names = zeroFileWarnings.map((d) => d.analyzerName ?? d.analyzer).join(', ');
|
|
501
|
+
console.error(`Zero-files failure: ${zeroFileWarnings.length} analyzer(s) matched zero source files (${names})`);
|
|
502
|
+
process.exit(2);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// Spec 32 — a run where any file failed to parse is incomplete and must
|
|
506
|
+
// not exit 0.
|
|
507
|
+
{
|
|
508
|
+
const unparsedFiles = result.metadata?.unparsedFiles ?? [];
|
|
509
|
+
if (unparsedFiles.length > 0) {
|
|
510
|
+
console.error(`Parse-failure: ${unparsedFiles.length} file(s) could not be parsed; analysis is incomplete.`);
|
|
511
|
+
process.exit(2);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// --fail-on: evaluate new + invariant findings only (unless --include-baseline)
|
|
515
|
+
if (failOnSeverity) {
|
|
516
|
+
const evaluableViolations = (baseline && !options.includeBaseline)
|
|
517
|
+
? violations.filter((v) => v.new || v.analyzer === 'invariants')
|
|
518
|
+
: violations;
|
|
519
|
+
const severityOrder = SEVERITIES;
|
|
520
|
+
const failIndex = severityOrder.indexOf(failOnSeverity);
|
|
521
|
+
const hasAtOrAbove = evaluableViolations.some((v) => {
|
|
522
|
+
// Spec 57 — a dismissed finding never blocks the gate.
|
|
523
|
+
if (v.dismissed)
|
|
524
|
+
return false;
|
|
525
|
+
// Spec 36 R4 — a finding from a path-profile-excluded file never blocks.
|
|
526
|
+
if (v.gateExcluded)
|
|
527
|
+
return false;
|
|
528
|
+
const vIndex = severityOrder.indexOf(v.severity);
|
|
529
|
+
return vIndex >= 0 && vIndex <= failIndex;
|
|
530
|
+
});
|
|
531
|
+
if (hasAtOrAbove) {
|
|
532
|
+
process.exit(2);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
91
536
|
// Legacy audit command (default behavior)
|
|
92
537
|
program
|
|
93
538
|
.command('audit', { isDefault: true })
|
|
94
539
|
.description('Run code quality audit (default command)')
|
|
95
540
|
.option('-p, --path <path>', 'Path to audit', process.cwd())
|
|
96
|
-
.option('-c, --config <config>',
|
|
541
|
+
.option('-c, --config <config>', OPTION_CONFIGURATION_NAME)
|
|
97
542
|
.option('-o, --output <dir>', 'Output directory for reports')
|
|
98
543
|
.option('-f, --format <format>', 'Report format: html, json, csv, or sarif')
|
|
544
|
+
.option('--overwrite', 'Overwrite an existing report file at --output (default: refuse)')
|
|
99
545
|
.option('--fail-on <severity>', 'Exit code 2 when violations at or above this severity exist')
|
|
100
546
|
.option('--full', 'Show full violation inventory (overrides default delta view when baseline exists)')
|
|
101
547
|
.option('--include-baseline', 'Evaluate baseline-known violations in --fail-on checks')
|
|
102
548
|
.option('--fail-on-regression', 'Exit code 2 when total high debt exceeds the baseline snapshot')
|
|
103
549
|
.option('--preset <id>', 'Apply a shareable preset (repeatable)', (v, prev) => prev.concat([v]), [])
|
|
104
550
|
.option('--detach', 'Run the audit in a detached background process and print the job ID')
|
|
105
|
-
.option('--partition-strategy <strategy>', 'Partition strategy for detached runs: none, auto, or top-level')
|
|
106
|
-
.option('--max-partitions <n>', 'Maximum number of partition shards (detached runs)')
|
|
107
|
-
.option('--shard-timeout-ms <ms>', 'Per-shard timeout in milliseconds (detached runs)')
|
|
108
551
|
.option('--explain-skipped', 'Print a per-reason breakdown of files dropped from analysis')
|
|
109
552
|
.action(async (options) => {
|
|
110
553
|
console.log(chalk.blue('🔍 Code Quality Audit Tool'));
|
|
@@ -137,302 +580,20 @@ program
|
|
|
137
580
|
}
|
|
138
581
|
}
|
|
139
582
|
}
|
|
140
|
-
const result = await
|
|
583
|
+
const result = await createAuditRunner({
|
|
141
584
|
projectRoot: options.path,
|
|
142
585
|
configName: options.config,
|
|
143
586
|
outputDirectory: options.output,
|
|
144
587
|
presets: presetIds
|
|
145
|
-
});
|
|
588
|
+
}).run();
|
|
146
589
|
const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
|
|
147
590
|
const baseline = result.metadata?.baseline;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const dismissedCount = result.summary.dismissed ?? 0;
|
|
151
|
-
const dismissedSuffix = dismissedCount > 0 ? `, ${dismissedCount} dismissed` : '';
|
|
152
|
-
// Spec 36 R4 — a path-profile-excluded finding reports at its real
|
|
153
|
-
// severity but never gates. When any finding is gate-excluded, surface
|
|
154
|
-
// the gating count next to the total so a clean exit doesn't read as
|
|
155
|
-
// "0 findings" beside a non-zero critical count (e.g. knex: 38 critical,
|
|
156
|
-
// all test-dir, exit 0). The excluded total still reads in full — the
|
|
157
|
-
// distinction is gating vs. reporting, never subtraction.
|
|
158
|
-
const gateExcludedCount = violations.filter((v) => v.gateExcluded).length;
|
|
159
|
-
const gatingCount = (severity) => violations.filter((v) => v.severity === severity && !v.gateExcluded && !v.dismissed).length;
|
|
160
|
-
const findingsSuffix = `${dismissedSuffix}${gateExcludedCount > 0 ? ` (${gateExcludedCount.toLocaleString()} excluded from gate)` : ''}`;
|
|
161
|
-
const summaryLine = (label, total, severity) => gateExcludedCount > 0
|
|
162
|
-
? `${label}: ${total} (${gatingCount(severity)} gating)`
|
|
163
|
-
: `${label}: ${total}`;
|
|
164
|
-
// ── Coverage panel leads the report (Spec 47 R2) ─────────────
|
|
165
|
-
// A diagnostic report opens with what was measured before it lists any
|
|
166
|
-
// readings, so a zero-reading report can't be mistaken for a clean tree.
|
|
167
|
-
const coverage = result.metadata?.coverage;
|
|
168
|
-
if (coverage && coverage.length > 0) {
|
|
169
|
-
const covFired = coverage.filter(c => c.state === 'fired').length;
|
|
170
|
-
const covClean = coverage.filter(c => c.state === 'clean').length;
|
|
171
|
-
const covUnassessed = coverage.filter(c => c.state === 'unassessed').length;
|
|
172
|
-
const covNotApplicable = coverage.filter(c => c.state === 'notApplicable').length;
|
|
173
|
-
const covCannotFire = coverage.filter(c => c.state === 'cannot-fire').length;
|
|
174
|
-
console.log(chalk.gray(`── Coverage panel ── ${covFired} fired · ${covClean} clean · ` +
|
|
175
|
-
`${covUnassessed} unassessed · ${covNotApplicable} not-applicable · ` +
|
|
176
|
-
`${covCannotFire} cannot-fire`));
|
|
177
|
-
}
|
|
178
|
-
// ── Skipped analyzers (Spec 61) ──────────────────────────────
|
|
179
|
-
// A language present on disk but never analyzed is a stated gap, not a
|
|
180
|
-
// silent zero. The Go path surfaces a named runtime failure (missing
|
|
181
|
-
// toolchain, wrong-arch prebuilt binary, failed rebuild) as a
|
|
182
|
-
// metadata.diagnostics entry; before this section those kinds were
|
|
183
|
-
// invisible outside --json, so a polyglot repo with .go files and no Go
|
|
184
|
-
// toolchain printed zero Go findings and exited clean — the silent-zero
|
|
185
|
-
// failure mode. These lead above coverage gaps: a skipped analyzer is a
|
|
186
|
-
// whole language unmeasured, more severe than a per-file visibility gap.
|
|
187
|
-
const skippedAnalyzers = (result.metadata?.diagnostics ?? []).filter((d) => d.kind === 'go-toolchain-missing' ||
|
|
188
|
-
d.kind === 'go-analyzer-wrong-arch' ||
|
|
189
|
-
d.kind === 'go-analyzer-build-failed');
|
|
190
|
-
if (skippedAnalyzers.length > 0) {
|
|
191
|
-
console.log(chalk.yellow(`── Skipped analyzers ── ${skippedAnalyzers.length}`));
|
|
192
|
-
for (const d of skippedAnalyzers) {
|
|
193
|
-
const name = d.analyzerName || 'go';
|
|
194
|
-
console.log(chalk.yellow(` ${name} [${d.kind}] — ${d.message}`));
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// ── Ignored config keys (Spec 61 R1.4) ─────────────────────────
|
|
198
|
-
// Keys dropped by sanitizeProjectFileConfig: undeclared keys (like
|
|
199
|
-
// `scope`, which previously reached execSync via mergeConfig's raw-key
|
|
200
|
-
// iteration) and path keys resolving outside the project root. Surfaced
|
|
201
|
-
// so a silently-ignored config never reads as "loaded fine".
|
|
202
|
-
const ignoredConfigKeys = (result.metadata?.diagnostics ?? []).filter((d) => d.kind === 'config-key-rejected');
|
|
203
|
-
if (ignoredConfigKeys.length > 0) {
|
|
204
|
-
console.log(chalk.yellow(`── Ignored config keys ── ${ignoredConfigKeys.length}`));
|
|
205
|
-
for (const d of ignoredConfigKeys) {
|
|
206
|
-
console.log(chalk.yellow(` ${d.message}`));
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
// ── Coverage gaps (Spec 58 follow-up) ─────────────────────────
|
|
210
|
-
// Coverage diagnostics are the analyzer saying "my visibility ends here",
|
|
211
|
-
// not "the code is wrong". They lead with counts, then per-occurrence
|
|
212
|
-
// file:line — visible and counted, never blocking (they never reach the
|
|
213
|
-
// gate). Rendered alongside the coverage panel that leads the report.
|
|
214
|
-
const coverageDiagnostics = (result.metadata?.diagnostics ?? []).filter((d) => d.kind === 'unresolved-query' || d.kind === 'unresolved-dynamic-import' || d.kind === 'undefined-class-not-found' || d.kind === 'cannot-fire');
|
|
215
|
-
if (coverageDiagnostics.length > 0) {
|
|
216
|
-
const byKind = {};
|
|
217
|
-
for (const d of coverageDiagnostics)
|
|
218
|
-
byKind[d.kind] = (byKind[d.kind] ?? 0) + 1;
|
|
219
|
-
const totals = Object.entries(byKind)
|
|
220
|
-
.map(([kind, n]) => `${n} ${kind}`)
|
|
221
|
-
.join(' · ');
|
|
222
|
-
console.log(chalk.gray(`── Coverage gaps ── ${totals}`));
|
|
223
|
-
for (const d of coverageDiagnostics.slice(0, 20)) {
|
|
224
|
-
const loc = d.file ? `${d.file}${typeof d.line === 'number' ? `:${d.line}` : ''}` : '(unknown)';
|
|
225
|
-
console.log(chalk.gray(` ${loc} [${d.kind}] — ${d.message}`));
|
|
226
|
-
}
|
|
227
|
-
if (coverageDiagnostics.length > 20) {
|
|
228
|
-
console.log(chalk.gray(` … and ${coverageDiagnostics.length - 20} more`));
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// ── Spec 60 R1 — test coverage (reporting, not detection) ─────
|
|
232
|
-
// Classifies every non-test module as tested / untested-live /
|
|
233
|
-
// untested-dead off the file-level import edges. The dead count is
|
|
234
|
-
// post-entry-point-exception, with the drop attributed.
|
|
235
|
-
const testCoverage = result.metadata?.testCoverage;
|
|
236
|
-
if (testCoverage) {
|
|
237
|
-
const pct = testCoverage.total > 0
|
|
238
|
-
? ((testCoverage.tested / testCoverage.total) * 100).toFixed(1)
|
|
239
|
-
: '0.0';
|
|
240
|
-
let line = `tested ${testCoverage.tested}/${testCoverage.total} (${pct}%)` +
|
|
241
|
-
` · untested-live ${testCoverage.untestedLive}` +
|
|
242
|
-
` · untested-dead ${testCoverage.untestedDead}`;
|
|
243
|
-
if (testCoverage.deadDrop > 0) {
|
|
244
|
-
line += ` (was ${testCoverage.deadPreException} pre-exception; −${testCoverage.deadDrop} entry points exempted)`;
|
|
245
|
-
}
|
|
246
|
-
console.log(chalk.gray(`── Test coverage ── ${line}`));
|
|
247
|
-
}
|
|
248
|
-
// ── Spec 60 R2 — size distributions (median / p95 / max) ──────
|
|
249
|
-
const sizeDistributions = result.metadata?.sizeDistributions;
|
|
250
|
-
if (sizeDistributions && sizeDistributions.length > 0) {
|
|
251
|
-
console.log(chalk.gray(`── Size distributions ──`));
|
|
252
|
-
for (const d of sizeDistributions) {
|
|
253
|
-
const fmt = (n) => Number.isInteger(n) ? String(n) : n.toFixed(1);
|
|
254
|
-
const tail = d.maxEntry
|
|
255
|
-
? ` (${d.maxEntry.name}${d.maxEntry.fileType ? ` · ${d.maxEntry.fileType}` : ''} · ${d.maxEntry.entityType})`
|
|
256
|
-
: '';
|
|
257
|
-
const pop = d.population ? `${d.population}, n=${d.count}` : `n=${d.count}`;
|
|
258
|
-
console.log(chalk.gray(` ${d.measure} [${pop}]: median ${fmt(d.median)} · p95 ${fmt(d.p95)} · max ${fmt(d.max)}${tail}`));
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
// ── Spec 60 R3 — dead-and-duplicated clusters ─────────────────
|
|
262
|
-
const deadClusters = result.metadata?.deadClusters;
|
|
263
|
-
if (deadClusters && deadClusters.length > 0) {
|
|
264
|
-
console.log(chalk.gray(`── Dead-and-duplicated ── ${deadClusters.length} clusters`));
|
|
265
|
-
for (const c of deadClusters.slice(0, 10)) {
|
|
266
|
-
console.log(chalk.gray(` ${c.count} × ${c.basename}`));
|
|
267
|
-
}
|
|
268
|
-
if (deadClusters.length > 10) {
|
|
269
|
-
console.log(chalk.gray(` … and ${deadClusters.length - 10} more clusters`));
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
// ── Delta output (Spec 18 R2) ─────────────────────────────────
|
|
273
|
-
if (baseline && !options.full) {
|
|
274
|
-
const newViolations = violations.filter((v) => v.new === true);
|
|
275
|
-
const knownCount = baseline.knownCount ?? 0;
|
|
276
|
-
const fixedCount = baseline.fixedCount ?? 0;
|
|
277
|
-
const previousKnown = baseline.previousKnownCount ?? 0;
|
|
278
|
-
const currentDebt = newViolations.length + knownCount;
|
|
279
|
-
const debtDelta = currentDebt - previousKnown;
|
|
280
|
-
const trendIcon = debtDelta > 0 ? '↑' : debtDelta < 0 ? '↓' : '→';
|
|
281
|
-
const trendLabel = debtDelta > 0
|
|
282
|
-
? `(debt increased since last baseline)`
|
|
283
|
-
: debtDelta < 0
|
|
284
|
-
? `(debt decreased since last baseline)`
|
|
285
|
-
: '(unchanged)';
|
|
286
|
-
console.log(`\n📊 Delta: +${newViolations.length} new · −${fixedCount} fixed · ${knownCount} known ${trendIcon} ${trendLabel}`);
|
|
287
|
-
if (newViolations.length > 0) {
|
|
288
|
-
console.log(chalk.gray(`\n── New readings (${newViolations.length}) ──────────────────────────`));
|
|
289
|
-
for (const v of newViolations) {
|
|
290
|
-
const icon = v.severity === 'critical' ? '🔴' :
|
|
291
|
-
v.severity === 'severe' ? '🟠' : '🟡';
|
|
292
|
-
console.log(`${icon} ${chalk.bold(v.file)}${v.line ? `:${v.line}` : ''} [${v.severity}] ${v.message}`);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
console.log(chalk.green('\n✓ No new readings since last baseline.'));
|
|
297
|
-
if (knownCount > 0) {
|
|
298
|
-
console.log(chalk.gray(` ${knownCount} known reading(s) are still open — recorded, not resolved.`));
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
// Debt by analyzer
|
|
302
|
-
console.log(chalk.gray(`\n── Debt by Analyzer ──────────────────────────`));
|
|
303
|
-
const analyzerCounts = {};
|
|
304
|
-
for (const v of violations) {
|
|
305
|
-
const a = v.analyzer || 'unknown';
|
|
306
|
-
if (!analyzerCounts[a])
|
|
307
|
-
analyzerCounts[a] = { known: 0, new: 0 };
|
|
308
|
-
if (v.new === false)
|
|
309
|
-
analyzerCounts[a].known++;
|
|
310
|
-
else if (v.new === true)
|
|
311
|
-
analyzerCounts[a].new++;
|
|
312
|
-
}
|
|
313
|
-
for (const [analyzer, counts] of Object.entries(analyzerCounts).sort()) {
|
|
314
|
-
const newPart = counts.new > 0 ? ` (+${counts.new})` : '';
|
|
315
|
-
console.log(`${analyzer}: ${counts.known.toLocaleString()} known${newPart}`);
|
|
316
|
-
}
|
|
317
|
-
// Top files
|
|
318
|
-
console.log(chalk.gray(`\n── Top Files ─────────────────────────────────`));
|
|
319
|
-
const fileCounts = new Map();
|
|
320
|
-
for (const v of violations) {
|
|
321
|
-
const f = v.file || '';
|
|
322
|
-
fileCounts.set(f, (fileCounts.get(f) || 0) + 1);
|
|
323
|
-
}
|
|
324
|
-
const topFiles = [...fileCounts.entries()]
|
|
325
|
-
.sort((a, b) => b[1] - a[1])
|
|
326
|
-
.slice(0, 5);
|
|
327
|
-
for (const [file, count] of topFiles) {
|
|
328
|
-
console.log(`${file} — ${count} reading${count !== 1 ? 's' : ''}`);
|
|
329
|
-
}
|
|
330
|
-
console.log(chalk.gray(`\n💡 Run ${chalk.cyan('code-audit --full')} to see all ${currentDebt.toLocaleString()} readings.`));
|
|
331
|
-
}
|
|
332
|
-
else if (!baseline) {
|
|
333
|
-
// No baseline: current behavior + hint
|
|
334
|
-
console.log(`\nFound ${result.summary.totalViolations} findings${findingsSuffix}`);
|
|
335
|
-
console.log(summaryLine('Critical', result.summary.criticalIssues, 'critical'));
|
|
336
|
-
console.log(summaryLine('Severe', result.summary.severe, 'severe'));
|
|
337
|
-
console.log(summaryLine('High', result.summary.high, 'high'));
|
|
338
|
-
console.log(chalk.gray(`\nEvery reading is a defect — severity is urgency, the order to act.`));
|
|
339
|
-
console.log(chalk.gray(`\n💡 Run ${chalk.cyan('code-audit baseline')} to adopt the ratchet and track changes over time.`));
|
|
340
|
-
}
|
|
341
|
-
else {
|
|
342
|
-
// --full with baseline: full itemized inventory (current behavior)
|
|
343
|
-
console.log(`\nFound ${result.summary.totalViolations} findings${findingsSuffix}`);
|
|
344
|
-
console.log(summaryLine('Critical', result.summary.criticalIssues, 'critical'));
|
|
345
|
-
console.log(summaryLine('Severe', result.summary.severe, 'severe'));
|
|
346
|
-
console.log(summaryLine('High', result.summary.high, 'high'));
|
|
347
|
-
console.log(chalk.gray(`\nEvery reading is a defect — severity is urgency, the order to act.`));
|
|
348
|
-
}
|
|
591
|
+
printAuditDiagnostics(result);
|
|
592
|
+
printFindingSummary(result, violations, baseline, options);
|
|
349
593
|
// Spec 44 R4 — file accounting: analyzed/dropped totals + optional breakdown.
|
|
350
594
|
printFileAccounting(result, !!options.explainSkipped);
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const unparsedFiles = result.metadata?.unparsedFiles ?? [];
|
|
354
|
-
if (unparsedFiles.length > 0) {
|
|
355
|
-
console.error(chalk.red(`\n⚠️ ${unparsedFiles.length} file${unparsedFiles.length !== 1 ? 's' : ''} failed to parse:`));
|
|
356
|
-
for (const u of unparsedFiles.slice(0, 20)) {
|
|
357
|
-
console.error(` ${u.filePath} — ${u.reason}`);
|
|
358
|
-
}
|
|
359
|
-
if (unparsedFiles.length > 20) {
|
|
360
|
-
console.error(` … and ${unparsedFiles.length - 20} more`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
// Spec 43 R5 follow-up — extensions present on disk that discovery skipped.
|
|
364
|
-
// Informational: surfaces "what isn't being analyzed here" so an unlisted
|
|
365
|
-
// extension (e.g. `.mdx`) never vanishes silently at discovery. Not an
|
|
366
|
-
// error — a skipped extension is not necessarily a defect (`.md`/`.svg`
|
|
367
|
-
// are legitimately not source), unlike `unparsedFiles` above.
|
|
368
|
-
const skippedExtensions = result.metadata?.skippedExtensions ?? [];
|
|
369
|
-
if (skippedExtensions.length > 0) {
|
|
370
|
-
console.log(chalk.gray(`\n── Not analyzed (skipped extensions) ────────`));
|
|
371
|
-
const parts = skippedExtensions.map(s => `${s.ext} (${s.count})`);
|
|
372
|
-
console.log(` ${parts.join(', ')}`);
|
|
373
|
-
}
|
|
374
|
-
// Per-analyzer activity (read from result data, not serialized summary)
|
|
375
|
-
// — surfaces zero-scan failures that would otherwise be invisible.
|
|
376
|
-
if (!options.json) {
|
|
377
|
-
const analyzerFiles = [];
|
|
378
|
-
for (const [name, ar] of Object.entries(result.analyzerResults)) {
|
|
379
|
-
const vCount = ar.violations.length;
|
|
380
|
-
if (isVisitorStatus(ar.status)) {
|
|
381
|
-
analyzerFiles.push(`${name} [visitor-ran]: ${getFilesProcessed(ar.status)} files, ${vCount} violations`);
|
|
382
|
-
}
|
|
383
|
-
else if (isReducerStatus(ar.status)) {
|
|
384
|
-
analyzerFiles.push(`${name} [reducer-ran]: ${getFactsConsumed(ar.status)} facts, ${vCount} violations`);
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
analyzerFiles.push(`${name} [notRun]: ${vCount} violations`);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
if (analyzerFiles.length > 0) {
|
|
391
|
-
console.log(chalk.gray(`\n── Pipeline Stages ──────────────────────────`));
|
|
392
|
-
console.log(analyzerFiles.join('\n'));
|
|
393
|
-
}
|
|
394
|
-
// ── Coverage (Spec 27) ──────────────────────────────────────────
|
|
395
|
-
const coverage = result.metadata?.coverage;
|
|
396
|
-
if (coverage && coverage.length > 0) {
|
|
397
|
-
const fired = coverage.filter(c => c.state === 'fired');
|
|
398
|
-
const clean = coverage.filter(c => c.state === 'clean');
|
|
399
|
-
const unassessed = coverage.filter(c => c.state === 'unassessed');
|
|
400
|
-
const notApplicable = coverage.filter(c => c.state === 'notApplicable');
|
|
401
|
-
const cannotFire = coverage.filter(c => c.state === 'cannot-fire');
|
|
402
|
-
const firedCount = fired.reduce((s, c) => s + c.count, 0);
|
|
403
|
-
console.log(chalk.gray(`\n── Coverage ─────────────────────────────────`));
|
|
404
|
-
console.log(` ${fired.length} fired (${firedCount.toLocaleString()} violations), ` +
|
|
405
|
-
`${clean.length} clean, ` +
|
|
406
|
-
`${unassessed.length} unassessed, ` +
|
|
407
|
-
`${notApplicable.length} notApplicable, ` +
|
|
408
|
-
`${cannotFire.length} cannot-fire ` +
|
|
409
|
-
`(${coverage.length} rules registered)`);
|
|
410
|
-
if (cannotFire.length > 0) {
|
|
411
|
-
console.log(chalk.yellow(` ── Cannot Fire (broken in the tool) ──`));
|
|
412
|
-
for (const c of cannotFire) {
|
|
413
|
-
console.log(` ${c.ruleId}: ${c.reason ?? 'unknown'}`);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
if (notApplicable.length > 0) {
|
|
417
|
-
console.log(chalk.gray(` ── Not Applicable ──`));
|
|
418
|
-
for (const c of notApplicable) {
|
|
419
|
-
console.log(` ${c.ruleId}: ${c.reason ?? 'unknown'}`);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
if (clean.length > 0) {
|
|
423
|
-
console.log(chalk.gray(` ── Clean ──`));
|
|
424
|
-
for (const c of clean) {
|
|
425
|
-
console.log(` ${c.ruleId} (0)`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (unassessed.length > 0) {
|
|
429
|
-
console.log(chalk.gray(` ── Unassessed (zero readings, applicability unknown) ──`));
|
|
430
|
-
for (const c of unassessed) {
|
|
431
|
-
console.log(` ${c.ruleId} (0)`);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
595
|
+
printUnparsedAndSkipped(result);
|
|
596
|
+
printPipelineStages(result, options);
|
|
436
597
|
// Spec-20 R4: built-in profile visibility — silent behavior changes
|
|
437
598
|
// are never acceptable. Notify when scripts-and-tests excludes findings
|
|
438
599
|
// from the blocking gate.
|
|
@@ -442,82 +603,8 @@ program
|
|
|
442
603
|
console.log(chalk.blue(`\nℹ️ ${builtinCapped.toLocaleString()} readings excluded from the blocking gate by built-in profile "scripts-and-tests" (scripts/tests/fixtures).`));
|
|
443
604
|
console.log(chalk.gray(` Set ${chalk.cyan('"builtin": false')} in .codeauditor.json to disable.`));
|
|
444
605
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const validFormats = ['html', 'json', 'csv', 'sarif'];
|
|
448
|
-
if (!validFormats.includes(options.format)) {
|
|
449
|
-
console.error(chalk.red(`Unknown format: "${options.format}". Must be one of: ${validFormats.join(', ')}`));
|
|
450
|
-
process.exit(1);
|
|
451
|
-
}
|
|
452
|
-
const { generateReport } = await import('./reporting/reportGenerator.js');
|
|
453
|
-
const { readVersionControlProvenance } = await import('./reporting/sarifReportGenerator.js');
|
|
454
|
-
const rootDir = resolve(options.path || process.cwd());
|
|
455
|
-
const config = options.format === 'sarif'
|
|
456
|
-
? { rootDir, ...readVersionControlProvenance(rootDir) }
|
|
457
|
-
: { rootDir };
|
|
458
|
-
const report = generateReport(result, options.format, config);
|
|
459
|
-
const outputDir = options.output || process.cwd();
|
|
460
|
-
const ext = options.format === 'sarif' ? 'sarif' : options.format;
|
|
461
|
-
const reportPath = join(outputDir, `audit-report.${ext}`);
|
|
462
|
-
await fs.mkdir(dirname(reportPath), { recursive: true });
|
|
463
|
-
await fs.writeFile(reportPath, report, 'utf-8');
|
|
464
|
-
console.log(chalk.green(`\nReport written to ${reportPath}`));
|
|
465
|
-
}
|
|
466
|
-
// ── Fail-on logic (Spec 18 R3) ───────────────────────────────
|
|
467
|
-
// --fail-on-regression: compare total high debt to baseline snapshot
|
|
468
|
-
if (baseline && options.failOnRegression) {
|
|
469
|
-
const currentDebt = violations.filter((v) => v.new || v.new === false).length;
|
|
470
|
-
const snapshotDebt = baseline.previousKnownCount ?? 0;
|
|
471
|
-
if (currentDebt > snapshotDebt) {
|
|
472
|
-
console.error(chalk.red(`Debt regression: ${currentDebt - snapshotDebt} readings added without re-baselining.`));
|
|
473
|
-
process.exit(2);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
// Zero-files gate — any enabled analyzer matching zero source files is a
|
|
477
|
-
// dark-analyzer failure; fail the run so the bug can't hide.
|
|
478
|
-
{
|
|
479
|
-
const diagnostics = result.metadata?.diagnostics ?? [];
|
|
480
|
-
const zeroFileWarnings = diagnostics.filter((d) => d.kind === 'zero-files');
|
|
481
|
-
if (zeroFileWarnings.length > 0) {
|
|
482
|
-
const names = zeroFileWarnings.map((d) => d.analyzerName ?? d.analyzer).join(', ');
|
|
483
|
-
console.error(`Zero-files failure: ${zeroFileWarnings.length} analyzer(s) matched zero source files (${names})`);
|
|
484
|
-
process.exit(2);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
// Spec 32 — a run where any file failed to parse is incomplete and must
|
|
488
|
-
// not exit 0. This mirrors the zero-files gate: a silent dark-analyzer
|
|
489
|
-
// failure at file granularity is a hard failure.
|
|
490
|
-
{
|
|
491
|
-
const unparsedFiles = result.metadata?.unparsedFiles ?? [];
|
|
492
|
-
if (unparsedFiles.length > 0) {
|
|
493
|
-
console.error(`Parse-failure: ${unparsedFiles.length} file(s) could not be parsed; analysis is incomplete.`);
|
|
494
|
-
process.exit(2);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
// --fail-on: evaluate new + invariant findings only (unless --include-baseline)
|
|
498
|
-
if (failOnSeverity) {
|
|
499
|
-
const evaluableViolations = (baseline && !options.includeBaseline)
|
|
500
|
-
? violations.filter((v) => v.new || v.analyzer === 'invariants')
|
|
501
|
-
: violations;
|
|
502
|
-
const severityOrder = SEVERITIES;
|
|
503
|
-
const failIndex = severityOrder.indexOf(failOnSeverity);
|
|
504
|
-
const hasAtOrAbove = evaluableViolations.some((v) => {
|
|
505
|
-
// Spec 57 — a dismissed finding never blocks the gate.
|
|
506
|
-
if (v.dismissed)
|
|
507
|
-
return false;
|
|
508
|
-
// Spec 36 R4 — a finding from a path-profile-excluded file (e.g. a
|
|
509
|
-
// test/script under "scripts-and-tests") never blocks the gate,
|
|
510
|
-
// whatever its severity. The file is doing its job; a hardcoded
|
|
511
|
-
// connection string in a stress-test script is not a defect to block on.
|
|
512
|
-
if (v.gateExcluded)
|
|
513
|
-
return false;
|
|
514
|
-
const vIndex = severityOrder.indexOf(v.severity);
|
|
515
|
-
return vIndex >= 0 && vIndex <= failIndex;
|
|
516
|
-
});
|
|
517
|
-
if (hasAtOrAbove) {
|
|
518
|
-
process.exit(2);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
606
|
+
await writeReport(result, options);
|
|
607
|
+
applyFailOnGates(result, violations, baseline, options, failOnSeverity);
|
|
521
608
|
}
|
|
522
609
|
catch (error) {
|
|
523
610
|
console.error(chalk.red('Error:'), error);
|
|
@@ -534,7 +621,7 @@ program
|
|
|
534
621
|
.option('--fail-on-zero-files', 'Exit code 2 when any enabled analyzer matches zero source files', true)
|
|
535
622
|
.option('--stdin', 'Read file paths from stdin (one per line)')
|
|
536
623
|
.option('--explain-skipped', 'Print a per-reason breakdown of files dropped from analysis')
|
|
537
|
-
.option('-p, --path <projectPath>',
|
|
624
|
+
.option('-p, --path <projectPath>', OPTION_PROJECT_ROOT_PATH, process.cwd())
|
|
538
625
|
.action(async (paths, options) => {
|
|
539
626
|
try {
|
|
540
627
|
const fileSet = new Set();
|
|
@@ -570,11 +657,17 @@ program
|
|
|
570
657
|
// the gate never misses a finding. SARIF needs full run metadata, so it
|
|
571
658
|
// always runs in-process.
|
|
572
659
|
const projectRoot = resolve(options.path || process.cwd());
|
|
660
|
+
const hookMode = process.env.CODE_AUDITOR_HOOK === '1' || process.env.CODE_AUDITOR_HOOK === 'true';
|
|
573
661
|
let result;
|
|
574
662
|
if (resolvedPaths.length > 0 && options.format !== 'sarif') {
|
|
575
|
-
const { resolveDaemon, readDaemonDiagnostics } = await import('./daemon/resolve.js');
|
|
663
|
+
const { resolveDaemon, readDaemonDiagnostics, startDaemonDetached } = await import('./daemon/resolve.js');
|
|
576
664
|
const daemon = await resolveDaemon(projectRoot);
|
|
577
665
|
if (daemon.mode === 'not-ready') {
|
|
666
|
+
// Hook mode (Spec 68 §hook-contract): never block the edit on an audit the
|
|
667
|
+
// daemon can't serve yet — it is still indexing, and the finding arrives on
|
|
668
|
+
// a later edit. Say nothing, exit clean. A non-hook caller gets the status.
|
|
669
|
+
if (hookMode)
|
|
670
|
+
process.exit(0);
|
|
578
671
|
reportDaemonIndexing(daemon.state, !!options.json);
|
|
579
672
|
return;
|
|
580
673
|
}
|
|
@@ -583,14 +676,27 @@ program
|
|
|
583
676
|
if (diag && diag.status === 'ready' && diag.staleFiles.length === 0) {
|
|
584
677
|
result = await buildChangedResultFromDiagnostics(diag.diagnostics, projectRoot);
|
|
585
678
|
}
|
|
679
|
+
else if (hookMode) {
|
|
680
|
+
// A just-edited file whose re-audit hasn't landed is not trustworthy from
|
|
681
|
+
// cache. Report nothing; the finding arrives on the next edit.
|
|
682
|
+
process.exit(0);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
else if (hookMode) {
|
|
686
|
+
// No daemon: enqueue by starting one detached, then exit. The hook reads
|
|
687
|
+
// results from the daemon's cache on later edits — it never computes them.
|
|
688
|
+
startDaemonDetached(projectRoot);
|
|
689
|
+
process.exit(0);
|
|
586
690
|
}
|
|
587
691
|
}
|
|
588
692
|
if (!result) {
|
|
693
|
+
// Hook mode never falls back to an in-process audit (Spec 68 §hook-contract).
|
|
694
|
+
if (hookMode)
|
|
695
|
+
process.exit(0);
|
|
589
696
|
await initParsers();
|
|
590
697
|
const runner = createAuditRunner({
|
|
591
698
|
projectRoot: options.path,
|
|
592
699
|
scope,
|
|
593
|
-
analyzerConcurrency: 4
|
|
594
700
|
});
|
|
595
701
|
result = await runner.run();
|
|
596
702
|
}
|
|
@@ -599,7 +705,7 @@ program
|
|
|
599
705
|
// Spec 57 — dismissed count is reported alongside the total, never
|
|
600
706
|
// subtracted from it. The hook surface (`changed`) is agent-facing, so it
|
|
601
707
|
// must show the same "N findings, M dismissed" figure as the full report.
|
|
602
|
-
const dismissedCount = result.summary
|
|
708
|
+
const dismissedCount = result.summary?.dismissed ?? 0;
|
|
603
709
|
// Blocking gate decision (Spec 45 R1/R4, Spec 54 R3), computed once so the
|
|
604
710
|
// agent-facing before/after count (Spec 45 A2) and the exit code agree on
|
|
605
711
|
// the same number. Every registered rule participates (R1); enforcement is
|
|
@@ -607,98 +713,21 @@ program
|
|
|
607
713
|
// {critical, severe, high} — every finding is a defect and every finding
|
|
608
714
|
// blocks; there is no configurable gate.
|
|
609
715
|
const { blocking, resolutionGaps } = computeGatingDecision(violations, BLOCKING_SEVERITIES);
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
const { generateSARIFReport, readVersionControlProvenance } = await import('./reporting/sarifReportGenerator.js');
|
|
613
|
-
const projectDir = resolve(options.path || process.cwd());
|
|
614
|
-
const sarifOutput = generateSARIFReport(result, { rootDir: projectDir, ...readVersionControlProvenance(projectDir) });
|
|
615
|
-
process.stdout.write(sarifOutput + '\n');
|
|
616
|
-
}
|
|
617
|
-
else if (options.json) {
|
|
618
|
-
const projectDir = resolve(options.path || process.cwd());
|
|
619
|
-
const jsonViolations = violations.map((v) => {
|
|
620
|
-
// Compute relative path if file resolves inside the project
|
|
621
|
-
let filePath = v.file || '';
|
|
622
|
-
if (filePath.startsWith('/') || filePath.startsWith('\\\\')) {
|
|
623
|
-
const rel = relative(projectDir, filePath);
|
|
624
|
-
// Only use relative path if it doesn't escape the project
|
|
625
|
-
if (!rel.startsWith('..') && !isAbsolute(rel)) {
|
|
626
|
-
filePath = rel;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
// Find column: prefer explicit column, then start.column, then default to 1
|
|
630
|
-
const col = v.column ?? v.start?.column ?? 1;
|
|
631
|
-
return {
|
|
632
|
-
analyzer: v.analyzer || '',
|
|
633
|
-
rule: v.rule,
|
|
634
|
-
severity: v.severity,
|
|
635
|
-
message: v.message,
|
|
636
|
-
file: filePath,
|
|
637
|
-
line: v.line ?? v.start?.line,
|
|
638
|
-
column: col,
|
|
639
|
-
endLine: v.end?.line,
|
|
640
|
-
endColumn: v.end?.column,
|
|
641
|
-
enclosingSymbol: v.symbol || v.enclosingFunction || '',
|
|
642
|
-
suggestion: v.suggestion || '',
|
|
643
|
-
details: v.details || '',
|
|
644
|
-
...(v.new !== undefined && { new: v.new }),
|
|
645
|
-
...(v.dismissed !== undefined && { dismissed: v.dismissed }),
|
|
646
|
-
fingerprint: fingerprint(buildFingerprintInput(v))
|
|
647
|
-
};
|
|
648
|
-
});
|
|
649
|
-
// Coverage diagnostics ride alongside violations so an agent sees "the
|
|
650
|
-
// analyzer couldn't resolve this" without a metadata flag — and without
|
|
651
|
-
// it ever gating (diagnostics never reach computeGatingDecision).
|
|
652
|
-
const jsonDiagnostics = (result.metadata?.diagnostics ?? []).map((d) => ({
|
|
653
|
-
analyzer: d.analyzerName ?? d.analyzer ?? '',
|
|
654
|
-
kind: d.kind,
|
|
655
|
-
message: d.message,
|
|
656
|
-
...(d.file ? { file: d.file } : {}),
|
|
657
|
-
...(typeof d.line === 'number' ? { line: d.line } : {}),
|
|
658
|
-
...(d.details ? { details: d.details } : {}),
|
|
659
|
-
}));
|
|
660
|
-
const jsonOutput = { violations: jsonViolations, diagnostics: jsonDiagnostics };
|
|
661
|
-
process.stdout.write(JSON.stringify(jsonOutput, null, 2) + '\n');
|
|
662
|
-
}
|
|
663
|
-
else if (!options.quiet || violations.length > 0) {
|
|
664
|
-
// Console output — Spec 45 A2: agent-facing output emits counts (per
|
|
665
|
-
// analyzer/rule/severity, plus the before/after gate figure) alongside
|
|
666
|
-
// findings. The hook and the `changed` command are the agent's surface.
|
|
667
|
-
console.log(chalk.blue('🔍 Diff-Scoped Code Audit'));
|
|
668
|
-
console.log(chalk.gray('══════════════════════════════════════════════════'));
|
|
669
|
-
printFileAccounting(result, !!options.explainSkipped);
|
|
670
|
-
if (violations.length > 0) {
|
|
671
|
-
console.log('');
|
|
672
|
-
printCountSummary(violations);
|
|
673
|
-
console.log(chalk.bold(`gate before/after: ${violations.length} → ${blocking.length} blocking`));
|
|
674
|
-
if (dismissedCount > 0) {
|
|
675
|
-
console.log(chalk.dim(` ${dismissedCount} dismissed — still counted above, excluded from the gate`));
|
|
676
|
-
}
|
|
677
|
-
console.log(chalk.gray('── Violations ────────────────────────────────────'));
|
|
678
|
-
for (const v of violations) {
|
|
679
|
-
const icon = v.severity === 'critical' ? '🔴' :
|
|
680
|
-
v.severity === 'severe' ? '🟠' : '🟡';
|
|
681
|
-
const statusTag = v.new === false
|
|
682
|
-
? chalk.dim(' [known — still open]')
|
|
683
|
-
: '';
|
|
684
|
-
const dismissedTag = v.dismissed
|
|
685
|
-
? chalk.dim(' [dismissed]')
|
|
686
|
-
: '';
|
|
687
|
-
console.log(`${icon} ${chalk.bold(v.file)}${v.line ? `:${v.line}` : ''} [${v.severity}] ${v.message}${statusTag}${dismissedTag}`);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
else {
|
|
691
|
-
console.log(chalk.green('\n✓ No readings.'));
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
// Spec 38 R2/R3 — per-rule timing + gate wall-clock, opt-in by env var.
|
|
716
|
+
await emitChangedOutput(result, violations, blocking, dismissedCount, options);
|
|
717
|
+
// Spec 38 R2/R3 — per-rule timing + gate cpu-time, opt-in by env var.
|
|
695
718
|
// Emitted to stderr so it never corrupts --json stdout. Slowest rule first.
|
|
719
|
+
// The gate metric is CPU time (auditCpuMs), not wall clock: the budget is
|
|
720
|
+
// about a slow *rule*, and CPU time isolates that from a slow machine.
|
|
696
721
|
if (process.env.CODE_AUDIT_RULE_TIMING === '1') {
|
|
697
722
|
const ruleTiming = result.metadata?.ruleTiming;
|
|
698
|
-
const gateMs = result.metadata?.
|
|
723
|
+
const gateMs = result.metadata?.auditCpuMs;
|
|
724
|
+
const gateWallMs = result.metadata?.auditDuration;
|
|
699
725
|
const lines = [];
|
|
700
726
|
lines.push('');
|
|
701
|
-
|
|
727
|
+
// §16 criterion 16 — report both wall-clock (what the hook's user feels)
|
|
728
|
+
// and CPU (what the rule actually burned); the budget asserts on CPU.
|
|
729
|
+
lines.push(`⏱ gate wall-clock: ${(gateWallMs ?? 0).toFixed(1)} ms`);
|
|
730
|
+
lines.push(`⏱ gate cpu-time: ${(gateMs ?? 0).toFixed(1)} ms (budget 400 ms)`);
|
|
702
731
|
lines.push('── per-rule timing (slowest first) ──');
|
|
703
732
|
if (ruleTiming && ruleTiming.length > 0) {
|
|
704
733
|
for (const t of ruleTiming) {
|
|
@@ -731,6 +760,17 @@ program
|
|
|
731
760
|
process.exit(2);
|
|
732
761
|
}
|
|
733
762
|
}
|
|
763
|
+
// Pipeline-failure gate — the audit pipeline itself threw. One error,
|
|
764
|
+
// reported once by the runner; exit non-zero (broken tool) so a swallowed
|
|
765
|
+
// pipeline throw can never read as a clean pass.
|
|
766
|
+
{
|
|
767
|
+
const diagnostics = result.metadata?.diagnostics ?? [];
|
|
768
|
+
const pipelineErrors = diagnostics.filter((d) => d.kind === 'pipeline-error');
|
|
769
|
+
if (pipelineErrors.length > 0) {
|
|
770
|
+
console.error(`Pipeline failure: ${pipelineErrors[0].message}`);
|
|
771
|
+
process.exit(1);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
734
774
|
// Exit code based on --no-fail-on-zero-files (default-on in v3.4.8)
|
|
735
775
|
// Any enabled analyzer matching zero source files is a failure — a >50%
|
|
736
776
|
// threshold lets the exact bug (3/9 analyzers dark) pass silently.
|
|
@@ -749,6 +789,96 @@ program
|
|
|
749
789
|
process.exit(1);
|
|
750
790
|
}
|
|
751
791
|
});
|
|
792
|
+
/**
|
|
793
|
+
* Emit the changed-audit output (SARIF, JSON, or console) for the hook surface.
|
|
794
|
+
* The SARIF branch dynamically imports its generator, so this is async.
|
|
795
|
+
*/
|
|
796
|
+
async function emitChangedOutput(result, violations, blocking, dismissedCount, options) {
|
|
797
|
+
// JSON output
|
|
798
|
+
if (options.format === 'sarif') {
|
|
799
|
+
const { generateSARIFReport, readVersionControlProvenance } = await import('./reporting/sarifReportGenerator.js');
|
|
800
|
+
const projectDir = resolve(options.path || process.cwd());
|
|
801
|
+
const sarifOutput = generateSARIFReport(result, { rootDir: projectDir, ...readVersionControlProvenance(projectDir) });
|
|
802
|
+
process.stdout.write(sarifOutput + '\n');
|
|
803
|
+
}
|
|
804
|
+
else if (options.json) {
|
|
805
|
+
const projectDir = resolve(options.path || process.cwd());
|
|
806
|
+
const jsonViolations = violations.map((v) => {
|
|
807
|
+
// Compute relative path if file resolves inside the project
|
|
808
|
+
let filePath = v.file || '';
|
|
809
|
+
if (filePath.startsWith('/') || filePath.startsWith('\\\\')) {
|
|
810
|
+
const rel = relative(projectDir, filePath);
|
|
811
|
+
// Only use relative path if it doesn't escape the project
|
|
812
|
+
if (!rel.startsWith('..') && !isAbsolute(rel)) {
|
|
813
|
+
filePath = rel;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// Find column: prefer explicit column, then start.column, then default to 1
|
|
817
|
+
const col = v.column ?? v.start?.column ?? 1;
|
|
818
|
+
return {
|
|
819
|
+
analyzer: v.analyzer || '',
|
|
820
|
+
rule: v.rule,
|
|
821
|
+
severity: v.severity,
|
|
822
|
+
message: v.message,
|
|
823
|
+
file: filePath,
|
|
824
|
+
line: v.line ?? v.start?.line,
|
|
825
|
+
column: col,
|
|
826
|
+
endLine: v.end?.line,
|
|
827
|
+
endColumn: v.end?.column,
|
|
828
|
+
enclosingSymbol: v.symbol || v.enclosingFunction || '',
|
|
829
|
+
suggestion: v.suggestion || '',
|
|
830
|
+
details: v.details || '',
|
|
831
|
+
...(v.new !== undefined && { new: v.new }),
|
|
832
|
+
...(v.dismissed !== undefined && { dismissed: v.dismissed }),
|
|
833
|
+
fingerprint: fingerprint(buildFingerprintInput(v))
|
|
834
|
+
};
|
|
835
|
+
});
|
|
836
|
+
// Coverage diagnostics ride alongside violations so an agent sees "the
|
|
837
|
+
// analyzer couldn't resolve this" without a metadata flag — and without
|
|
838
|
+
// it ever gating (diagnostics never reach computeGatingDecision).
|
|
839
|
+
const jsonDiagnostics = (result.metadata?.diagnostics ?? []).map((d) => ({
|
|
840
|
+
analyzer: d.analyzerName ?? d.analyzer ?? '',
|
|
841
|
+
kind: d.kind,
|
|
842
|
+
message: d.message,
|
|
843
|
+
...(d.file ? { file: d.file } : {}),
|
|
844
|
+
...(typeof d.line === 'number' ? { line: d.line } : {}),
|
|
845
|
+
...(d.details ? { details: d.details } : {}),
|
|
846
|
+
}));
|
|
847
|
+
const jsonOutput = { violations: jsonViolations, diagnostics: jsonDiagnostics };
|
|
848
|
+
process.stdout.write(JSON.stringify(jsonOutput, null, 2) + '\n');
|
|
849
|
+
}
|
|
850
|
+
else if (!options.quiet || violations.length > 0) {
|
|
851
|
+
// Console output — Spec 45 A2: agent-facing output emits counts (per
|
|
852
|
+
// analyzer/rule/severity, plus the before/after gate figure) alongside
|
|
853
|
+
// findings. The hook and the `changed` command are the agent's surface.
|
|
854
|
+
console.log(chalk.blue('🔍 Diff-Scoped Code Audit'));
|
|
855
|
+
console.log(chalk.gray('══════════════════════════════════════════════════'));
|
|
856
|
+
printFileAccounting(result, !!options.explainSkipped);
|
|
857
|
+
if (violations.length > 0) {
|
|
858
|
+
console.log('');
|
|
859
|
+
printCountSummary(violations);
|
|
860
|
+
console.log(chalk.bold(`gate before/after: ${violations.length} → ${blocking.length} blocking`));
|
|
861
|
+
if (dismissedCount > 0) {
|
|
862
|
+
console.log(chalk.dim(` ${dismissedCount} dismissed — still counted above, excluded from the gate`));
|
|
863
|
+
}
|
|
864
|
+
console.log(chalk.gray('── Violations ────────────────────────────────────'));
|
|
865
|
+
for (const v of violations) {
|
|
866
|
+
const icon = v.severity === 'critical' ? '🔴' :
|
|
867
|
+
v.severity === 'severe' ? '🟠' : '🟡';
|
|
868
|
+
const statusTag = v.new === false
|
|
869
|
+
? chalk.dim(' [known — still open]')
|
|
870
|
+
: '';
|
|
871
|
+
const dismissedTag = v.dismissed
|
|
872
|
+
? chalk.dim(' [dismissed]')
|
|
873
|
+
: '';
|
|
874
|
+
console.log(`${icon} ${chalk.bold(v.file)}${lineSuffix(v.line)} [${v.severity}] ${v.message}${statusTag}${dismissedTag}`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
console.log(chalk.green('\n✓ No readings.'));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
752
882
|
// Spec 45 A2 — counts are emitted where useful (per analyzer, per rule, per
|
|
753
883
|
// severity), on agent-facing surfaces as well as human ones. This reverts Spec
|
|
754
884
|
// 36 R3's "no bare counts": an agent now sees the shape of what fired instead
|
|
@@ -788,11 +918,61 @@ function printCountSummary(violations) {
|
|
|
788
918
|
*/
|
|
789
919
|
async function buildChangedResultFromDiagnostics(violations, projectRoot) {
|
|
790
920
|
const analyzerResults = {};
|
|
921
|
+
const byAnalyzer = {};
|
|
922
|
+
let totalViolations = 0;
|
|
923
|
+
let criticalIssues = 0;
|
|
924
|
+
let severe = 0;
|
|
925
|
+
let high = 0;
|
|
926
|
+
let dismissed = 0;
|
|
927
|
+
const violationsByCategory = {};
|
|
791
928
|
for (const v of violations) {
|
|
792
929
|
const key = v.analyzer || 'unknown';
|
|
793
930
|
(analyzerResults[key] = analyzerResults[key] || { violations: [] }).violations.push(v);
|
|
931
|
+
totalViolations++;
|
|
932
|
+
switch (v.severity) {
|
|
933
|
+
case 'critical':
|
|
934
|
+
criticalIssues++;
|
|
935
|
+
break;
|
|
936
|
+
case 'severe':
|
|
937
|
+
severe++;
|
|
938
|
+
break;
|
|
939
|
+
case 'high':
|
|
940
|
+
high++;
|
|
941
|
+
break;
|
|
942
|
+
}
|
|
943
|
+
const rule = v.rule || 'unknown';
|
|
944
|
+
violationsByCategory[rule] = (violationsByCategory[rule] ?? 0) + 1;
|
|
945
|
+
const bucket = byAnalyzer[key] ?? (byAnalyzer[key] = { violations: 0 });
|
|
946
|
+
bucket.violations++;
|
|
947
|
+
if (v.dismissed)
|
|
948
|
+
dismissed++;
|
|
794
949
|
}
|
|
795
|
-
|
|
950
|
+
const topIssues = Object.entries(violationsByCategory)
|
|
951
|
+
.sort(([, a], [, b]) => b - a)
|
|
952
|
+
.slice(0, 5)
|
|
953
|
+
.map(([type, count]) => ({ type, count }));
|
|
954
|
+
// A *real* summary derived from the diagnostics the daemon served — not a
|
|
955
|
+
// zero-filled placeholder (a stub satisfying the type, with a `dismissed: 0`
|
|
956
|
+
// that was never measured). Every field here is computed from the actual
|
|
957
|
+
// findings: severity/category/byAnalyzer rollups, plus `dismissed` from each
|
|
958
|
+
// diagnostic's own `dismissed` flag. `totalFiles` and the per-analyzer
|
|
959
|
+
// `filesProcessed`/`fatalErrors`/`unprovenSites` are not derivable from a flat
|
|
960
|
+
// per-file diagnostic list, so they are omitted; the guarded read
|
|
961
|
+
// (`result.summary?.dismissed ?? 0`) tolerates their absence.
|
|
962
|
+
return {
|
|
963
|
+
analyzerResults,
|
|
964
|
+
summary: {
|
|
965
|
+
totalViolations,
|
|
966
|
+
criticalIssues,
|
|
967
|
+
severe,
|
|
968
|
+
high,
|
|
969
|
+
violationsByCategory,
|
|
970
|
+
topIssues,
|
|
971
|
+
byAnalyzer,
|
|
972
|
+
dismissed,
|
|
973
|
+
},
|
|
974
|
+
metadata: { configUsed: undefined, diagnostics: [] },
|
|
975
|
+
};
|
|
796
976
|
}
|
|
797
977
|
// Self-audit gate (Spec 33 Item 15 + Spec 44 remediation). Runs the full
|
|
798
978
|
// analyzer pipeline over the tool's own production source and asserts zero
|
|
@@ -828,7 +1008,7 @@ program
|
|
|
828
1008
|
.description('Audit the tool\'s own analyzers/ + languages/ source and fail on blocking readings')
|
|
829
1009
|
.option('--json', 'Output violations as machine-readable JSON to stdout')
|
|
830
1010
|
.option('--stdin', 'Read file paths from stdin (one per line)')
|
|
831
|
-
.option('-p, --path <projectPath>',
|
|
1011
|
+
.option('-p, --path <projectPath>', OPTION_PROJECT_ROOT_PATH, process.cwd())
|
|
832
1012
|
.option('--fail-on <severity>', 'Blocking severity floor: critical, severe, or high', 'high')
|
|
833
1013
|
.action(async (paths, options) => {
|
|
834
1014
|
try {
|
|
@@ -867,7 +1047,6 @@ program
|
|
|
867
1047
|
const runner = createAuditRunner({
|
|
868
1048
|
projectRoot: options.path,
|
|
869
1049
|
scope,
|
|
870
|
-
analyzerConcurrency: 4
|
|
871
1050
|
});
|
|
872
1051
|
const result = await runner.run();
|
|
873
1052
|
const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
|
|
@@ -912,7 +1091,7 @@ program
|
|
|
912
1091
|
for (const v of blocking) {
|
|
913
1092
|
const icon = v.severity === 'critical' ? '🔴' :
|
|
914
1093
|
v.severity === 'severe' ? '🟠' : '🟡';
|
|
915
|
-
console.log(`${icon} ${v.file}${v.line
|
|
1094
|
+
console.log(`${icon} ${v.file}${lineSuffix(v.line)} [${v.severity}] ${v.message}`);
|
|
916
1095
|
}
|
|
917
1096
|
}
|
|
918
1097
|
else {
|
|
@@ -938,8 +1117,8 @@ program
|
|
|
938
1117
|
program
|
|
939
1118
|
.command('next-file')
|
|
940
1119
|
.description('Audit and return the highest-priority file with readings (file-by-file refactor loop)')
|
|
941
|
-
.option('-p, --path <projectPath>',
|
|
942
|
-
.option('-c, --config <config>',
|
|
1120
|
+
.option('-p, --path <projectPath>', OPTION_PROJECT_ROOT_PATH, process.cwd())
|
|
1121
|
+
.option('-c, --config <config>', OPTION_CONFIGURATION_NAME)
|
|
943
1122
|
.option('--json', 'Output a single JSON object (or {done:true}) to stdout')
|
|
944
1123
|
.action(async (options) => {
|
|
945
1124
|
try {
|
|
@@ -1037,7 +1216,7 @@ function printNextFile(violations, summary, projectDir, json) {
|
|
|
1037
1216
|
console.log(chalk.gray('\n── Readings ────────────────────────────────────────'));
|
|
1038
1217
|
for (const v of ordered) {
|
|
1039
1218
|
const icon = v.severity === 'critical' ? '🔴' : v.severity === 'severe' ? '🟠' : '🟡';
|
|
1040
|
-
console.log(`${icon} ${chalk.bold(relativize(v.file || ''))}${v.line
|
|
1219
|
+
console.log(`${icon} ${chalk.bold(relativize(v.file || ''))}${lineSuffix(v.line)} [${v.severity}] ${v.rule} — ${v.message}`);
|
|
1041
1220
|
}
|
|
1042
1221
|
}
|
|
1043
1222
|
}
|
|
@@ -1045,8 +1224,8 @@ function printNextFile(violations, summary, projectDir, json) {
|
|
|
1045
1224
|
program
|
|
1046
1225
|
.command('baseline')
|
|
1047
1226
|
.description('Snapshot current advisory readings as the baseline (excludes invariants)')
|
|
1048
|
-
.option('-p, --path <path>',
|
|
1049
|
-
.option('--json',
|
|
1227
|
+
.option('-p, --path <path>', OPTION_PROJECT_PATH, process.cwd())
|
|
1228
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1050
1229
|
.action(async (options) => {
|
|
1051
1230
|
try {
|
|
1052
1231
|
await initParsers();
|
|
@@ -1112,9 +1291,9 @@ program
|
|
|
1112
1291
|
.command('dismiss <fingerprint>')
|
|
1113
1292
|
.description('Dismiss one finding by fingerprint — a written reason is required')
|
|
1114
1293
|
.requiredOption('--reason <reason>', 'Why this finding is dismissed (required — a dismissal without a reason is a config error)')
|
|
1115
|
-
.option('-p, --path <path>',
|
|
1294
|
+
.option('-p, --path <path>', OPTION_PROJECT_PATH, process.cwd())
|
|
1116
1295
|
.option('--telemetry-endpoint <url>', 'Feedback service endpoint override (defaults to the opt-in endpoint recorded by the telemetry tool)')
|
|
1117
|
-
.option('--json',
|
|
1296
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1118
1297
|
.action(async (fingerprintArg, options) => {
|
|
1119
1298
|
try {
|
|
1120
1299
|
const reason = (options.reason ?? '').trim();
|
|
@@ -1198,8 +1377,9 @@ program
|
|
|
1198
1377
|
.description('Generate a .codeauditor.json scaffold with invariant rules')
|
|
1199
1378
|
.option('-o, --output <dir>', 'Output directory', '.')
|
|
1200
1379
|
.option('-i, --interactive', 'Interactive rule builder')
|
|
1201
|
-
.option('
|
|
1202
|
-
.option('-
|
|
1380
|
+
.option('--overwrite', 'Overwrite an existing .codeauditor.json without confirmation')
|
|
1381
|
+
.option('-f, --force', 'Alias for --overwrite')
|
|
1382
|
+
.option('-y, --yes', 'Alias for --overwrite')
|
|
1203
1383
|
.action(async (options) => {
|
|
1204
1384
|
console.log(chalk.blue('🛠️ Code Auditor Config Generator'));
|
|
1205
1385
|
console.log(chalk.gray('════════════════════════════════════════════════════'));
|
|
@@ -1225,7 +1405,7 @@ indexCmd
|
|
|
1225
1405
|
.command('sync')
|
|
1226
1406
|
.description('Synchronize index from source files')
|
|
1227
1407
|
.option('--path <path>', 'Path to a specific file or directory', process.cwd())
|
|
1228
|
-
.option('--json',
|
|
1408
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1229
1409
|
.action(async (options) => {
|
|
1230
1410
|
try {
|
|
1231
1411
|
await initParsers();
|
|
@@ -1266,64 +1446,65 @@ indexCmd
|
|
|
1266
1446
|
process.exit(1);
|
|
1267
1447
|
}
|
|
1268
1448
|
});
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1449
|
+
/** Register a simple `index` subcommand that runs one mutation on the
|
|
1450
|
+
* initialized index and prints the outcome (JSON or console). `cleanup` and
|
|
1451
|
+
* `reset` share the get-instance/initialize/json-vs-console/try-catch
|
|
1452
|
+
* scaffolding; only the mutation and its result prose differ, so they live
|
|
1453
|
+
* here once and each command is a thin `defineIndexMutationCommand(...)` call. */
|
|
1454
|
+
function defineIndexMutationCommand(name, description, work) {
|
|
1455
|
+
indexCmd
|
|
1456
|
+
.command(name)
|
|
1457
|
+
.description(description)
|
|
1458
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1459
|
+
.action(async (options) => {
|
|
1460
|
+
try {
|
|
1461
|
+
const db = CodeIndexDB.getInstance();
|
|
1462
|
+
await db.initialize();
|
|
1463
|
+
const { extra, greenLine, grayLine } = await work(db);
|
|
1464
|
+
if (options.json) {
|
|
1465
|
+
process.stdout.write(JSON.stringify({ mode: name, success: true, ...extra }) + '\n');
|
|
1466
|
+
}
|
|
1467
|
+
else {
|
|
1468
|
+
console.log(chalk.green(greenLine));
|
|
1469
|
+
console.log(chalk.gray(grayLine));
|
|
1470
|
+
}
|
|
1280
1471
|
}
|
|
1281
|
-
|
|
1282
|
-
console.
|
|
1283
|
-
|
|
1472
|
+
catch (error) {
|
|
1473
|
+
console.error(chalk.red('Error:'), error);
|
|
1474
|
+
process.exit(1);
|
|
1284
1475
|
}
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
defineIndexMutationCommand('cleanup', 'Remove index entries for deleted files', async (db) => {
|
|
1479
|
+
const result = await db.bulkCleanup();
|
|
1480
|
+
return {
|
|
1481
|
+
extra: result,
|
|
1482
|
+
greenLine: '✓ Cleanup complete',
|
|
1483
|
+
grayLine: `Removed ${result.removedCount} entries from ${result.removedFiles.length} deleted files`,
|
|
1484
|
+
};
|
|
1290
1485
|
});
|
|
1291
|
-
|
|
1292
|
-
.
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
await db.initialize();
|
|
1299
|
-
await db.clearIndex();
|
|
1300
|
-
if (options.json) {
|
|
1301
|
-
process.stdout.write(JSON.stringify({ mode: 'reset', success: true, message: 'All analysis data cleared' }) + '\n');
|
|
1302
|
-
}
|
|
1303
|
-
else {
|
|
1304
|
-
console.log(chalk.green('✓ Index reset complete'));
|
|
1305
|
-
console.log(chalk.gray('All analysis-derived data cleared (tasks, config, and whitelist preserved)'));
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
catch (error) {
|
|
1309
|
-
console.error(chalk.red('Error:'), error);
|
|
1310
|
-
process.exit(1);
|
|
1311
|
-
}
|
|
1486
|
+
defineIndexMutationCommand('reset', 'Clear all analysis data (preserves tasks, config, whitelist)', async (db) => {
|
|
1487
|
+
await db.clearIndex();
|
|
1488
|
+
return {
|
|
1489
|
+
extra: { message: 'All analysis data cleared' },
|
|
1490
|
+
greenLine: '✓ Index reset complete',
|
|
1491
|
+
grayLine: 'All analysis-derived data cleared (tasks, config, and whitelist preserved)',
|
|
1492
|
+
};
|
|
1312
1493
|
});
|
|
1313
1494
|
// ── Index status command (Spec 14 R1) ──────────────────────────
|
|
1314
1495
|
indexCmd
|
|
1315
1496
|
.command('status')
|
|
1316
1497
|
.description('Show index health and graph statistics')
|
|
1317
|
-
.option('--json',
|
|
1498
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1318
1499
|
.action(async (options) => {
|
|
1319
1500
|
try {
|
|
1320
1501
|
const db = CodeIndexDB.getInstance();
|
|
1321
1502
|
await db.initialize();
|
|
1322
|
-
const stats = await db.getStats();
|
|
1503
|
+
const stats = await db.functionIndex.getStats();
|
|
1323
1504
|
// Graph stats — non-fatal if graph cache is empty
|
|
1324
1505
|
let graphStats = null;
|
|
1325
1506
|
try {
|
|
1326
|
-
graphStats = db.getGraphStats();
|
|
1507
|
+
graphStats = db.graph.getGraphStats();
|
|
1327
1508
|
}
|
|
1328
1509
|
catch {
|
|
1329
1510
|
// No graph data yet — ok
|
|
@@ -1394,7 +1575,7 @@ configCmd
|
|
|
1394
1575
|
.command('rules-list')
|
|
1395
1576
|
.description('List all configured invariant rules from .codeauditor.json')
|
|
1396
1577
|
.option('--config-path <path>', 'Path to .codeauditor.json')
|
|
1397
|
-
.option('--json',
|
|
1578
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1398
1579
|
.action(async (options) => {
|
|
1399
1580
|
try {
|
|
1400
1581
|
const pathModule = await import('path');
|
|
@@ -1404,7 +1585,9 @@ configCmd
|
|
|
1404
1585
|
try {
|
|
1405
1586
|
const raw = readFileSync(configPath, 'utf-8');
|
|
1406
1587
|
const config = JSON.parse(raw);
|
|
1407
|
-
|
|
1588
|
+
// `rules` is an array by schema; tolerate the `{}` shorthand some fixtures
|
|
1589
|
+
// and hand-edited configs use for "no rules" — `.map` on `{}` would throw.
|
|
1590
|
+
const rules = Array.isArray(config?.rules) ? config.rules : [];
|
|
1408
1591
|
if (options.json) {
|
|
1409
1592
|
process.stdout.write(JSON.stringify({
|
|
1410
1593
|
rules: rules.map((r) => ({ id: r.id, kind: r.kind, severity: r.severity, message: r.message || null })),
|
|
@@ -1442,7 +1625,7 @@ configCmd
|
|
|
1442
1625
|
.command('rules-check')
|
|
1443
1626
|
.description('Validate the current .codeauditor.json rules')
|
|
1444
1627
|
.option('--config-path <path>', 'Path to .codeauditor.json')
|
|
1445
|
-
.option('--json',
|
|
1628
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1446
1629
|
.action(async (options) => {
|
|
1447
1630
|
try {
|
|
1448
1631
|
const pathModule = await import('path');
|
|
@@ -1490,9 +1673,9 @@ configCmd
|
|
|
1490
1673
|
configCmd
|
|
1491
1674
|
.command('profiles')
|
|
1492
1675
|
.description('Show active path profiles and resolve per-file matching')
|
|
1493
|
-
.option('-p, --path <path>',
|
|
1676
|
+
.option('-p, --path <path>', OPTION_PROJECT_PATH, process.cwd())
|
|
1494
1677
|
.option('--file <file>', 'Resolve profiles for a specific file')
|
|
1495
|
-
.option('--json',
|
|
1678
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1496
1679
|
.action(async (options) => {
|
|
1497
1680
|
try {
|
|
1498
1681
|
const fs = await import('fs/promises');
|
|
@@ -1590,8 +1773,8 @@ configCmd
|
|
|
1590
1773
|
configCmd
|
|
1591
1774
|
.command('detection')
|
|
1592
1775
|
.description('Show DB-provenanced and inferred receiver identifiers with per-entry evidence')
|
|
1593
|
-
.option('-p, --path <path>',
|
|
1594
|
-
.option('--json',
|
|
1776
|
+
.option('-p, --path <path>', OPTION_PROJECT_PATH, process.cwd())
|
|
1777
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1595
1778
|
.option('--mode <mode>', 'Detection mode filter (hybrid, provenance, names)')
|
|
1596
1779
|
.action(async (options) => {
|
|
1597
1780
|
try {
|
|
@@ -1643,9 +1826,7 @@ configCmd
|
|
|
1643
1826
|
// Build provenance context using schema analyzer config (most complete)
|
|
1644
1827
|
const ctx = buildProvenanceContext(ast, adapter, sourceCode, {
|
|
1645
1828
|
mode: detectionMode,
|
|
1646
|
-
dbReceiverNames: schemaCfg.dbReceiverNames ?? daCfg.dbReceiverNames,
|
|
1647
1829
|
dbBindingNames: schemaCfg.dbBindingNames ?? daCfg.dbBindingNames,
|
|
1648
|
-
dbCallMethods: schemaCfg.dbCallMethods ?? daCfg.dbCallMethods,
|
|
1649
1830
|
});
|
|
1650
1831
|
// Collect DB-provenanced
|
|
1651
1832
|
for (const [id, evidence] of ctx.dbProvenanced) {
|
|
@@ -1698,70 +1879,84 @@ configCmd
|
|
|
1698
1879
|
entry.validatorProvenanced.push(v);
|
|
1699
1880
|
}
|
|
1700
1881
|
for (const [file, data] of byFile) {
|
|
1701
|
-
db.storeFileProvenance(file, data);
|
|
1882
|
+
db.provenance.storeFileProvenance(file, data);
|
|
1702
1883
|
}
|
|
1703
1884
|
// Store inferred receivers
|
|
1704
|
-
db.storeInferredReceivers(inferred);
|
|
1885
|
+
db.inferredReceivers.storeInferredReceivers(inferred);
|
|
1705
1886
|
}
|
|
1706
1887
|
catch {
|
|
1707
1888
|
// Index not available — skip persistence
|
|
1708
1889
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
}
|
|
1719
|
-
else {
|
|
1720
|
-
console.log(chalk.bold(`Detection mode: ${chalk.cyan(detectionMode)}`));
|
|
1721
|
-
console.log(chalk.gray(`Scanned ${files.slice(0, 500).length} files`));
|
|
1722
|
-
console.log();
|
|
1723
|
-
if (provenanced.length > 0) {
|
|
1724
|
-
console.log(chalk.bold('DB-Provenanced Identifiers:'));
|
|
1725
|
-
for (const p of provenanced) {
|
|
1726
|
-
const reasonColor = p.reason === 'fallback'
|
|
1727
|
-
? chalk.yellow
|
|
1728
|
-
: p.reason === 'package'
|
|
1729
|
-
? chalk.green
|
|
1730
|
-
: chalk.cyan;
|
|
1731
|
-
console.log(` ${chalk.white(p.identifier)} ${chalk.gray(`(${p.file})`)}`);
|
|
1732
|
-
console.log(` ${reasonColor(`reason: ${p.reason}`)} — ${chalk.gray(p.source)}`);
|
|
1733
|
-
if (p.chain) {
|
|
1734
|
-
console.log(` ${chalk.gray(`chain: ${p.chain.join(' → ')}`)}`);
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
console.log();
|
|
1738
|
-
}
|
|
1739
|
-
if (inferred.length > 0) {
|
|
1740
|
-
console.log(chalk.bold('Inferred Receivers:'));
|
|
1741
|
-
for (const i of inferred) {
|
|
1742
|
-
console.log(` ${chalk.white(i.identifier)} ${chalk.gray(`(${i.file})`)}`);
|
|
1743
|
-
console.log(` ${chalk.magenta(i.reason)}`);
|
|
1744
|
-
}
|
|
1745
|
-
console.log();
|
|
1746
|
-
}
|
|
1747
|
-
if (validatorIdentifiers.length > 0) {
|
|
1748
|
-
console.log(chalk.bold('Validator-Provenanced Identifiers:'));
|
|
1749
|
-
for (const v of validatorIdentifiers) {
|
|
1750
|
-
console.log(` ${chalk.white(v.identifier)} ${chalk.gray(`(${v.file})`)}`);
|
|
1751
|
-
console.log(` ${chalk.green(`reason: ${v.reason}`)} — ${chalk.gray(v.source)}`);
|
|
1752
|
-
}
|
|
1753
|
-
console.log();
|
|
1754
|
-
}
|
|
1755
|
-
if (provenanced.length === 0 && inferred.length === 0 && validatorIdentifiers.length === 0) {
|
|
1756
|
-
console.log(chalk.gray('No DB-provenanced, inferred, or validator-provenanced identifiers found.'));
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1890
|
+
printDetectionResults({
|
|
1891
|
+
json: options.json,
|
|
1892
|
+
mode: detectionMode,
|
|
1893
|
+
projectRoot,
|
|
1894
|
+
filesScanned: files.slice(0, 500).length,
|
|
1895
|
+
dbProvenanced: provenanced,
|
|
1896
|
+
validatorProvenanced: validatorIdentifiers,
|
|
1897
|
+
inferred,
|
|
1898
|
+
});
|
|
1759
1899
|
}
|
|
1760
1900
|
catch (error) {
|
|
1761
1901
|
console.error(chalk.red('Error:'), error);
|
|
1762
1902
|
process.exit(1);
|
|
1763
1903
|
}
|
|
1764
1904
|
});
|
|
1905
|
+
// Renders the `config detection` results — pulled out of the `.action` handler so
|
|
1906
|
+
// the scan/persist logic stays short and the two output shapes (JSON vs human) are
|
|
1907
|
+
// isolated from the provenance collection.
|
|
1908
|
+
function printDetectionResults(params) {
|
|
1909
|
+
const { json, mode, projectRoot, filesScanned, dbProvenanced, validatorProvenanced, inferred } = params;
|
|
1910
|
+
if (json) {
|
|
1911
|
+
process.stdout.write(JSON.stringify({
|
|
1912
|
+
mode,
|
|
1913
|
+
projectRoot,
|
|
1914
|
+
filesScanned,
|
|
1915
|
+
dbProvenanced,
|
|
1916
|
+
validatorProvenanced,
|
|
1917
|
+
inferred,
|
|
1918
|
+
}, null, 2) + '\n');
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
console.log(chalk.bold(`Detection mode: ${chalk.cyan(mode)}`));
|
|
1922
|
+
console.log(chalk.gray(`Scanned ${filesScanned} files`));
|
|
1923
|
+
console.log();
|
|
1924
|
+
if (dbProvenanced.length > 0) {
|
|
1925
|
+
console.log(chalk.bold('DB-Provenanced Identifiers:'));
|
|
1926
|
+
for (const p of dbProvenanced) {
|
|
1927
|
+
const reasonColor = p.reason === 'fallback'
|
|
1928
|
+
? chalk.yellow
|
|
1929
|
+
: p.reason === 'package'
|
|
1930
|
+
? chalk.green
|
|
1931
|
+
: chalk.cyan;
|
|
1932
|
+
console.log(` ${chalk.white(p.identifier)} ${chalk.gray(`(${p.file})`)}`);
|
|
1933
|
+
console.log(` ${reasonColor(`reason: ${p.reason}`)} — ${chalk.gray(p.source)}`);
|
|
1934
|
+
if (p.chain) {
|
|
1935
|
+
console.log(` ${chalk.gray(`chain: ${p.chain.join(' → ')}`)}`);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
console.log();
|
|
1939
|
+
}
|
|
1940
|
+
if (inferred.length > 0) {
|
|
1941
|
+
console.log(chalk.bold('Inferred Receivers:'));
|
|
1942
|
+
for (const i of inferred) {
|
|
1943
|
+
console.log(` ${chalk.white(i.identifier)} ${chalk.gray(`(${i.file})`)}`);
|
|
1944
|
+
console.log(` ${chalk.magenta(i.reason)}`);
|
|
1945
|
+
}
|
|
1946
|
+
console.log();
|
|
1947
|
+
}
|
|
1948
|
+
if (validatorProvenanced.length > 0) {
|
|
1949
|
+
console.log(chalk.bold('Validator-Provenanced Identifiers:'));
|
|
1950
|
+
for (const v of validatorProvenanced) {
|
|
1951
|
+
console.log(` ${chalk.white(v.identifier)} ${chalk.gray(`(${v.file})`)}`);
|
|
1952
|
+
console.log(` ${chalk.green(`reason: ${v.reason}`)} — ${chalk.gray(v.source)}`);
|
|
1953
|
+
}
|
|
1954
|
+
console.log();
|
|
1955
|
+
}
|
|
1956
|
+
if (dbProvenanced.length === 0 && inferred.length === 0 && validatorProvenanced.length === 0) {
|
|
1957
|
+
console.log(chalk.gray('No DB-provenanced, inferred, or validator-provenanced identifiers found.'));
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1765
1960
|
// Search command
|
|
1766
1961
|
program
|
|
1767
1962
|
.command('search <query>')
|
|
@@ -1795,6 +1990,7 @@ program
|
|
|
1795
1990
|
.option('--no-unused-imports', 'Hide unused import warnings')
|
|
1796
1991
|
.option('--min-complexity <threshold>', 'Minimum complexity threshold for warnings', '7')
|
|
1797
1992
|
.option('-o, --output <file>', 'Save output to file instead of displaying')
|
|
1993
|
+
.option('--overwrite', 'Overwrite an existing output file (default: refuse)')
|
|
1798
1994
|
.action(async (options) => {
|
|
1799
1995
|
console.log(chalk.blue('🗺️ Codebase Map Generator'));
|
|
1800
1996
|
console.log(chalk.gray('════════════════════════════════════════════════════'));
|
|
@@ -1828,7 +2024,7 @@ tasksCmd
|
|
|
1828
2024
|
.option('--priority <priority>', 'Filter by priority (low, medium, high)')
|
|
1829
2025
|
.option('--label <label>', 'Filter by label')
|
|
1830
2026
|
.option('--limit <limit>', 'Result limit', '50')
|
|
1831
|
-
.option('--json',
|
|
2027
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1832
2028
|
.action(async (options) => {
|
|
1833
2029
|
try {
|
|
1834
2030
|
const args = { action: 'list' };
|
|
@@ -1863,7 +2059,7 @@ tasksCmd
|
|
|
1863
2059
|
}
|
|
1864
2060
|
}
|
|
1865
2061
|
else {
|
|
1866
|
-
console.error(chalk.red(result.error ||
|
|
2062
|
+
console.error(chalk.red(result.error || UNKNOWN_ERROR));
|
|
1867
2063
|
process.exit(1);
|
|
1868
2064
|
}
|
|
1869
2065
|
}
|
|
@@ -1880,7 +2076,7 @@ tasksCmd
|
|
|
1880
2076
|
.option('--priority <priority>', 'Priority (low, medium, high)')
|
|
1881
2077
|
.option('--status <status>', 'Status (open, in_progress)')
|
|
1882
2078
|
.option('--labels <labels>', 'Comma-separated labels')
|
|
1883
|
-
.option('--json',
|
|
2079
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1884
2080
|
.action(async (options) => {
|
|
1885
2081
|
try {
|
|
1886
2082
|
const args = {
|
|
@@ -1905,7 +2101,7 @@ tasksCmd
|
|
|
1905
2101
|
console.log(chalk.green(`✓ Task created: ${task.title || task.taskId || 'unknown'}`));
|
|
1906
2102
|
}
|
|
1907
2103
|
else {
|
|
1908
|
-
console.error(chalk.red(result.error ||
|
|
2104
|
+
console.error(chalk.red(result.error || UNKNOWN_ERROR));
|
|
1909
2105
|
process.exit(1);
|
|
1910
2106
|
}
|
|
1911
2107
|
}
|
|
@@ -1917,7 +2113,7 @@ tasksCmd
|
|
|
1917
2113
|
tasksCmd
|
|
1918
2114
|
.command('get <taskId>')
|
|
1919
2115
|
.description('Get a task by ID')
|
|
1920
|
-
.option('--json',
|
|
2116
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1921
2117
|
.action(async (taskId, options) => {
|
|
1922
2118
|
try {
|
|
1923
2119
|
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
@@ -1943,7 +2139,7 @@ tasksCmd
|
|
|
1943
2139
|
console.log(chalk.dim(`\nfingerprint: ${t.fingerprint}`));
|
|
1944
2140
|
}
|
|
1945
2141
|
else {
|
|
1946
|
-
console.error(chalk.red(result.error ||
|
|
2142
|
+
console.error(chalk.red(result.error || UNKNOWN_ERROR));
|
|
1947
2143
|
process.exit(1);
|
|
1948
2144
|
}
|
|
1949
2145
|
}
|
|
@@ -1960,7 +2156,7 @@ tasksCmd
|
|
|
1960
2156
|
.option('--status <status>', 'New status (open, in_progress, completed)')
|
|
1961
2157
|
.option('--priority <priority>', 'New priority (low, medium, high)')
|
|
1962
2158
|
.option('--labels <labels>', 'Comma-separated labels')
|
|
1963
|
-
.option('--json',
|
|
2159
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
1964
2160
|
.action(async (taskId, options) => {
|
|
1965
2161
|
try {
|
|
1966
2162
|
const patch = {};
|
|
@@ -1987,7 +2183,7 @@ tasksCmd
|
|
|
1987
2183
|
console.log(chalk.green(`✓ Task updated`));
|
|
1988
2184
|
}
|
|
1989
2185
|
else {
|
|
1990
|
-
console.error(chalk.red(result.error ||
|
|
2186
|
+
console.error(chalk.red(result.error || UNKNOWN_ERROR));
|
|
1991
2187
|
process.exit(1);
|
|
1992
2188
|
}
|
|
1993
2189
|
}
|
|
@@ -1996,60 +2192,45 @@ tasksCmd
|
|
|
1996
2192
|
process.exit(1);
|
|
1997
2193
|
}
|
|
1998
2194
|
});
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2195
|
+
/** Run a single-taskId tasks subcommand and print its result. */
|
|
2196
|
+
function makeTaskIdAction(action, successLabel) {
|
|
2197
|
+
return async (taskId, options) => {
|
|
2198
|
+
try {
|
|
2199
|
+
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
2200
|
+
const result = await handleProjectTasks({ action, taskId });
|
|
2201
|
+
if (options.json) {
|
|
2202
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
2203
|
+
}
|
|
2204
|
+
else if (result.success) {
|
|
2205
|
+
console.log(chalk.green(`✓ ${successLabel}`));
|
|
2206
|
+
}
|
|
2207
|
+
else {
|
|
2208
|
+
console.error(chalk.red(result.error || UNKNOWN_ERROR));
|
|
2209
|
+
process.exit(1);
|
|
2210
|
+
}
|
|
2012
2211
|
}
|
|
2013
|
-
|
|
2014
|
-
console.error(chalk.red(
|
|
2212
|
+
catch (error) {
|
|
2213
|
+
console.error(chalk.red('Error:'), error);
|
|
2015
2214
|
process.exit(1);
|
|
2016
2215
|
}
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
tasksCmd
|
|
2219
|
+
.command('complete <taskId>')
|
|
2220
|
+
.description('Mark a task as completed')
|
|
2221
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2222
|
+
.action(makeTaskIdAction('complete_task', 'Task completed'));
|
|
2023
2223
|
tasksCmd
|
|
2024
2224
|
.command('delete <taskId>')
|
|
2025
2225
|
.description('Delete a task')
|
|
2026
|
-
.option('--json',
|
|
2027
|
-
.action(
|
|
2028
|
-
try {
|
|
2029
|
-
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
2030
|
-
const result = await handleProjectTasks({ action: 'delete', taskId });
|
|
2031
|
-
if (options.json) {
|
|
2032
|
-
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
2033
|
-
}
|
|
2034
|
-
else if (result.success) {
|
|
2035
|
-
console.log(chalk.green(`✓ Task deleted`));
|
|
2036
|
-
}
|
|
2037
|
-
else {
|
|
2038
|
-
console.error(chalk.red(result.error || 'Unknown error'));
|
|
2039
|
-
process.exit(1);
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
catch (error) {
|
|
2043
|
-
console.error(chalk.red('Error:'), error);
|
|
2044
|
-
process.exit(1);
|
|
2045
|
-
}
|
|
2046
|
-
});
|
|
2226
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2227
|
+
.action(makeTaskIdAction('delete', 'Task deleted'));
|
|
2047
2228
|
tasksCmd
|
|
2048
2229
|
.command('from-audit')
|
|
2049
2230
|
.description('Populate tasks from audit violations')
|
|
2050
2231
|
.option('--auditJobId <id>', 'Specific audit result ID')
|
|
2051
2232
|
.option('--severities <severities>', 'Comma-separated severities (critical,severe,high)', 'critical,severe,high')
|
|
2052
|
-
.option('--json',
|
|
2233
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2053
2234
|
.action(async (options) => {
|
|
2054
2235
|
try {
|
|
2055
2236
|
const args = { action: 'from_audit' };
|
|
@@ -2076,7 +2257,7 @@ tasksCmd
|
|
|
2076
2257
|
console.log(chalk.yellow(result.error));
|
|
2077
2258
|
}
|
|
2078
2259
|
else {
|
|
2079
|
-
console.error(chalk.red(result.error ||
|
|
2260
|
+
console.error(chalk.red(result.error || UNKNOWN_ERROR));
|
|
2080
2261
|
process.exit(1);
|
|
2081
2262
|
}
|
|
2082
2263
|
}
|
|
@@ -2092,7 +2273,7 @@ program
|
|
|
2092
2273
|
.description('Rank files and functions by hotspot score (churn × complexity)')
|
|
2093
2274
|
.option('--limit <n>', 'Max entries to show')
|
|
2094
2275
|
.option('--path <dir>', 'Target directory (triggers spot churn extraction if index is empty)')
|
|
2095
|
-
.option('--json',
|
|
2276
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2096
2277
|
.action(async (options) => {
|
|
2097
2278
|
try {
|
|
2098
2279
|
const db = CodeIndexDB.getInstance();
|
|
@@ -2167,8 +2348,8 @@ program
|
|
|
2167
2348
|
.command('risk')
|
|
2168
2349
|
.description('Rank functions by architectural risk (PageRank × betweenness × complexity × untested)')
|
|
2169
2350
|
.option('--limit <n>', 'Max entries to show', '20')
|
|
2170
|
-
.option('--path <dir>',
|
|
2171
|
-
.option('--json',
|
|
2351
|
+
.option('--path <dir>', OPTION_PROJECT_PATH, process.cwd())
|
|
2352
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2172
2353
|
.option('--format <format>', 'Output format: dot (call-graph neighborhood of top functions)')
|
|
2173
2354
|
.action(async (options) => {
|
|
2174
2355
|
try {
|
|
@@ -2183,10 +2364,13 @@ program
|
|
|
2183
2364
|
if (options.format === 'dot') {
|
|
2184
2365
|
// DOT output: call-graph neighborhood of top-N risk functions
|
|
2185
2366
|
const { callGraphToDot } = await import('./graph/outputFormatter.js');
|
|
2186
|
-
// Collect IDs for top risk functions
|
|
2367
|
+
// Collect IDs for top risk functions — one query for the whole top-N
|
|
2368
|
+
// set, not one query per function (loop-query).
|
|
2187
2369
|
const topIds = new Set();
|
|
2188
|
-
|
|
2189
|
-
const
|
|
2370
|
+
if (top.length > 0) {
|
|
2371
|
+
const orClause = top.map(() => '(name = ? AND file_path = ?)').join(' OR ');
|
|
2372
|
+
const params = top.flatMap((e) => [e.functionName, e.filePath]);
|
|
2373
|
+
const rows = rawDb.prepare(`SELECT id FROM functions WHERE ${orClause}`).all(...params);
|
|
2190
2374
|
for (const r of rows)
|
|
2191
2375
|
topIds.add(r.id);
|
|
2192
2376
|
}
|
|
@@ -2243,7 +2427,7 @@ ledgerCmd
|
|
|
2243
2427
|
.command('list')
|
|
2244
2428
|
.description('List recent audit runs')
|
|
2245
2429
|
.option('--limit <n>', 'Max runs to show', '20')
|
|
2246
|
-
.option('--json',
|
|
2430
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2247
2431
|
.action(async (options) => {
|
|
2248
2432
|
try {
|
|
2249
2433
|
const { listRuns } = await import('./ledger.js');
|
|
@@ -2292,7 +2476,7 @@ ledgerCmd
|
|
|
2292
2476
|
ledgerCmd
|
|
2293
2477
|
.command('stats')
|
|
2294
2478
|
.description('Show aggregated ledger statistics')
|
|
2295
|
-
.option('--json',
|
|
2479
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2296
2480
|
.action(async (options) => {
|
|
2297
2481
|
try {
|
|
2298
2482
|
const { getLedgerStats } = await import('./ledger.js');
|
|
@@ -2325,7 +2509,7 @@ ledgerCmd
|
|
|
2325
2509
|
.command('trends')
|
|
2326
2510
|
.description('Compare consecutive full-audit runs and report new vs fixed readings per rule')
|
|
2327
2511
|
.option('--since <runId>', 'Only consider runs after this run ID')
|
|
2328
|
-
.option('--json',
|
|
2512
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2329
2513
|
.action(async (options) => {
|
|
2330
2514
|
try {
|
|
2331
2515
|
const { getTrends } = await import('./ledger.js');
|
|
@@ -2388,9 +2572,6 @@ ledgerCmd
|
|
|
2388
2572
|
// `audit --detach` forks workers/auditJobRunner; `status`/`result`/`jobs` read
|
|
2389
2573
|
// the persisted ledger. One job model (the ledger run), one write path — these
|
|
2390
2574
|
// are thin wrappers over ledger.ts + mcpAuditJobs.ts, not a second store.
|
|
2391
|
-
/** Full analyzer set — derives from createAuditRunner's registry so `--detach`
|
|
2392
|
-
* produces the same per-rule coverage as a synchronous `audit`. */
|
|
2393
|
-
const DETACHED_DEFAULT_ANALYZERS = [...ALL_ANALYZERS];
|
|
2394
2575
|
/** Lease TTL used by read-path reclaim, mirroring mcpAuditJobs.jobLeaseTtlMs. */
|
|
2395
2576
|
const CLI_JOB_LEASE_TTL_MS = Number(process.env.CODE_AUDITOR_JOB_LEASE_TTL_MS) || 30_000;
|
|
2396
2577
|
function spec41StatusBadge(status) {
|
|
@@ -2472,12 +2653,8 @@ async function runDetachedAudit(options) {
|
|
|
2472
2653
|
const logFd = openSync(logPath, 'a');
|
|
2473
2654
|
const argsJson = JSON.stringify({
|
|
2474
2655
|
path: auditPath,
|
|
2475
|
-
...(options.partitionStrategy && { partitionStrategy: options.partitionStrategy }),
|
|
2476
|
-
...(options.maxPartitions && { maxPartitions: Number(options.maxPartitions) }),
|
|
2477
|
-
...(options.shardTimeoutMs && { shardTimeoutMs: Number(options.shardTimeoutMs) }),
|
|
2478
2656
|
});
|
|
2479
2657
|
const defaultsJson = JSON.stringify({
|
|
2480
|
-
defaultAnalyzers: DETACHED_DEFAULT_ANALYZERS,
|
|
2481
2658
|
defaultMinSeverity: 'high',
|
|
2482
2659
|
defaultGenerateCodeMap: false,
|
|
2483
2660
|
});
|
|
@@ -2497,7 +2674,7 @@ program
|
|
|
2497
2674
|
.command('status <jobId>')
|
|
2498
2675
|
.description('Show a detached audit job status, progress, and staleness')
|
|
2499
2676
|
.option('-p, --path <path>', 'Project root containing the job (default: cwd)', process.cwd())
|
|
2500
|
-
.option('--json',
|
|
2677
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2501
2678
|
.action(async (jobId, options) => {
|
|
2502
2679
|
try {
|
|
2503
2680
|
const { getLedgerRun, reclaimStaleRunning, computeStaleness } = await import('./ledger.js');
|
|
@@ -2560,12 +2737,12 @@ program
|
|
|
2560
2737
|
.option('--analyzer <analyzer>', 'Filter by analyzer')
|
|
2561
2738
|
.option('--file <file>', 'Filter by file path (substring match)')
|
|
2562
2739
|
.option('--severity <severity>', 'Filter by severity (critical|severe|high)')
|
|
2563
|
-
.option('--state [state]', 'Query coverage by state (fired|clean|notApplicable|cannot-fire|
|
|
2740
|
+
.option('--state [state]', 'Query coverage by state (fired|clean|notApplicable|cannot-fire|incomplete); omit value for all')
|
|
2564
2741
|
.option('--count', 'Group findings by analyzer/rule with counts')
|
|
2565
2742
|
.option('--limit <n>', 'Max findings to return (0 = unbounded)', '50')
|
|
2566
2743
|
.option('--offset <n>', 'Findings offset', '0')
|
|
2567
2744
|
.option('--stale', 'Force full content re-hash for staleness')
|
|
2568
|
-
.option('--json',
|
|
2745
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2569
2746
|
.action(async (jobId, options) => {
|
|
2570
2747
|
try {
|
|
2571
2748
|
const { getLedgerRun, queryLedgerFindings, queryLedgerCoverage, computeStaleness } = await import('./ledger.js');
|
|
@@ -2658,7 +2835,7 @@ program
|
|
|
2658
2835
|
.option('--prune', 'Delete all but the newest runs for this project')
|
|
2659
2836
|
.option('--keep <n>', 'Runs to keep when --prune is set', '10')
|
|
2660
2837
|
.option('--limit <n>', 'Runs to display (staleness computed for these)', '20')
|
|
2661
|
-
.option('--json',
|
|
2838
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2662
2839
|
.action(async (options) => {
|
|
2663
2840
|
try {
|
|
2664
2841
|
const { listLedgerRuns, reclaimStaleRunning, pruneLedgerRuns, computeStaleness } = await import('./ledger.js');
|
|
@@ -2714,7 +2891,7 @@ conventionsCmd
|
|
|
2714
2891
|
.command('list')
|
|
2715
2892
|
.description('List mined conventions from the code index')
|
|
2716
2893
|
.option('--domain <domain>', 'Filter by domain: usage-pair, import-form, error-handling, export-shape, naming')
|
|
2717
|
-
.option('--json',
|
|
2894
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2718
2895
|
.action(async (options) => {
|
|
2719
2896
|
try {
|
|
2720
2897
|
const db = CodeIndexDB.getInstance();
|
|
@@ -2778,7 +2955,7 @@ conventionsCmd
|
|
|
2778
2955
|
.command('propose')
|
|
2779
2956
|
.description('Emit ready-to-paste .codeauditor.json rules from mined conventions')
|
|
2780
2957
|
.option('--domain <domain>', 'Filter by domain: naming or import-form (other domains do not map to rule kinds)')
|
|
2781
|
-
.option('--json',
|
|
2958
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2782
2959
|
.action(async (options) => {
|
|
2783
2960
|
try {
|
|
2784
2961
|
const db = CodeIndexDB.getInstance();
|
|
@@ -2888,8 +3065,8 @@ program
|
|
|
2888
3065
|
program
|
|
2889
3066
|
.command('architecture')
|
|
2890
3067
|
.description('Analyze import graph: community detection, directory purity, Martin instability metrics')
|
|
2891
|
-
.option('--path <dir>',
|
|
2892
|
-
.option('--json',
|
|
3068
|
+
.option('--path <dir>', OPTION_PROJECT_PATH, process.cwd())
|
|
3069
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
2893
3070
|
.option('--format <format>', 'Output format: dot or mermaid (import graph colored by community)')
|
|
2894
3071
|
.action(async (options) => {
|
|
2895
3072
|
try {
|
|
@@ -3087,7 +3264,7 @@ coverageCmd
|
|
|
3087
3264
|
coverageCmd
|
|
3088
3265
|
.command('by-risk', { isDefault: true })
|
|
3089
3266
|
.description('Show coverage rate by risk decile')
|
|
3090
|
-
.option('--json',
|
|
3267
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
3091
3268
|
.action(async (options) => {
|
|
3092
3269
|
try {
|
|
3093
3270
|
const { generateCoverageReport } = await import('./coverage/coverageService.js');
|
|
@@ -3136,8 +3313,8 @@ coverageCmd
|
|
|
3136
3313
|
program
|
|
3137
3314
|
.command('print-config <file>')
|
|
3138
3315
|
.description('Print the effective config for a file, naming the source of every value')
|
|
3139
|
-
.option('-p, --path <path>',
|
|
3140
|
-
.option('--json',
|
|
3316
|
+
.option('-p, --path <path>', OPTION_PROJECT_PATH, process.cwd())
|
|
3317
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
3141
3318
|
.option('--preset <id>', 'Apply a shareable preset (repeatable)', (v, prev) => prev.concat([v]), [])
|
|
3142
3319
|
.action(async (file, options) => {
|
|
3143
3320
|
try {
|
|
@@ -3155,9 +3332,6 @@ program
|
|
|
3155
3332
|
// normalizes the real config, so path keys compare cleanly instead of
|
|
3156
3333
|
// reading as "user-set" because one side is absolute and the other is not.
|
|
3157
3334
|
const pristine = { ...getDefaultConfig(), codeIndex: { ...DEFAULT_CODE_INDEX_CONFIG } };
|
|
3158
|
-
if (pristine.outputDirectory) {
|
|
3159
|
-
pristine.outputDirectory = pathModule.resolve(projectRoot, pristine.outputDirectory);
|
|
3160
|
-
}
|
|
3161
3335
|
pristine.includePaths = (pristine.includePaths ?? []).map((p) => pathModule.isAbsolute(p) ? p : pathModule.resolve(projectRoot, p));
|
|
3162
3336
|
pristine.excludePaths = (pristine.excludePaths ?? []).map((p) => pathModule.isAbsolute(p) ? p : pathModule.resolve(projectRoot, p));
|
|
3163
3337
|
// Load the project config (defaults when no config file exists).
|
|
@@ -3171,7 +3345,6 @@ program
|
|
|
3171
3345
|
}
|
|
3172
3346
|
const analyzerConfigs = (config.analyzerConfigs ?? {});
|
|
3173
3347
|
const pathProfiles = (config.pathProfiles ?? []);
|
|
3174
|
-
const enabledAnalyzers = (config.enabledAnalyzers ?? []);
|
|
3175
3348
|
// Resolve --preset ids to ordered preset objects (Spec 38 R4). Unknown
|
|
3176
3349
|
// ids are a hard error, not a silent skip.
|
|
3177
3350
|
const presetIds = Array.isArray(options.preset) ? options.preset : (options.preset ? [options.preset] : []);
|
|
@@ -3191,7 +3364,6 @@ program
|
|
|
3191
3364
|
projectRoot,
|
|
3192
3365
|
analyzerConfigs,
|
|
3193
3366
|
pathProfiles,
|
|
3194
|
-
enabledAnalyzers,
|
|
3195
3367
|
presets,
|
|
3196
3368
|
lintConfig,
|
|
3197
3369
|
});
|
|
@@ -3304,7 +3476,7 @@ program
|
|
|
3304
3476
|
for (const v of sorted) {
|
|
3305
3477
|
const icon = v.severity === 'critical' ? '🔴' :
|
|
3306
3478
|
v.severity === 'severe' ? '🟠' : '🟡';
|
|
3307
|
-
console.log(`${icon} ${chalk.bold(v.file)}${v.line
|
|
3479
|
+
console.log(`${icon} ${chalk.bold(v.file)}${lineSuffix(v.line)} [${v.rule || v.severity}] ${v.message}`);
|
|
3308
3480
|
}
|
|
3309
3481
|
}
|
|
3310
3482
|
catch (error) {
|
|
@@ -3369,8 +3541,8 @@ const daemonCmd = program
|
|
|
3369
3541
|
daemonCmd
|
|
3370
3542
|
.command('start')
|
|
3371
3543
|
.description('Start the daemon for this project (detached by default)')
|
|
3372
|
-
.option('-p, --path <path>',
|
|
3373
|
-
.option('-c, --config <config>',
|
|
3544
|
+
.option('-p, --path <path>', OPTION_PROJECT_ROOT, process.cwd())
|
|
3545
|
+
.option('-c, --config <config>', OPTION_CONFIGURATION_NAME)
|
|
3374
3546
|
.option('--foreground', 'Run in the foreground (blocking; useful for debugging)')
|
|
3375
3547
|
.action(async (options) => {
|
|
3376
3548
|
try {
|
|
@@ -3401,7 +3573,7 @@ daemonCmd
|
|
|
3401
3573
|
daemonCmd
|
|
3402
3574
|
.command('stop')
|
|
3403
3575
|
.description('Ask the daemon to shut down')
|
|
3404
|
-
.option('-p, --path <path>',
|
|
3576
|
+
.option('-p, --path <path>', OPTION_PROJECT_ROOT, process.cwd())
|
|
3405
3577
|
.action(async (options) => {
|
|
3406
3578
|
try {
|
|
3407
3579
|
const { resolveDaemonSocketPath } = await import('./dataPaths.js');
|
|
@@ -3423,8 +3595,8 @@ daemonCmd
|
|
|
3423
3595
|
daemonCmd
|
|
3424
3596
|
.command('status')
|
|
3425
3597
|
.description('Show daemon status')
|
|
3426
|
-
.option('-p, --path <path>',
|
|
3427
|
-
.option('--json',
|
|
3598
|
+
.option('-p, --path <path>', OPTION_PROJECT_ROOT, process.cwd())
|
|
3599
|
+
.option('--json', OPTION_OUTPUT_AS_JSON)
|
|
3428
3600
|
.action(async (options) => {
|
|
3429
3601
|
try {
|
|
3430
3602
|
const { resolveDaemon } = await import('./daemon/resolve.js');
|
|
@@ -3534,6 +3706,9 @@ const SCAFFOLD_CONFIG = {
|
|
|
3534
3706
|
async function generateConfigurations(options) {
|
|
3535
3707
|
const outputDir = resolve(options.output || '.');
|
|
3536
3708
|
const outputPath = join(outputDir, '.codeauditor.json');
|
|
3709
|
+
// One consent spelling across the CLI: `--overwrite`. `--force`/`--yes` are
|
|
3710
|
+
// retained as aliases for back-compat.
|
|
3711
|
+
const overwrite = Boolean(options.overwrite || options.force || options.yes);
|
|
3537
3712
|
let config;
|
|
3538
3713
|
if (options.interactive) {
|
|
3539
3714
|
// --- Interactive rule builder ---
|
|
@@ -3553,7 +3728,7 @@ async function generateConfigurations(options) {
|
|
|
3553
3728
|
config = await buildRulesInteractively();
|
|
3554
3729
|
}
|
|
3555
3730
|
// Confirm output directory
|
|
3556
|
-
if (!
|
|
3731
|
+
if (!overwrite) {
|
|
3557
3732
|
const { dir } = await inquirer.prompt({
|
|
3558
3733
|
dir: {
|
|
3559
3734
|
type: 'input',
|
|
@@ -3598,12 +3773,12 @@ async function generateConfigurations(options) {
|
|
|
3598
3773
|
exists = true;
|
|
3599
3774
|
}
|
|
3600
3775
|
catch { /* ok */ }
|
|
3601
|
-
if (exists && !
|
|
3776
|
+
if (exists && !overwrite) {
|
|
3602
3777
|
console.log(chalk.yellow(`.codeauditor.json already exists at ${outputPath}`));
|
|
3603
|
-
console.log(chalk.gray('Use --
|
|
3778
|
+
console.log(chalk.gray('Use --overwrite to replace it, or --interactive to build a custom config.'));
|
|
3604
3779
|
return;
|
|
3605
3780
|
}
|
|
3606
|
-
if (exists &&
|
|
3781
|
+
if (exists && overwrite) {
|
|
3607
3782
|
console.log(chalk.yellow('Overwriting existing .codeauditor.json...'));
|
|
3608
3783
|
}
|
|
3609
3784
|
config = SCAFFOLD_CONFIG;
|
|
@@ -3614,6 +3789,136 @@ async function generateConfigurations(options) {
|
|
|
3614
3789
|
console.log(chalk.gray(' 2. Run ') + chalk.cyan('code-audit') + chalk.gray(' to enforce your rules'));
|
|
3615
3790
|
console.log(chalk.gray(' 3. Use ') + chalk.cyan('code-audit changed') + chalk.gray(' in your agent hook (gating rules block on new readings)'));
|
|
3616
3791
|
}
|
|
3792
|
+
async function promptKindSpecificFields(kind, rule) {
|
|
3793
|
+
switch (kind) {
|
|
3794
|
+
case 'import-ban': {
|
|
3795
|
+
const { module } = await inquirer.prompt({
|
|
3796
|
+
module: {
|
|
3797
|
+
type: 'input',
|
|
3798
|
+
message: 'Banned module specifier (e.g. "lodash" or "@old-lib/*"):',
|
|
3799
|
+
validate: (input) => input.trim() ? true : 'Module specifier is required',
|
|
3800
|
+
},
|
|
3801
|
+
});
|
|
3802
|
+
rule.module = module;
|
|
3803
|
+
const { addExcept } = await inquirer.prompt({
|
|
3804
|
+
addExcept: {
|
|
3805
|
+
type: 'confirm',
|
|
3806
|
+
message: 'Add exception paths (files allowed to import it)?',
|
|
3807
|
+
default: false,
|
|
3808
|
+
},
|
|
3809
|
+
});
|
|
3810
|
+
if (addExcept) {
|
|
3811
|
+
const { except } = await inquirer.prompt({
|
|
3812
|
+
except: {
|
|
3813
|
+
type: 'input',
|
|
3814
|
+
message: 'Exception globs (comma-separated, e.g. "src/migration/**"):',
|
|
3815
|
+
},
|
|
3816
|
+
});
|
|
3817
|
+
const exceptList = except.split(',').map((s) => s.trim()).filter(Boolean);
|
|
3818
|
+
if (exceptList.length > 0)
|
|
3819
|
+
rule.except = exceptList;
|
|
3820
|
+
}
|
|
3821
|
+
break;
|
|
3822
|
+
}
|
|
3823
|
+
case 'call-constraint': {
|
|
3824
|
+
const { callee } = await inquirer.prompt({
|
|
3825
|
+
callee: {
|
|
3826
|
+
type: 'input',
|
|
3827
|
+
message: 'Callee (function name, optionally path-qualified as "path/glob#name"):',
|
|
3828
|
+
validate: (input) => input.trim() ? true : 'Callee is required',
|
|
3829
|
+
},
|
|
3830
|
+
});
|
|
3831
|
+
rule.callee = callee;
|
|
3832
|
+
const { mode } = await inquirer.prompt({
|
|
3833
|
+
mode: {
|
|
3834
|
+
type: 'list',
|
|
3835
|
+
message: 'Restriction mode:',
|
|
3836
|
+
choices: [
|
|
3837
|
+
{ name: 'Allow only specific callers (allowFrom)', value: 'allow' },
|
|
3838
|
+
{ name: 'Deny specific callers (denyFrom)', value: 'deny' },
|
|
3839
|
+
],
|
|
3840
|
+
},
|
|
3841
|
+
});
|
|
3842
|
+
const { paths } = await inquirer.prompt({
|
|
3843
|
+
paths: {
|
|
3844
|
+
type: 'input',
|
|
3845
|
+
message: `Path globs (comma-separated) for ${mode === 'allow' ? 'allowFrom' : 'denyFrom'}:`,
|
|
3846
|
+
validate: (input) => input.trim() ? true : 'At least one path glob is required',
|
|
3847
|
+
},
|
|
3848
|
+
});
|
|
3849
|
+
const pathList = paths.split(',').map((s) => s.trim()).filter(Boolean);
|
|
3850
|
+
if (mode === 'allow') {
|
|
3851
|
+
rule.allowFrom = pathList;
|
|
3852
|
+
}
|
|
3853
|
+
else {
|
|
3854
|
+
rule.denyFrom = pathList;
|
|
3855
|
+
}
|
|
3856
|
+
break;
|
|
3857
|
+
}
|
|
3858
|
+
case 'module-boundary': {
|
|
3859
|
+
const { from, to } = await inquirer.prompt({
|
|
3860
|
+
from: {
|
|
3861
|
+
type: 'input',
|
|
3862
|
+
message: 'From (path glob for files that must not import):',
|
|
3863
|
+
validate: (input) => input.trim() ? true : '"from" path glob is required',
|
|
3864
|
+
},
|
|
3865
|
+
to: {
|
|
3866
|
+
type: 'input',
|
|
3867
|
+
message: 'To (path glob for files that must not be imported):',
|
|
3868
|
+
validate: (input) => input.trim() ? true : '"to" path glob is required',
|
|
3869
|
+
},
|
|
3870
|
+
});
|
|
3871
|
+
rule.from = from;
|
|
3872
|
+
rule.to = to;
|
|
3873
|
+
break;
|
|
3874
|
+
}
|
|
3875
|
+
case 'naming': {
|
|
3876
|
+
const { path, exports: exportPattern } = await inquirer.prompt({
|
|
3877
|
+
path: {
|
|
3878
|
+
type: 'input',
|
|
3879
|
+
message: 'Path (glob for files this rule applies to):',
|
|
3880
|
+
validate: (input) => input.trim() ? true : 'Path glob is required',
|
|
3881
|
+
},
|
|
3882
|
+
exports: {
|
|
3883
|
+
type: 'input',
|
|
3884
|
+
message: 'Exports regex (exported symbols must match, e.g. "^use[A-Z]"):',
|
|
3885
|
+
validate: (input) => input.trim() ? true : 'Exports regex is required',
|
|
3886
|
+
},
|
|
3887
|
+
});
|
|
3888
|
+
rule.path = path;
|
|
3889
|
+
rule.exports = exportPattern;
|
|
3890
|
+
break;
|
|
3891
|
+
}
|
|
3892
|
+
case 'ast-pattern': {
|
|
3893
|
+
const { pattern } = await inquirer.prompt({
|
|
3894
|
+
pattern: {
|
|
3895
|
+
type: 'input',
|
|
3896
|
+
message: 'AST pattern (ast-grep syntax, e.g. "new Function($$$)"):',
|
|
3897
|
+
validate: (input) => input.trim() ? true : 'Pattern is required',
|
|
3898
|
+
},
|
|
3899
|
+
});
|
|
3900
|
+
rule.pattern = pattern;
|
|
3901
|
+
const { addLanguage } = await inquirer.prompt({
|
|
3902
|
+
addLanguage: {
|
|
3903
|
+
type: 'confirm',
|
|
3904
|
+
message: 'Restrict to a specific language? (default: typescript)',
|
|
3905
|
+
default: false,
|
|
3906
|
+
},
|
|
3907
|
+
});
|
|
3908
|
+
if (addLanguage) {
|
|
3909
|
+
const { language } = await inquirer.prompt({
|
|
3910
|
+
language: {
|
|
3911
|
+
type: 'list',
|
|
3912
|
+
message: 'Language:',
|
|
3913
|
+
choices: ['typescript', 'javascript', 'go'],
|
|
3914
|
+
},
|
|
3915
|
+
});
|
|
3916
|
+
rule.language = language;
|
|
3917
|
+
}
|
|
3918
|
+
break;
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3617
3922
|
/**
|
|
3618
3923
|
* Interactive rule builder — walks the user through adding rules one at a time.
|
|
3619
3924
|
*/
|
|
@@ -3672,134 +3977,7 @@ async function buildRulesInteractively() {
|
|
|
3672
3977
|
message: common.message,
|
|
3673
3978
|
};
|
|
3674
3979
|
// Kind-specific fields
|
|
3675
|
-
|
|
3676
|
-
case 'import-ban': {
|
|
3677
|
-
const { module } = await inquirer.prompt({
|
|
3678
|
-
module: {
|
|
3679
|
-
type: 'input',
|
|
3680
|
-
message: 'Banned module specifier (e.g. "lodash" or "@old-lib/*"):',
|
|
3681
|
-
validate: (input) => input.trim() ? true : 'Module specifier is required',
|
|
3682
|
-
},
|
|
3683
|
-
});
|
|
3684
|
-
rule.module = module;
|
|
3685
|
-
const { addExcept } = await inquirer.prompt({
|
|
3686
|
-
addExcept: {
|
|
3687
|
-
type: 'confirm',
|
|
3688
|
-
message: 'Add exception paths (files allowed to import it)?',
|
|
3689
|
-
default: false,
|
|
3690
|
-
},
|
|
3691
|
-
});
|
|
3692
|
-
if (addExcept) {
|
|
3693
|
-
const { except } = await inquirer.prompt({
|
|
3694
|
-
except: {
|
|
3695
|
-
type: 'input',
|
|
3696
|
-
message: 'Exception globs (comma-separated, e.g. "src/migration/**"):',
|
|
3697
|
-
},
|
|
3698
|
-
});
|
|
3699
|
-
const exceptList = except.split(',').map((s) => s.trim()).filter(Boolean);
|
|
3700
|
-
if (exceptList.length > 0)
|
|
3701
|
-
rule.except = exceptList;
|
|
3702
|
-
}
|
|
3703
|
-
break;
|
|
3704
|
-
}
|
|
3705
|
-
case 'call-constraint': {
|
|
3706
|
-
const { callee } = await inquirer.prompt({
|
|
3707
|
-
callee: {
|
|
3708
|
-
type: 'input',
|
|
3709
|
-
message: 'Callee (function name, optionally path-qualified as "path/glob#name"):',
|
|
3710
|
-
validate: (input) => input.trim() ? true : 'Callee is required',
|
|
3711
|
-
},
|
|
3712
|
-
});
|
|
3713
|
-
rule.callee = callee;
|
|
3714
|
-
const { mode } = await inquirer.prompt({
|
|
3715
|
-
mode: {
|
|
3716
|
-
type: 'list',
|
|
3717
|
-
message: 'Restriction mode:',
|
|
3718
|
-
choices: [
|
|
3719
|
-
{ name: 'Allow only specific callers (allowFrom)', value: 'allow' },
|
|
3720
|
-
{ name: 'Deny specific callers (denyFrom)', value: 'deny' },
|
|
3721
|
-
],
|
|
3722
|
-
},
|
|
3723
|
-
});
|
|
3724
|
-
const { paths } = await inquirer.prompt({
|
|
3725
|
-
paths: {
|
|
3726
|
-
type: 'input',
|
|
3727
|
-
message: `Path globs (comma-separated) for ${mode === 'allow' ? 'allowFrom' : 'denyFrom'}:`,
|
|
3728
|
-
validate: (input) => input.trim() ? true : 'At least one path glob is required',
|
|
3729
|
-
},
|
|
3730
|
-
});
|
|
3731
|
-
const pathList = paths.split(',').map((s) => s.trim()).filter(Boolean);
|
|
3732
|
-
if (mode === 'allow') {
|
|
3733
|
-
rule.allowFrom = pathList;
|
|
3734
|
-
}
|
|
3735
|
-
else {
|
|
3736
|
-
rule.denyFrom = pathList;
|
|
3737
|
-
}
|
|
3738
|
-
break;
|
|
3739
|
-
}
|
|
3740
|
-
case 'module-boundary': {
|
|
3741
|
-
const { from, to } = await inquirer.prompt({
|
|
3742
|
-
from: {
|
|
3743
|
-
type: 'input',
|
|
3744
|
-
message: 'From (path glob for files that must not import):',
|
|
3745
|
-
validate: (input) => input.trim() ? true : '"from" path glob is required',
|
|
3746
|
-
},
|
|
3747
|
-
to: {
|
|
3748
|
-
type: 'input',
|
|
3749
|
-
message: 'To (path glob for files that must not be imported):',
|
|
3750
|
-
validate: (input) => input.trim() ? true : '"to" path glob is required',
|
|
3751
|
-
},
|
|
3752
|
-
});
|
|
3753
|
-
rule.from = from;
|
|
3754
|
-
rule.to = to;
|
|
3755
|
-
break;
|
|
3756
|
-
}
|
|
3757
|
-
case 'naming': {
|
|
3758
|
-
const { path, exports: exportPattern } = await inquirer.prompt({
|
|
3759
|
-
path: {
|
|
3760
|
-
type: 'input',
|
|
3761
|
-
message: 'Path (glob for files this rule applies to):',
|
|
3762
|
-
validate: (input) => input.trim() ? true : 'Path glob is required',
|
|
3763
|
-
},
|
|
3764
|
-
exports: {
|
|
3765
|
-
type: 'input',
|
|
3766
|
-
message: 'Exports regex (exported symbols must match, e.g. "^use[A-Z]"):',
|
|
3767
|
-
validate: (input) => input.trim() ? true : 'Exports regex is required',
|
|
3768
|
-
},
|
|
3769
|
-
});
|
|
3770
|
-
rule.path = path;
|
|
3771
|
-
rule.exports = exportPattern;
|
|
3772
|
-
break;
|
|
3773
|
-
}
|
|
3774
|
-
case 'ast-pattern': {
|
|
3775
|
-
const { pattern } = await inquirer.prompt({
|
|
3776
|
-
pattern: {
|
|
3777
|
-
type: 'input',
|
|
3778
|
-
message: 'AST pattern (ast-grep syntax, e.g. "new Function($$$)"):',
|
|
3779
|
-
validate: (input) => input.trim() ? true : 'Pattern is required',
|
|
3780
|
-
},
|
|
3781
|
-
});
|
|
3782
|
-
rule.pattern = pattern;
|
|
3783
|
-
const { addLanguage } = await inquirer.prompt({
|
|
3784
|
-
addLanguage: {
|
|
3785
|
-
type: 'confirm',
|
|
3786
|
-
message: 'Restrict to a specific language? (default: typescript)',
|
|
3787
|
-
default: false,
|
|
3788
|
-
},
|
|
3789
|
-
});
|
|
3790
|
-
if (addLanguage) {
|
|
3791
|
-
const { language } = await inquirer.prompt({
|
|
3792
|
-
language: {
|
|
3793
|
-
type: 'list',
|
|
3794
|
-
message: 'Language:',
|
|
3795
|
-
choices: ['typescript', 'javascript', 'go'],
|
|
3796
|
-
},
|
|
3797
|
-
});
|
|
3798
|
-
rule.language = language;
|
|
3799
|
-
}
|
|
3800
|
-
break;
|
|
3801
|
-
}
|
|
3802
|
-
}
|
|
3980
|
+
await promptKindSpecificFields(kind, rule);
|
|
3803
3981
|
rules.push(rule);
|
|
3804
3982
|
console.log(chalk.green(` ✓ Added rule "${rule.id}" [${rule.kind}]`));
|
|
3805
3983
|
const { cont } = await inquirer.prompt({
|
|
@@ -3877,9 +4055,22 @@ async function generateCodeMap(options) {
|
|
|
3877
4055
|
const textOutput = mapGenerator.formatAsText(completeCodeMap, mapOptions);
|
|
3878
4056
|
// Output results
|
|
3879
4057
|
if (options.output) {
|
|
3880
|
-
// Save to file
|
|
3881
|
-
|
|
3882
|
-
|
|
4058
|
+
// Save to file. Refuse to clobber an existing file unless --overwrite is
|
|
4059
|
+
// passed — same guard as the report writer, so `map -o` cannot silently
|
|
4060
|
+
// destroy a file the developer did not consent to replacing.
|
|
4061
|
+
const outPath = resolve(options.output);
|
|
4062
|
+
let exists = false;
|
|
4063
|
+
try {
|
|
4064
|
+
await fs.access(outPath);
|
|
4065
|
+
exists = true;
|
|
4066
|
+
}
|
|
4067
|
+
catch { /* ok */ }
|
|
4068
|
+
if (exists && !options.overwrite) {
|
|
4069
|
+
console.error(chalk.red(`Refusing to overwrite existing file ${outPath} — pass --overwrite to replace it.`));
|
|
4070
|
+
process.exit(1);
|
|
4071
|
+
}
|
|
4072
|
+
await fs.writeFile(outPath, textOutput);
|
|
4073
|
+
console.log(chalk.green(`✓ Code map saved to: ${outPath}`));
|
|
3883
4074
|
// Show summary
|
|
3884
4075
|
console.log('');
|
|
3885
4076
|
console.log(chalk.blue('Summary:'));
|
|
@@ -3919,7 +4110,7 @@ async function runSearch(query, options) {
|
|
|
3919
4110
|
await db.initialize();
|
|
3920
4111
|
// --definition: look up a specific symbol
|
|
3921
4112
|
if (options.definition) {
|
|
3922
|
-
const func = await db.findDefinition(query);
|
|
4113
|
+
const func = await db.functionIndex.findDefinition(query);
|
|
3923
4114
|
if (!func) {
|
|
3924
4115
|
console.log(chalk.yellow(`No definition found for "${query}"`));
|
|
3925
4116
|
return;
|
|
@@ -3931,9 +4122,6 @@ async function runSearch(query, options) {
|
|
|
3931
4122
|
console.log(chalk.blue(`\nDefinition: ${func.name}`));
|
|
3932
4123
|
console.log(chalk.gray('──────────────────────────────────────────'));
|
|
3933
4124
|
console.log(`${chalk.bold('File:')} ${func.filePath}${func.lineNumber ? `:${func.lineNumber}` : ''}`);
|
|
3934
|
-
if (func.signature) {
|
|
3935
|
-
console.log(`${chalk.bold('Signature:')} ${func.signature}`);
|
|
3936
|
-
}
|
|
3937
4125
|
if (func.purpose) {
|
|
3938
4126
|
console.log(`${chalk.bold('Purpose:')} ${func.purpose}`);
|
|
3939
4127
|
}
|
|
@@ -3965,7 +4153,7 @@ async function runSearch(query, options) {
|
|
|
3965
4153
|
}
|
|
3966
4154
|
searchOptions.filters.language = options.language;
|
|
3967
4155
|
}
|
|
3968
|
-
const result = await db.searchFunctions(searchOptions);
|
|
4156
|
+
const result = await db.search.searchFunctions(searchOptions);
|
|
3969
4157
|
if (options.json) {
|
|
3970
4158
|
process.stdout.write(JSON.stringify({
|
|
3971
4159
|
query,
|
|
@@ -3988,9 +4176,6 @@ async function runSearch(query, options) {
|
|
|
3988
4176
|
const lang = func.language ? chalk.dim(` [${func.language}]`) : '';
|
|
3989
4177
|
const lineInfo = func.lineNumber ? chalk.dim(`:${func.lineNumber}`) : '';
|
|
3990
4178
|
console.log(`${chalk.bold(func.name)}${lang} — ${chalk.green(func.filePath)}${lineInfo}`);
|
|
3991
|
-
if (func.signature) {
|
|
3992
|
-
console.log(chalk.dim(` ${func.signature}`));
|
|
3993
|
-
}
|
|
3994
4179
|
if (func.purpose) {
|
|
3995
4180
|
console.log(chalk.dim(` ${func.purpose}`));
|
|
3996
4181
|
}
|