code-auditor-mcp 3.4.11 ā 3.4.13
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 +2 -2
- package/CHANGELOG.md +119 -1
- package/dist/analyzers/__verify_self_probe.d.ts +2 -0
- package/dist/analyzers/__verify_self_probe.d.ts.map +1 -0
- package/dist/analyzers/__verify_self_probe.js +5 -0
- package/dist/analyzers/__verify_self_probe.js.map +1 -0
- package/dist/analyzers/analyzerUtils.d.ts +34 -1
- package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
- package/dist/analyzers/analyzerUtils.js +61 -22
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +18 -23
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +198 -186
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +78 -55
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +244 -243
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +27 -37
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +314 -275
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +17 -46
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +620 -523
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts +7 -2
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +259 -198
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.js +59 -46
- package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.d.ts +10 -0
- package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.js +10 -0
- package/dist/analyzers/orm/adapterRegistry.js.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.d.ts +22 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.js +42 -44
- package/dist/analyzers/orm/drizzleAdapter.js.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.d.ts +30 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.js +30 -0
- package/dist/analyzers/orm/prismaAdapter.js.map +1 -1
- package/dist/analyzers/provenance.d.ts +72 -2
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +462 -338
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts +11 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +188 -125
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +118 -1
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +1717 -104
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/ruleTiming.d.ts +54 -0
- package/dist/analyzers/ruleTiming.d.ts.map +1 -0
- package/dist/analyzers/ruleTiming.js +71 -0
- package/dist/analyzers/ruleTiming.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +13 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +362 -283
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +26 -68
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +430 -391
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +24 -186
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +1081 -1061
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +3 -49
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +414 -340
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +48 -5
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +262 -134
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +23 -212
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +139 -1518
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +78 -79
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +613 -472
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +307 -0
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/codeAnalysis.js +965 -0
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -0
- package/dist/analyzers/universal/schema/config.d.ts +31 -0
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/config.js +57 -0
- package/dist/analyzers/universal/schema/config.js.map +1 -0
- package/dist/analyzers/universal/schema/discovery.d.ts +167 -0
- package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/discovery.js +616 -0
- package/dist/analyzers/universal/schema/discovery.js.map +1 -0
- package/dist/analyzers/universal/schema/jsonSchema.d.ts +27 -0
- package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/jsonSchema.js +368 -0
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -0
- package/dist/analyzers/universal/schema/migrations.d.ts +90 -0
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/migrations.js +238 -0
- package/dist/analyzers/universal/schema/migrations.js.map +1 -0
- package/dist/analyzers/universal/schema/types.d.ts +131 -0
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/types.js +10 -0
- package/dist/analyzers/universal/schema/types.js.map +1 -0
- package/dist/analyzers/universal/schema/violations.d.ts +44 -0
- package/dist/analyzers/universal/schema/violations.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/violations.js +54 -0
- package/dist/analyzers/universal/schema/violations.js.map +1 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +101 -39
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.js +348 -40
- package/dist/cli.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +9 -7
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +12 -3
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +15 -5
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +75 -0
- package/dist/config/effectiveConfig.d.ts.map +1 -0
- package/dist/config/effectiveConfig.js +213 -0
- package/dist/config/effectiveConfig.js.map +1 -0
- package/dist/config/pathProfiles.d.ts +7 -4
- package/dist/config/pathProfiles.d.ts.map +1 -1
- package/dist/config/pathProfiles.js +8 -10
- package/dist/config/pathProfiles.js.map +1 -1
- package/dist/config/thresholdRationales.d.ts +45 -0
- package/dist/config/thresholdRationales.d.ts.map +1 -0
- package/dist/config/thresholdRationales.js +67 -0
- package/dist/config/thresholdRationales.js.map +1 -0
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +6 -2
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/enforcement/diffGate.d.ts +55 -0
- package/dist/enforcement/diffGate.d.ts.map +1 -0
- package/dist/enforcement/diffGate.js +136 -0
- package/dist/enforcement/diffGate.js.map +1 -0
- package/dist/enforcement/gate.d.ts +36 -0
- package/dist/enforcement/gate.d.ts.map +1 -0
- package/dist/enforcement/gate.js +72 -0
- package/dist/enforcement/gate.js.map +1 -0
- package/dist/enforcement/suppressions.d.ts +41 -0
- package/dist/enforcement/suppressions.d.ts.map +1 -0
- package/dist/enforcement/suppressions.js +170 -0
- package/dist/enforcement/suppressions.js.map +1 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +4 -1
- package/dist/fingerprint.js.map +1 -1
- package/dist/grammars/tree-sitter-scss.wasm +0 -0
- package/dist/invariants/ruleEngine.d.ts +3 -4
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +2 -2
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +14 -0
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +93 -55
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/LanguageRegistry.d.ts +8 -0
- package/dist/languages/LanguageRegistry.d.ts.map +1 -1
- package/dist/languages/LanguageRegistry.js +8 -0
- package/dist/languages/LanguageRegistry.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +82 -60
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +404 -349
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts +51 -6
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +127 -88
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +36 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +89 -35
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts +66 -23
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +553 -474
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +38 -9
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +111 -74
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/converter.d.ts +18 -0
- package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/converter.js +18 -0
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/languages/tree-sitter/parser.d.ts +39 -2
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +95 -12
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/types.d.ts +67 -2
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +253 -62
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +1461 -791
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/mcp-tools/whitelistTools.d.ts +1 -1
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js +1 -0
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +1 -0
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/pipeline.d.ts +30 -1
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +527 -203
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +5 -12
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +175 -100
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/presets/presets.d.ts +54 -0
- package/dist/presets/presets.d.ts.map +1 -0
- package/dist/presets/presets.js +181 -0
- package/dist/presets/presets.js.map +1 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +0 -2
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/ruleAliases.d.ts +67 -0
- package/dist/ruleAliases.d.ts.map +1 -0
- package/dist/ruleAliases.js +79 -0
- package/dist/ruleAliases.js.map +1 -0
- package/dist/styles/cssAstExtractor.d.ts +20 -5
- package/dist/styles/cssAstExtractor.d.ts.map +1 -1
- package/dist/styles/cssAstExtractor.js +247 -9
- package/dist/styles/cssAstExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.js +13 -4
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +8 -6
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +61 -15
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +13 -1
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/types.d.ts +201 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +7 -4
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/hooks.json +1 -1
- package/plugin/scripts/hook-audit.sh +25 -7
- package/plugin/skills/code-auditor/SKILL.md +5 -5
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,8 @@ import { queryParser } from './search/QueryParser.js';
|
|
|
20
20
|
import { CodeIndexDB } from './codeIndexDB.js';
|
|
21
21
|
import { getFilesProcessed, getFactsConsumed, isVisitorStatus, isReducerStatus } from './pipeline.js';
|
|
22
22
|
import { createBaselineFromFindings, saveBaseline, loadBaseline, diffBaselines } from './baseline.js';
|
|
23
|
+
import { computeDiffGatingDecision } from './enforcement/gate.js';
|
|
24
|
+
import { computeDiffGate } from './enforcement/diffGate.js';
|
|
23
25
|
// Get package.json for version info
|
|
24
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
25
27
|
const __dirname = dirname(__filename);
|
|
@@ -42,6 +44,7 @@ program
|
|
|
42
44
|
.option('--full', 'Show full violation inventory (overrides default delta view when baseline exists)')
|
|
43
45
|
.option('--include-baseline', 'Evaluate baseline-known violations in --fail-on checks')
|
|
44
46
|
.option('--fail-on-regression', 'Exit code 2 when total advisory debt exceeds the baseline snapshot')
|
|
47
|
+
.option('--preset <id>', 'Apply a shareable preset (repeatable)', (v, prev) => prev.concat([v]), [])
|
|
45
48
|
.action(async (options) => {
|
|
46
49
|
console.log(chalk.blue('š Code Quality Audit Tool'));
|
|
47
50
|
console.log(chalk.gray('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
@@ -54,10 +57,23 @@ program
|
|
|
54
57
|
console.error(chalk.red(`Invalid --fail-on severity: "${failOnSeverity}". Must be one of: ${validSeverities.join(', ')}`));
|
|
55
58
|
process.exit(1);
|
|
56
59
|
}
|
|
60
|
+
// Validate --preset ids up front (Spec 38 R4). Unknown ids are a hard
|
|
61
|
+
// error, not a silent skip ā same contract as print-config.
|
|
62
|
+
const presetIds = Array.isArray(options.preset) ? options.preset : (options.preset ? [options.preset] : []);
|
|
63
|
+
if (presetIds.length > 0) {
|
|
64
|
+
const { getPreset, PRESET_IDS } = await import('./presets/presets.js');
|
|
65
|
+
for (const id of presetIds) {
|
|
66
|
+
if (!getPreset(id)) {
|
|
67
|
+
console.error(chalk.red(`Error: unknown preset "${id}". Available: ${PRESET_IDS.join(', ')}`));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
57
72
|
const runner = createAuditRunner({
|
|
58
73
|
projectRoot: options.path,
|
|
59
74
|
configName: options.config,
|
|
60
|
-
outputDirectory: options.output
|
|
75
|
+
outputDirectory: options.output,
|
|
76
|
+
presets: presetIds
|
|
61
77
|
});
|
|
62
78
|
const result = await runner.run();
|
|
63
79
|
const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
|
|
@@ -134,6 +150,38 @@ program
|
|
|
134
150
|
console.log(`Warnings: ${result.summary.warnings}`);
|
|
135
151
|
console.log(`Suggestions: ${result.summary.suggestions}`);
|
|
136
152
|
}
|
|
153
|
+
// Spec 32 ā unparsed files are never silent. A run where any file failed
|
|
154
|
+
// to parse (WASM abort, read error, ā¦) must surface the count + reasons.
|
|
155
|
+
const unparsedFiles = result.metadata?.unparsedFiles ?? [];
|
|
156
|
+
if (unparsedFiles.length > 0) {
|
|
157
|
+
console.error(chalk.red(`\nā ļø ${unparsedFiles.length} file${unparsedFiles.length !== 1 ? 's' : ''} failed to parse:`));
|
|
158
|
+
for (const u of unparsedFiles.slice(0, 20)) {
|
|
159
|
+
console.error(` ${u.filePath} ā ${u.reason}`);
|
|
160
|
+
}
|
|
161
|
+
if (unparsedFiles.length > 20) {
|
|
162
|
+
console.error(` ⦠and ${unparsedFiles.length - 20} more`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Spec 36 R7 ā suppressions are reported: how many exist, where, and how
|
|
166
|
+
// many are unnecessary (or reasonless). Both are errors, surfaced here.
|
|
167
|
+
const suppressions = result.metadata?.suppressions;
|
|
168
|
+
if (suppressions) {
|
|
169
|
+
console.log(chalk.gray(`\nāā Suppressions āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`));
|
|
170
|
+
console.log(` ${suppressions.total} directive${suppressions.total !== 1 ? 's' : ''}, ` +
|
|
171
|
+
`${suppressions.suppressed} finding${suppressions.suppressed !== 1 ? 's' : ''} suppressed`);
|
|
172
|
+
if (suppressions.unnecessary.length > 0) {
|
|
173
|
+
console.error(chalk.red(` ā ${suppressions.unnecessary.length} unnecessary (finding no longer fires):`));
|
|
174
|
+
for (const d of suppressions.unnecessary) {
|
|
175
|
+
console.error(` ${d.rule} @ ${d.file}:${d.line}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (suppressions.reasonless.length > 0) {
|
|
179
|
+
console.error(chalk.red(` ā ${suppressions.reasonless.length} missing a required reason:`));
|
|
180
|
+
for (const d of suppressions.reasonless) {
|
|
181
|
+
console.error(` ${d.rule} @ ${d.file}:${d.line}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
137
185
|
// Per-analyzer activity (read from result data, not serialized summary)
|
|
138
186
|
// ā surfaces zero-scan failures that would otherwise be invisible.
|
|
139
187
|
if (!options.json) {
|
|
@@ -154,6 +202,39 @@ program
|
|
|
154
202
|
console.log(chalk.gray(`\nāā Pipeline Stages āāāāāāāāāāāāāāāāāāāāāāāāāā`));
|
|
155
203
|
console.log(analyzerFiles.join('\n'));
|
|
156
204
|
}
|
|
205
|
+
// āā Coverage (Spec 27) āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
206
|
+
const coverage = result.metadata?.coverage;
|
|
207
|
+
if (coverage && coverage.length > 0) {
|
|
208
|
+
const fired = coverage.filter(c => c.state === 'fired');
|
|
209
|
+
const clean = coverage.filter(c => c.state === 'clean');
|
|
210
|
+
const unassessed = coverage.filter(c => c.state === 'unassessed');
|
|
211
|
+
const notApplicable = coverage.filter(c => c.state === 'notApplicable');
|
|
212
|
+
const firedCount = fired.reduce((s, c) => s + c.count, 0);
|
|
213
|
+
console.log(chalk.gray(`\nāā Coverage āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`));
|
|
214
|
+
console.log(` ${fired.length} fired (${firedCount.toLocaleString()} violations), ` +
|
|
215
|
+
`${clean.length} clean, ` +
|
|
216
|
+
`${unassessed.length} unassessed, ` +
|
|
217
|
+
`${notApplicable.length} notApplicable ` +
|
|
218
|
+
`(${coverage.length} rules registered)`);
|
|
219
|
+
if (notApplicable.length > 0) {
|
|
220
|
+
console.log(chalk.gray(` āā Not Applicable āā`));
|
|
221
|
+
for (const c of notApplicable) {
|
|
222
|
+
console.log(` ${c.ruleId}: ${c.reason ?? 'unknown'}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (clean.length > 0) {
|
|
226
|
+
console.log(chalk.gray(` āā Clean āā`));
|
|
227
|
+
for (const c of clean) {
|
|
228
|
+
console.log(` ${c.ruleId} (0)`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (unassessed.length > 0) {
|
|
232
|
+
console.log(chalk.gray(` āā Unassessed (zero findings, applicability unknown) āā`));
|
|
233
|
+
for (const c of unassessed) {
|
|
234
|
+
console.log(` ${c.ruleId} (0)`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
157
238
|
}
|
|
158
239
|
// Spec-20 R4: built-in profile visibility ā silent behavior changes
|
|
159
240
|
// are never acceptable. Notify when scripts-and-tests capped findings.
|
|
@@ -175,6 +256,7 @@ program
|
|
|
175
256
|
const outputDir = options.output || process.cwd();
|
|
176
257
|
const ext = options.format === 'sarif' ? 'sarif' : options.format;
|
|
177
258
|
const reportPath = join(outputDir, `audit-report.${ext}`);
|
|
259
|
+
await fs.mkdir(dirname(reportPath), { recursive: true });
|
|
178
260
|
await fs.writeFile(reportPath, report, 'utf-8');
|
|
179
261
|
console.log(chalk.green(`\nReport written to ${reportPath}`));
|
|
180
262
|
}
|
|
@@ -199,6 +281,16 @@ program
|
|
|
199
281
|
process.exit(2);
|
|
200
282
|
}
|
|
201
283
|
}
|
|
284
|
+
// Spec 32 ā a run where any file failed to parse is incomplete and must
|
|
285
|
+
// not exit 0. This mirrors the zero-files gate: a silent dark-analyzer
|
|
286
|
+
// failure at file granularity is a hard failure.
|
|
287
|
+
{
|
|
288
|
+
const unparsedFiles = result.metadata?.unparsedFiles ?? [];
|
|
289
|
+
if (unparsedFiles.length > 0) {
|
|
290
|
+
console.error(`Parse-failure: ${unparsedFiles.length} file(s) could not be parsed; analysis is incomplete.`);
|
|
291
|
+
process.exit(2);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
202
294
|
// --fail-on: evaluate new + invariant findings only (unless --include-baseline)
|
|
203
295
|
if (failOnSeverity) {
|
|
204
296
|
const evaluableViolations = (baseline && !options.includeBaseline)
|
|
@@ -227,8 +319,6 @@ program
|
|
|
227
319
|
.option('--json', 'Output violations as machine-readable JSON to stdout')
|
|
228
320
|
.option('-f, --format <format>', 'Report format: json, or sarif (overrides --json)')
|
|
229
321
|
.option('--quiet', 'Suppress output when zero violations')
|
|
230
|
-
.option('--fail-on <severity>', 'Exit code 2 when violations at or above this severity exist', 'critical')
|
|
231
|
-
.option('--include-baseline', 'Evaluate baseline-known violations in --fail-on checks')
|
|
232
322
|
.option('--fail-on-zero-files', 'Exit code 2 when any enabled analyzer matches zero source files', true)
|
|
233
323
|
.option('--stdin', 'Read file paths from stdin (one per line)')
|
|
234
324
|
.option('-p, --path <projectPath>', 'Project root path', process.cwd())
|
|
@@ -260,13 +350,6 @@ program
|
|
|
260
350
|
const resolved = [...fileSet].map((f) => isAbsolute(f) ? f : resolve(process.cwd(), f));
|
|
261
351
|
scope = resolved;
|
|
262
352
|
}
|
|
263
|
-
// Validate --fail-on severity
|
|
264
|
-
const validSeverities = ['critical', 'warning', 'suggestion'];
|
|
265
|
-
const failOnSeverity = options.failOn;
|
|
266
|
-
if (!validSeverities.includes(failOnSeverity)) {
|
|
267
|
-
console.error(chalk.red(`Invalid --fail-on severity: "${failOnSeverity}". Must be one of: ${validSeverities.join(', ')}`));
|
|
268
|
-
process.exit(1);
|
|
269
|
-
}
|
|
270
353
|
// Create runner
|
|
271
354
|
const runner = createAuditRunner({
|
|
272
355
|
projectRoot: options.path,
|
|
@@ -276,11 +359,6 @@ program
|
|
|
276
359
|
const result = await runner.run();
|
|
277
360
|
// Collect all violations
|
|
278
361
|
const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
|
|
279
|
-
// Baseline classification (Spec 18 R4 ā hook path)
|
|
280
|
-
const baseline = result.metadata?.baseline;
|
|
281
|
-
const knownViolations = violations.filter((v) => v.new === false);
|
|
282
|
-
const newViolations = violations.filter((v) => v.new === true || v.new === undefined);
|
|
283
|
-
const knownCount = knownViolations.length;
|
|
284
362
|
// JSON output
|
|
285
363
|
if (options.format === 'sarif') {
|
|
286
364
|
const { generateSARIFReport } = await import('./reporting/sarifReportGenerator.js');
|
|
@@ -320,18 +398,13 @@ program
|
|
|
320
398
|
process.stdout.write(JSON.stringify(jsonOutput, null, 2) + '\n');
|
|
321
399
|
}
|
|
322
400
|
else if (!options.quiet || violations.length > 0) {
|
|
323
|
-
// Console output
|
|
401
|
+
// Console output ā Spec 36 R3: agent-facing output emits findings,
|
|
402
|
+
// never an aggregate total. The hook and the `changed` command are the
|
|
403
|
+
// agent's surface; a finding total is exactly the representation the
|
|
404
|
+
// consumer routes around. Findings only, each with file:line.
|
|
324
405
|
console.log(chalk.blue('š Diff-Scoped Code Audit'));
|
|
325
406
|
console.log(chalk.gray('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
326
407
|
console.log(chalk.gray(`Files analyzed: ${result.metadata.filesAnalyzed}`));
|
|
327
|
-
console.log(`\nFound ${violations.length} violations`);
|
|
328
|
-
console.log(`Critical: ${result.summary.criticalIssues}`);
|
|
329
|
-
console.log(`Warnings: ${result.summary.warnings}`);
|
|
330
|
-
console.log(`Suggestions: ${result.summary.suggestions}`);
|
|
331
|
-
// Known-finding informational line (Spec 18 R4)
|
|
332
|
-
if (baseline && knownCount > 0) {
|
|
333
|
-
console.log(chalk.gray(`\nā¹ļø ${knownCount} pre-existing finding${knownCount !== 1 ? 's' : ''} in files you touched (not blocking).`));
|
|
334
|
-
}
|
|
335
408
|
if (violations.length > 0) {
|
|
336
409
|
console.log(chalk.gray('\nāā Violations āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
337
410
|
for (const v of violations) {
|
|
@@ -343,19 +416,80 @@ program
|
|
|
343
416
|
console.log(`${icon} ${chalk.bold(v.file)}${v.line ? `:${v.line}` : ''} [${v.severity}] ${v.message}${statusTag}`);
|
|
344
417
|
}
|
|
345
418
|
}
|
|
419
|
+
else {
|
|
420
|
+
console.log(chalk.green('\nā No findings.'));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// Spec 38 R2/R3 ā per-rule timing + gate wall-clock, opt-in by env var.
|
|
424
|
+
// Emitted to stderr so it never corrupts --json stdout. Slowest rule first.
|
|
425
|
+
if (process.env.CODE_AUDIT_RULE_TIMING === '1') {
|
|
426
|
+
const ruleTiming = result.metadata?.ruleTiming;
|
|
427
|
+
const gateMs = result.metadata?.auditDuration;
|
|
428
|
+
const lines = [];
|
|
429
|
+
lines.push('');
|
|
430
|
+
lines.push(`ā± gate wall-clock: ${(gateMs ?? 0).toFixed(1)} ms (budget 300 ms)`);
|
|
431
|
+
lines.push('āā per-rule timing (slowest first) āā');
|
|
432
|
+
if (ruleTiming && ruleTiming.length > 0) {
|
|
433
|
+
for (const t of ruleTiming) {
|
|
434
|
+
lines.push(` ${t.ruleId.padEnd(28)} ${t.totalMs.toFixed(2).padStart(9)} ms Ć${t.calls}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
lines.push(' (no gating rules recorded)');
|
|
439
|
+
}
|
|
440
|
+
process.stderr.write(lines.join('\n') + '\n');
|
|
346
441
|
}
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
442
|
+
// Binary gate (Spec 36 R2/R4/R6): exit 2 when a gating finding is present.
|
|
443
|
+
// The `changed` command is the edit-boundary gate, so it compares against
|
|
444
|
+
// the file's prior state (git HEAD) via the touched-line diff gate (R2),
|
|
445
|
+
// NOT against a stored baseline. Gate-excluded files and non-gating rules
|
|
446
|
+
// never block. Severity does NOT factor in (R4). A gating rule that cannot
|
|
447
|
+
// name a next action for an occurrence emits non-blocking and the gap is
|
|
448
|
+
// recorded (R6), surfaced below to stderr.
|
|
449
|
+
{
|
|
450
|
+
const analyzedFiles = result.metadata?.analyzedFiles;
|
|
451
|
+
// The `changed` command is always scoped, so the runner records the
|
|
452
|
+
// analyzed file list. If it is somehow absent we still derive the gate
|
|
453
|
+
// from the files that actually produced findings rather than falling
|
|
454
|
+
// back to the baseline gate ā a silent soft-fail is the one outcome R1
|
|
455
|
+
// forbids.
|
|
456
|
+
const gateFiles = analyzedFiles && analyzedFiles.length > 0
|
|
457
|
+
? analyzedFiles
|
|
458
|
+
: [...new Set(violations.map((v) => v.file).filter(Boolean))];
|
|
459
|
+
const diffGate = computeDiffGate(options.path, gateFiles);
|
|
460
|
+
const { blocking, resolutionGaps } = computeDiffGatingDecision(violations, diffGate);
|
|
461
|
+
if (resolutionGaps.length > 0) {
|
|
462
|
+
// Spec 36 R6 ā a gating rule that could not name an action is a defect
|
|
463
|
+
// in the rule. Record it loudly so it cannot route around as a count.
|
|
464
|
+
const lines = [
|
|
465
|
+
'ā ļø resolution gap ā gating rule produced no next action for these occurrences (Spec 36 R6):',
|
|
466
|
+
...resolutionGaps.map((g) => ` - ${g.rule} @ ${g.file}${g.line ? `:${g.line}` : ''}`),
|
|
467
|
+
];
|
|
468
|
+
process.stderr.write(lines.join('\n') + '\n');
|
|
469
|
+
}
|
|
470
|
+
if (blocking.length > 0) {
|
|
471
|
+
process.exit(2);
|
|
472
|
+
}
|
|
473
|
+
// Spec 36 R7 ā an unnecessary or reasonless suppression is itself an
|
|
474
|
+
// error. It is reported loudly and fails the write, exactly like an
|
|
475
|
+
// unused `@ts-expect-error`. A suppression that outlives its finding is
|
|
476
|
+
// a baseline entry with better branding unless it errors here.
|
|
477
|
+
const suppressions = result.metadata?.suppressions;
|
|
478
|
+
if (suppressions && (suppressions.unnecessary.length > 0 || suppressions.reasonless.length > 0)) {
|
|
479
|
+
const lines = [];
|
|
480
|
+
if (suppressions.reasonless.length > 0) {
|
|
481
|
+
lines.push('ā suppression missing a required reason (Spec 36 R7):');
|
|
482
|
+
for (const d of suppressions.reasonless) {
|
|
483
|
+
lines.push(` - ${d.rule} @ ${d.file}:${d.line}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (suppressions.unnecessary.length > 0) {
|
|
487
|
+
lines.push('ā unnecessary suppression ā the finding no longer fires (Spec 36 R7):');
|
|
488
|
+
for (const d of suppressions.unnecessary) {
|
|
489
|
+
lines.push(` - ${d.rule} @ ${d.file}:${d.line}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
process.stderr.write(lines.join('\n') + '\n');
|
|
359
493
|
process.exit(2);
|
|
360
494
|
}
|
|
361
495
|
}
|
|
@@ -786,7 +920,7 @@ configCmd
|
|
|
786
920
|
file: options.file,
|
|
787
921
|
resolvedFilePath: filePath,
|
|
788
922
|
matchedProfileNames: resolved.matchedProfileNames,
|
|
789
|
-
|
|
923
|
+
excludeFromGate: resolved.excludeFromGate || null,
|
|
790
924
|
overrides: resolved.overrides,
|
|
791
925
|
}, null, 2) + '\n');
|
|
792
926
|
}
|
|
@@ -801,8 +935,8 @@ configCmd
|
|
|
801
935
|
const builtinTag = profile?.builtin !== false && profiles.some(p => p.name === name) ? '' : '';
|
|
802
936
|
console.log(` ${chalk.cyan(name)}${builtinTag ? chalk.gray(' (built-in)') : ''}`);
|
|
803
937
|
}
|
|
804
|
-
if (resolved.
|
|
805
|
-
console.log(chalk.yellow(`
|
|
938
|
+
if (resolved.excludeFromGate) {
|
|
939
|
+
console.log(chalk.yellow(` excludeFromGate: true`));
|
|
806
940
|
}
|
|
807
941
|
if (Object.keys(resolved.overrides).length > 0) {
|
|
808
942
|
console.log(chalk.gray(' merged overrides:'));
|
|
@@ -2084,6 +2218,180 @@ coverageCmd
|
|
|
2084
2218
|
process.exit(1);
|
|
2085
2219
|
}
|
|
2086
2220
|
});
|
|
2221
|
+
// print-config command ā effective config with source attribution (Spec 38 R1)
|
|
2222
|
+
program
|
|
2223
|
+
.command('print-config <file>')
|
|
2224
|
+
.description('Print the effective config for a file, naming the source of every value')
|
|
2225
|
+
.option('-p, --path <path>', 'Project path', process.cwd())
|
|
2226
|
+
.option('--json', 'Output as JSON')
|
|
2227
|
+
.option('--preset <id>', 'Apply a shareable preset (repeatable)', (v, prev) => prev.concat([v]), [])
|
|
2228
|
+
.action(async (file, options) => {
|
|
2229
|
+
try {
|
|
2230
|
+
const pathModule = await import('path');
|
|
2231
|
+
const fs = await import('fs/promises');
|
|
2232
|
+
const { loadConfig } = await import('./config/configLoader.js');
|
|
2233
|
+
const { getDefaultConfig, DEFAULT_CODE_INDEX_CONFIG } = await import('./config/defaults.js');
|
|
2234
|
+
const { computeEffectiveConfig, computeTopLevelConfig, } = await import('./config/effectiveConfig.js');
|
|
2235
|
+
const { getPreset, PRESET_IDS } = await import('./presets/presets.js');
|
|
2236
|
+
const projectRoot = pathModule.resolve(options.path);
|
|
2237
|
+
const filePath = pathModule.resolve(file);
|
|
2238
|
+
const configPath = pathModule.join(projectRoot, '.codeauditor.json');
|
|
2239
|
+
// Pristine defaults, normalized against projectRoot the same way loadConfig
|
|
2240
|
+
// normalizes the real config, so path keys compare cleanly instead of
|
|
2241
|
+
// reading as "user-set" because one side is absolute and the other is not.
|
|
2242
|
+
const pristine = { ...getDefaultConfig(), codeIndex: { ...DEFAULT_CODE_INDEX_CONFIG } };
|
|
2243
|
+
if (pristine.outputDirectory) {
|
|
2244
|
+
pristine.outputDirectory = pathModule.resolve(projectRoot, pristine.outputDirectory);
|
|
2245
|
+
}
|
|
2246
|
+
pristine.includePaths = (pristine.includePaths ?? []).map((p) => pathModule.isAbsolute(p) ? p : pathModule.resolve(projectRoot, p));
|
|
2247
|
+
pristine.excludePaths = (pristine.excludePaths ?? []).map((p) => pathModule.isAbsolute(p) ? p : pathModule.resolve(projectRoot, p));
|
|
2248
|
+
// Load the project config (defaults when no config file exists).
|
|
2249
|
+
let config;
|
|
2250
|
+
try {
|
|
2251
|
+
await fs.access(configPath);
|
|
2252
|
+
config = await loadConfig({ configPath });
|
|
2253
|
+
}
|
|
2254
|
+
catch {
|
|
2255
|
+
config = pristine;
|
|
2256
|
+
}
|
|
2257
|
+
const analyzerConfigs = (config.analyzerConfigs ?? {});
|
|
2258
|
+
const pathProfiles = (config.pathProfiles ?? []);
|
|
2259
|
+
const enabledAnalyzers = (config.enabledAnalyzers ?? []);
|
|
2260
|
+
// Resolve --preset ids to ordered preset objects (Spec 38 R4). Unknown
|
|
2261
|
+
// ids are a hard error, not a silent skip.
|
|
2262
|
+
const presetIds = Array.isArray(options.preset) ? options.preset : (options.preset ? [options.preset] : []);
|
|
2263
|
+
for (const id of presetIds) {
|
|
2264
|
+
if (!getPreset(id)) {
|
|
2265
|
+
console.error(chalk.red(`Error: unknown preset "${id}". Available: ${PRESET_IDS.join(', ')}`));
|
|
2266
|
+
process.exit(1);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
const presets = presetIds.map((id) => getPreset(id));
|
|
2270
|
+
const effective = computeEffectiveConfig({
|
|
2271
|
+
filePath,
|
|
2272
|
+
projectRoot,
|
|
2273
|
+
analyzerConfigs,
|
|
2274
|
+
pathProfiles,
|
|
2275
|
+
enabledAnalyzers,
|
|
2276
|
+
presets,
|
|
2277
|
+
});
|
|
2278
|
+
const topLevel = computeTopLevelConfig(pristine, config);
|
|
2279
|
+
if (options.json) {
|
|
2280
|
+
process.stdout.write(JSON.stringify({
|
|
2281
|
+
file: filePath,
|
|
2282
|
+
projectRoot,
|
|
2283
|
+
configPath,
|
|
2284
|
+
presets: presetIds,
|
|
2285
|
+
matchedProfiles: effective.matchedProfiles,
|
|
2286
|
+
excludeFromGate: effective.excludeFromGate ?? null,
|
|
2287
|
+
topLevel,
|
|
2288
|
+
analyzers: effective.analyzers,
|
|
2289
|
+
}, null, 2) + '\n');
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
console.log(chalk.blue('š§ Effective Config'));
|
|
2293
|
+
console.log(chalk.gray('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
2294
|
+
console.log(`${chalk.bold('file:')} ${effective.relativePath}`);
|
|
2295
|
+
console.log(`${chalk.bold('project root:')} ${projectRoot}`);
|
|
2296
|
+
console.log(`${chalk.bold('presets:')} ${presetIds.length > 0 ? presetIds.join(', ') : chalk.gray('(none)')}`);
|
|
2297
|
+
console.log(`${chalk.bold('profiles:')} ${effective.matchedProfiles.length > 0 ? effective.matchedProfiles.join(', ') : chalk.gray('(none)')}`);
|
|
2298
|
+
if (effective.excludeFromGate) {
|
|
2299
|
+
console.log(`${chalk.bold('excludeFromGate:')} ${chalk.yellow('true')}`);
|
|
2300
|
+
}
|
|
2301
|
+
// Top-level keys
|
|
2302
|
+
console.log(chalk.bold('\nāā Top-level āā'));
|
|
2303
|
+
for (const key of topLevel) {
|
|
2304
|
+
const flag = key.differsFromDefault ? chalk.yellow(' [differs from default]') : '';
|
|
2305
|
+
console.log(`${key.key.padEnd(24)} ${chalk.dim(JSON.stringify(key.value))} ${chalk.cyan(key.source)}${flag}`);
|
|
2306
|
+
}
|
|
2307
|
+
// Per-analyzer keys
|
|
2308
|
+
for (const analyzer of effective.analyzers) {
|
|
2309
|
+
console.log(chalk.bold(`\nāā ${analyzer.namespace} āā`));
|
|
2310
|
+
for (const key of analyzer.keys) {
|
|
2311
|
+
const flag = key.differsFromDefault
|
|
2312
|
+
? chalk.yellow(` [differs from default: ${JSON.stringify(key.defaultValue)}]`)
|
|
2313
|
+
: '';
|
|
2314
|
+
console.log(`${key.key.padEnd(32)} ${chalk.dim(JSON.stringify(key.value))} ${chalk.cyan(key.source)}${flag}`);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
catch (error) {
|
|
2319
|
+
console.error(chalk.red('Error:'), error);
|
|
2320
|
+
process.exit(1);
|
|
2321
|
+
}
|
|
2322
|
+
});
|
|
2323
|
+
// Outstanding command (Spec 36 R8) ā "what is outstanding" is re-derived from
|
|
2324
|
+
// the codebase on every invocation, never read from a stored list. The answer a
|
|
2325
|
+
// compacted agent gets is identical to an uncompacted one because the only input
|
|
2326
|
+
// is the code itself: run the audit, list the findings. No progress file, no
|
|
2327
|
+
// tracked count, no state that can drift from the code.
|
|
2328
|
+
program
|
|
2329
|
+
.command('outstanding')
|
|
2330
|
+
.description('List outstanding findings, re-derived from the codebase (never a stored list)')
|
|
2331
|
+
.option('-p, --path <path>', 'Path to audit', process.cwd())
|
|
2332
|
+
.option('--json', 'Output findings as machine-readable JSON')
|
|
2333
|
+
.action(async (options) => {
|
|
2334
|
+
try {
|
|
2335
|
+
await initParsers();
|
|
2336
|
+
const runner = createAuditRunner({ projectRoot: options.path });
|
|
2337
|
+
const result = await runner.run();
|
|
2338
|
+
const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
|
|
2339
|
+
// Deterministic order ā same answer every run, independent of analyzer
|
|
2340
|
+
// registration order or any prior agent state. Sort by file, then line,
|
|
2341
|
+
// then rule. This is what makes a compacted and uncompacted agent agree.
|
|
2342
|
+
const sorted = [...violations].sort((a, b) => {
|
|
2343
|
+
const fa = a.file ?? '';
|
|
2344
|
+
const fb = b.file ?? '';
|
|
2345
|
+
if (fa !== fb)
|
|
2346
|
+
return fa < fb ? -1 : 1;
|
|
2347
|
+
const la = a.line ?? 0;
|
|
2348
|
+
const lb = b.line ?? 0;
|
|
2349
|
+
if (la !== lb)
|
|
2350
|
+
return la - lb;
|
|
2351
|
+
const ra = a.rule ?? '';
|
|
2352
|
+
const rb = b.rule ?? '';
|
|
2353
|
+
if (ra !== rb)
|
|
2354
|
+
return ra < rb ? -1 : 1;
|
|
2355
|
+
return 0;
|
|
2356
|
+
});
|
|
2357
|
+
if (options.json) {
|
|
2358
|
+
const projectDir = resolve(options.path || process.cwd());
|
|
2359
|
+
const out = sorted.map((v) => {
|
|
2360
|
+
let filePath = v.file || '';
|
|
2361
|
+
if (filePath.startsWith('/')) {
|
|
2362
|
+
const rel = relative(projectDir, filePath);
|
|
2363
|
+
if (!rel.startsWith('..') && !isAbsolute(rel))
|
|
2364
|
+
filePath = rel;
|
|
2365
|
+
}
|
|
2366
|
+
return {
|
|
2367
|
+
rule: v.rule || v.type || '',
|
|
2368
|
+
analyzer: v.analyzer || '',
|
|
2369
|
+
severity: v.severity,
|
|
2370
|
+
file: filePath,
|
|
2371
|
+
line: v.line ?? v.start?.line,
|
|
2372
|
+
message: v.message,
|
|
2373
|
+
resolution: v.resolution ?? null,
|
|
2374
|
+
suppressed: v.suppressed === true,
|
|
2375
|
+
...(v.suppressionReason && { suppressionReason: v.suppressionReason }),
|
|
2376
|
+
};
|
|
2377
|
+
});
|
|
2378
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
// Spec 36 R3 ā findings, never a total. Each line is a file:line finding;
|
|
2382
|
+
// there is no count anywhere in this output to route around.
|
|
2383
|
+
for (const v of sorted) {
|
|
2384
|
+
const icon = v.severity === 'critical' ? 'š“' :
|
|
2385
|
+
v.severity === 'warning' ? 'š”' : 'šµ';
|
|
2386
|
+
const suppressed = v.suppressed ? chalk.dim(' [suppressed]') : '';
|
|
2387
|
+
console.log(`${icon} ${chalk.bold(v.file)}${v.line ? `:${v.line}` : ''} [${v.rule || v.severity}] ${v.message}${suppressed}`);
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
catch (error) {
|
|
2391
|
+
console.error(chalk.red('Error:'), error);
|
|
2392
|
+
process.exit(1);
|
|
2393
|
+
}
|
|
2394
|
+
});
|
|
2087
2395
|
/**
|
|
2088
2396
|
* Render a simple ASCII bar chart.
|
|
2089
2397
|
*/
|
|
@@ -2247,7 +2555,7 @@ async function generateConfigurations(options) {
|
|
|
2247
2555
|
console.log(chalk.blue('\nNext steps:'));
|
|
2248
2556
|
console.log(chalk.gray(' 1. Edit .codeauditor.json to match your codebase conventions'));
|
|
2249
2557
|
console.log(chalk.gray(' 2. Run ') + chalk.cyan('code-audit') + chalk.gray(' to enforce your rules'));
|
|
2250
|
-
console.log(chalk.gray(' 3. Use ') + chalk.cyan('code-audit changed
|
|
2558
|
+
console.log(chalk.gray(' 3. Use ') + chalk.cyan('code-audit changed') + chalk.gray(' in your agent hook (gating rules block on new findings)'));
|
|
2251
2559
|
}
|
|
2252
2560
|
/**
|
|
2253
2561
|
* Interactive rule builder ā walks the user through adding rules one at a time.
|