code-auditor-mcp 3.9.6 ā 3.9.9
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 +106 -0
- package/README.md +18 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +4 -1
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +8 -0
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +20 -10
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +23 -20
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/auditRouter.d.ts.map +1 -1
- package/dist/auditRouter.js +6 -1
- package/dist/auditRouter.js.map +1 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +53 -1
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.js +119 -4
- package/dist/cli.js.map +1 -1
- package/dist/config/defaults.d.ts +1 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +14 -6
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +22 -1
- package/dist/config/effectiveConfig.d.ts.map +1 -1
- package/dist/config/effectiveConfig.js +69 -2
- package/dist/config/effectiveConfig.js.map +1 -1
- package/dist/config/lintConfigReader.d.ts +56 -0
- package/dist/config/lintConfigReader.d.ts.map +1 -0
- package/dist/config/lintConfigReader.js +263 -0
- package/dist/config/lintConfigReader.js.map +1 -0
- package/dist/dataPaths.d.ts +8 -0
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +21 -0
- package/dist/dataPaths.js.map +1 -1
- package/dist/dismissals.d.ts +59 -0
- package/dist/dismissals.d.ts.map +1 -0
- package/dist/dismissals.js +128 -0
- package/dist/dismissals.js.map +1 -0
- package/dist/enforcement/gate.d.ts.map +1 -1
- package/dist/enforcement/gate.js +5 -0
- package/dist/enforcement/gate.js.map +1 -1
- package/dist/installConfig.d.ts +40 -0
- package/dist/installConfig.d.ts.map +1 -0
- package/dist/installConfig.js +84 -0
- package/dist/installConfig.js.map +1 -0
- package/dist/languages/go/analyzer +0 -0
- package/dist/languages/go/analyzer-src/solid.go +14 -6
- package/dist/mcp.js +54 -0
- package/dist/mcp.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +1 -0
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +5 -0
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +6 -0
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +3 -0
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/structuralSignature.d.ts +41 -0
- package/dist/structuralSignature.d.ts.map +1 -0
- package/dist/structuralSignature.js +93 -0
- package/dist/structuralSignature.js.map +1 -0
- package/dist/telemetry.d.ts +80 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +148 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/code-auditor/SKILL.md +24 -2
- package/server.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,10 @@ 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 { fingerprint, buildFingerprintInput } from './fingerprint.js';
|
|
24
|
+
import { buildDismissalEntry, upsertDismissal } from './dismissals.js';
|
|
25
|
+
import { resolveTelemetryConfig, signatureForFinding, buildTelemetryPayload, formatTelemetryPreview, sendTelemetry, languageHint } from './telemetry.js';
|
|
26
|
+
import { getInstallId } from './installConfig.js';
|
|
23
27
|
import { ALL_ANALYZERS } from './analyzers/ruleRegistry.js';
|
|
24
28
|
import { computeGatingDecision } from './enforcement/gate.js';
|
|
25
29
|
import { BLOCKING_SEVERITIES } from './types.js';
|
|
@@ -139,6 +143,10 @@ program
|
|
|
139
143
|
});
|
|
140
144
|
const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
|
|
141
145
|
const baseline = result.metadata?.baseline;
|
|
146
|
+
// Spec 57 ā dismissed count is reported alongside the total, never
|
|
147
|
+
// subtracted from it ("43 findings, 3 dismissed").
|
|
148
|
+
const dismissedCount = result.summary.dismissed ?? 0;
|
|
149
|
+
const dismissedSuffix = dismissedCount > 0 ? `, ${dismissedCount} dismissed` : '';
|
|
142
150
|
// āā Coverage panel leads the report (Spec 47 R2) āāāāāāāāāāāāā
|
|
143
151
|
// A diagnostic report opens with what was measured before it lists any
|
|
144
152
|
// readings, so a zero-reading report can't be mistaken for a clean tree.
|
|
@@ -215,7 +223,7 @@ program
|
|
|
215
223
|
}
|
|
216
224
|
else if (!baseline) {
|
|
217
225
|
// No baseline: current behavior + hint
|
|
218
|
-
console.log(`\nFound ${result.summary.totalViolations}
|
|
226
|
+
console.log(`\nFound ${result.summary.totalViolations} findings${dismissedSuffix}`);
|
|
219
227
|
console.log(`Critical: ${result.summary.criticalIssues}`);
|
|
220
228
|
console.log(`Severe: ${result.summary.severe}`);
|
|
221
229
|
console.log(`High: ${result.summary.high}`);
|
|
@@ -224,7 +232,7 @@ program
|
|
|
224
232
|
}
|
|
225
233
|
else {
|
|
226
234
|
// --full with baseline: full itemized inventory (current behavior)
|
|
227
|
-
console.log(`\nFound ${result.summary.totalViolations}
|
|
235
|
+
console.log(`\nFound ${result.summary.totalViolations} findings${dismissedSuffix}`);
|
|
228
236
|
console.log(`Critical: ${result.summary.criticalIssues}`);
|
|
229
237
|
console.log(`Severe: ${result.summary.severe}`);
|
|
230
238
|
console.log(`High: ${result.summary.high}`);
|
|
@@ -381,6 +389,9 @@ program
|
|
|
381
389
|
const severityOrder = ['critical', 'severe', 'high'];
|
|
382
390
|
const failIndex = severityOrder.indexOf(failOnSeverity);
|
|
383
391
|
const hasAtOrAbove = evaluableViolations.some((v) => {
|
|
392
|
+
// Spec 57 ā a dismissed finding never blocks the gate.
|
|
393
|
+
if (v.dismissed)
|
|
394
|
+
return false;
|
|
384
395
|
const vIndex = severityOrder.indexOf(v.severity);
|
|
385
396
|
return vIndex >= 0 && vIndex <= failIndex;
|
|
386
397
|
});
|
|
@@ -466,6 +477,10 @@ program
|
|
|
466
477
|
}
|
|
467
478
|
// Collect all violations
|
|
468
479
|
const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
|
|
480
|
+
// Spec 57 ā dismissed count is reported alongside the total, never
|
|
481
|
+
// subtracted from it. The hook surface (`changed`) is agent-facing, so it
|
|
482
|
+
// must show the same "N findings, M dismissed" figure as the full report.
|
|
483
|
+
const dismissedCount = result.summary.dismissed ?? 0;
|
|
469
484
|
// Blocking gate decision (Spec 45 R1/R4, Spec 54 R3), computed once so the
|
|
470
485
|
// agent-facing before/after count (Spec 45 A2) and the exit code agree on
|
|
471
486
|
// the same number. Every registered rule participates (R1); enforcement is
|
|
@@ -506,7 +521,9 @@ program
|
|
|
506
521
|
enclosingSymbol: v.symbol || v.enclosingFunction || '',
|
|
507
522
|
suggestion: v.suggestion || '',
|
|
508
523
|
details: v.details || '',
|
|
509
|
-
...(v.new !== undefined && { new: v.new })
|
|
524
|
+
...(v.new !== undefined && { new: v.new }),
|
|
525
|
+
...(v.dismissed !== undefined && { dismissed: v.dismissed }),
|
|
526
|
+
fingerprint: fingerprint(buildFingerprintInput(v))
|
|
510
527
|
};
|
|
511
528
|
});
|
|
512
529
|
process.stdout.write(JSON.stringify(jsonOutput, null, 2) + '\n');
|
|
@@ -522,6 +539,9 @@ program
|
|
|
522
539
|
console.log('');
|
|
523
540
|
printCountSummary(violations);
|
|
524
541
|
console.log(chalk.bold(`gate before/after: ${violations.length} ā ${blocking.length} blocking`));
|
|
542
|
+
if (dismissedCount > 0) {
|
|
543
|
+
console.log(chalk.dim(` ${dismissedCount} dismissed ā still counted above, excluded from the gate`));
|
|
544
|
+
}
|
|
525
545
|
console.log(chalk.gray('āā Violations āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
526
546
|
for (const v of violations) {
|
|
527
547
|
const icon = v.severity === 'critical' ? 'š“' :
|
|
@@ -529,7 +549,10 @@ program
|
|
|
529
549
|
const statusTag = v.new === false
|
|
530
550
|
? chalk.dim(' [known ā still open]')
|
|
531
551
|
: '';
|
|
532
|
-
|
|
552
|
+
const dismissedTag = v.dismissed
|
|
553
|
+
? chalk.dim(' [dismissed]')
|
|
554
|
+
: '';
|
|
555
|
+
console.log(`${icon} ${chalk.bold(v.file)}${v.line ? `:${v.line}` : ''} [${v.severity}] ${v.message}${statusTag}${dismissedTag}`);
|
|
533
556
|
}
|
|
534
557
|
}
|
|
535
558
|
else {
|
|
@@ -947,6 +970,90 @@ program
|
|
|
947
970
|
process.exit(1);
|
|
948
971
|
}
|
|
949
972
|
});
|
|
973
|
+
// Dismiss command (Spec 57)
|
|
974
|
+
program
|
|
975
|
+
.command('dismiss <fingerprint>')
|
|
976
|
+
.description('Dismiss one finding by fingerprint ā a written reason is required')
|
|
977
|
+
.requiredOption('--reason <reason>', 'Why this finding is dismissed (required ā a dismissal without a reason is a config error)')
|
|
978
|
+
.option('-p, --path <path>', 'Project path', process.cwd())
|
|
979
|
+
.option('--telemetry-endpoint <url>', 'Feedback service endpoint override (defaults to the opt-in endpoint recorded by the telemetry tool)')
|
|
980
|
+
.option('--json', 'Output as JSON')
|
|
981
|
+
.action(async (fingerprintArg, options) => {
|
|
982
|
+
try {
|
|
983
|
+
const reason = (options.reason ?? '').trim();
|
|
984
|
+
if (!reason) {
|
|
985
|
+
console.error(chalk.red('Error: --reason is required and must be non-empty. A dismissal without a reason is a config error, not a suppression.'));
|
|
986
|
+
process.exit(1);
|
|
987
|
+
}
|
|
988
|
+
const projectRoot = resolve(options.path || process.cwd());
|
|
989
|
+
await initParsers();
|
|
990
|
+
// Re-audit so the fingerprint resolves against the live finding. The
|
|
991
|
+
// fingerprint is a content hash of rule+file+symbol, so it is stable
|
|
992
|
+
// across runs on the same machine (same tuple the baseline uses).
|
|
993
|
+
const runner = createAuditRunner({ projectRoot });
|
|
994
|
+
const result = await runner.run();
|
|
995
|
+
const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
|
|
996
|
+
const match = violations.find((v) => fingerprint(buildFingerprintInput(v)) === fingerprintArg);
|
|
997
|
+
if (!match) {
|
|
998
|
+
console.error(chalk.red(`Error: no finding matches fingerprint "${fingerprintArg}".`));
|
|
999
|
+
console.error(chalk.gray(`The fingerprint is a content hash of rule+file+symbol. Get the exact value from `));
|
|
1000
|
+
console.error(chalk.gray(`the JSON report: run ${chalk.cyan('code-audit audit --format json')} (or ${chalk.cyan('code-audit changed --json')}) `));
|
|
1001
|
+
console.error(chalk.gray(`and copy the "fingerprint" field of the finding you want to dismiss.`));
|
|
1002
|
+
process.exit(1);
|
|
1003
|
+
}
|
|
1004
|
+
const entry = buildDismissalEntry(match, reason, packageJson.version);
|
|
1005
|
+
const dismissals = upsertDismissal(projectRoot, entry);
|
|
1006
|
+
// Spec 57 ā opt-in telemetry. Nothing is sent unless the user opted in via
|
|
1007
|
+
// the `telemetry` MCP tool (default OFF) AND an endpoint is configured. The
|
|
1008
|
+
// payload is printed for review before sending; any send failure is silent
|
|
1009
|
+
// and never affects the dismissal or the exit code.
|
|
1010
|
+
const telemetry = resolveTelemetryConfig({ endpoint: options.telemetryEndpoint });
|
|
1011
|
+
if (telemetry.enabled && telemetry.endpoint) {
|
|
1012
|
+
const signature = signatureForFinding(match.file, match.line ?? match.start?.line ?? 1, match.column ?? match.start?.column ?? 1) ?? 'unknown';
|
|
1013
|
+
const payload = buildTelemetryPayload({
|
|
1014
|
+
install_id: getInstallId(),
|
|
1015
|
+
toolVersion: packageJson.version,
|
|
1016
|
+
rule: entry.rule,
|
|
1017
|
+
level: match.severity,
|
|
1018
|
+
reason: entry.reason,
|
|
1019
|
+
signature,
|
|
1020
|
+
lang: languageHint(match.file),
|
|
1021
|
+
});
|
|
1022
|
+
process.stderr.write(formatTelemetryPreview(payload));
|
|
1023
|
+
const sendResult = await sendTelemetry(payload, telemetry.endpoint);
|
|
1024
|
+
if (options.json) {
|
|
1025
|
+
// Surface the send outcome in machine output without ever failing on it.
|
|
1026
|
+
process.stderr.write(` telemetry: ${sendResult.sent ? 'sent' : 'not sent'}${sendResult.error ? ` (${sendResult.error})` : ''}\n`);
|
|
1027
|
+
}
|
|
1028
|
+
else if (!sendResult.sent) {
|
|
1029
|
+
console.log(chalk.gray(` Telemetry not sent${sendResult.error ? ` (${sendResult.error})` : ''} ā offline is never an error.`));
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
console.log(chalk.gray(' Telemetry sent.'));
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
if (options.json) {
|
|
1036
|
+
process.stdout.write(JSON.stringify({
|
|
1037
|
+
success: true,
|
|
1038
|
+
fingerprint: entry.fingerprint,
|
|
1039
|
+
rule: entry.rule,
|
|
1040
|
+
file: entry.file,
|
|
1041
|
+
symbol: entry.symbol,
|
|
1042
|
+
reason: entry.reason,
|
|
1043
|
+
totalDismissed: dismissals.entries.length,
|
|
1044
|
+
}, null, 2) + '\n');
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
console.log(chalk.green(`\nā Dismissed ${chalk.bold(entry.rule)} @ ${entry.symbol || entry.file} ā "${entry.reason}"`));
|
|
1048
|
+
console.log(chalk.gray(` ${dismissals.entries.length} dismissal(s) recorded in .codeauditor.dismissals.json.`));
|
|
1049
|
+
console.log(chalk.gray(` The finding is still counted in every report, but excluded from the gate.`));
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
catch (error) {
|
|
1053
|
+
console.error(chalk.red('Error:'), error);
|
|
1054
|
+
process.exit(1);
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
950
1057
|
// Generate configuration command
|
|
951
1058
|
program
|
|
952
1059
|
.command('generate-config')
|
|
@@ -2903,6 +3010,7 @@ program
|
|
|
2903
3010
|
const { getDefaultConfig, DEFAULT_CODE_INDEX_CONFIG } = await import('./config/defaults.js');
|
|
2904
3011
|
const { computeEffectiveConfig, computeTopLevelConfig, } = await import('./config/effectiveConfig.js');
|
|
2905
3012
|
const { getPreset, PRESET_IDS } = await import('./presets/presets.js');
|
|
3013
|
+
const { readProjectLintThresholds, thresholdsToAnalyzerConfig } = await import('./config/lintConfigReader.js');
|
|
2906
3014
|
const projectRoot = pathModule.resolve(options.path);
|
|
2907
3015
|
const filePath = pathModule.resolve(file);
|
|
2908
3016
|
const configPath = pathModule.join(projectRoot, '.codeauditor.json');
|
|
@@ -2937,6 +3045,10 @@ program
|
|
|
2937
3045
|
}
|
|
2938
3046
|
}
|
|
2939
3047
|
const presets = presetIds.map((id) => getPreset(id));
|
|
3048
|
+
// Spec 50 R2 ā read the project's own lint config so size thresholds sourced
|
|
3049
|
+
// from ESLint are named `project-lint-config` rather than `default`.
|
|
3050
|
+
const lintResult = await readProjectLintThresholds(projectRoot);
|
|
3051
|
+
const lintConfig = lintResult ? thresholdsToAnalyzerConfig(lintResult.thresholds) : {};
|
|
2940
3052
|
const effective = computeEffectiveConfig({
|
|
2941
3053
|
filePath,
|
|
2942
3054
|
projectRoot,
|
|
@@ -2944,6 +3056,7 @@ program
|
|
|
2944
3056
|
pathProfiles,
|
|
2945
3057
|
enabledAnalyzers,
|
|
2946
3058
|
presets,
|
|
3059
|
+
lintConfig,
|
|
2947
3060
|
});
|
|
2948
3061
|
const topLevel = computeTopLevelConfig(pristine, config);
|
|
2949
3062
|
if (options.json) {
|
|
@@ -2952,6 +3065,7 @@ program
|
|
|
2952
3065
|
projectRoot,
|
|
2953
3066
|
configPath,
|
|
2954
3067
|
presets: presetIds,
|
|
3068
|
+
lintConfigPath: lintResult?.configPath ?? null,
|
|
2955
3069
|
matchedProfiles: effective.matchedProfiles,
|
|
2956
3070
|
excludeFromGate: effective.excludeFromGate ?? null,
|
|
2957
3071
|
topLevel,
|
|
@@ -2964,6 +3078,7 @@ program
|
|
|
2964
3078
|
console.log(`${chalk.bold('file:')} ${effective.relativePath}`);
|
|
2965
3079
|
console.log(`${chalk.bold('project root:')} ${projectRoot}`);
|
|
2966
3080
|
console.log(`${chalk.bold('presets:')} ${presetIds.length > 0 ? presetIds.join(', ') : chalk.gray('(none)')}`);
|
|
3081
|
+
console.log(`${chalk.bold('lint config:')} ${lintResult?.configPath ? chalk.cyan(lintResult.configKind) + ' ' + chalk.dim(lintResult.configPath) : chalk.gray('(none ā defaults)')}`);
|
|
2967
3082
|
console.log(`${chalk.bold('profiles:')} ${effective.matchedProfiles.length > 0 ? effective.matchedProfiles.join(', ') : chalk.gray('(none)')}`);
|
|
2968
3083
|
if (effective.excludeFromGate) {
|
|
2969
3084
|
console.log(`${chalk.bold('excludeFromGate:')} ${chalk.yellow('true')}`);
|