code-auditor-mcp 3.9.6 → 3.9.11

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.
Files changed (127) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +220 -0
  3. package/README.md +18 -0
  4. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  5. package/dist/analyzers/reactAnalyzer.js +4 -1
  6. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  7. package/dist/analyzers/ruleRegistry.d.ts +8 -0
  8. package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
  9. package/dist/analyzers/ruleRegistry.js +32 -10
  10. package/dist/analyzers/ruleRegistry.js.map +1 -1
  11. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  12. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +23 -20
  13. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  14. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +1 -0
  15. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  16. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +19 -5
  17. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  18. package/dist/analyzers/universal/schema/codeAnalysis.d.ts +44 -2
  19. package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
  20. package/dist/analyzers/universal/schema/codeAnalysis.js +153 -8
  21. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
  22. package/dist/analyzers/universal/schema/config.d.ts +8 -0
  23. package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
  24. package/dist/analyzers/universal/schema/config.js +9 -0
  25. package/dist/analyzers/universal/schema/config.js.map +1 -1
  26. package/dist/analyzers/universal/schema/types.d.ts +5 -0
  27. package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
  28. package/dist/auditRouter.d.ts.map +1 -1
  29. package/dist/auditRouter.js +6 -1
  30. package/dist/auditRouter.js.map +1 -1
  31. package/dist/auditRunner.d.ts.map +1 -1
  32. package/dist/auditRunner.js +62 -8
  33. package/dist/auditRunner.js.map +1 -1
  34. package/dist/cli.js +157 -13
  35. package/dist/cli.js.map +1 -1
  36. package/dist/codeIndexDB.d.ts.map +1 -1
  37. package/dist/codeIndexDB.js +29 -7
  38. package/dist/codeIndexDB.js.map +1 -1
  39. package/dist/config/defaults.d.ts +2 -0
  40. package/dist/config/defaults.d.ts.map +1 -1
  41. package/dist/config/defaults.js +15 -6
  42. package/dist/config/defaults.js.map +1 -1
  43. package/dist/config/effectiveConfig.d.ts +22 -1
  44. package/dist/config/effectiveConfig.d.ts.map +1 -1
  45. package/dist/config/effectiveConfig.js +69 -2
  46. package/dist/config/effectiveConfig.js.map +1 -1
  47. package/dist/config/lintConfigReader.d.ts +56 -0
  48. package/dist/config/lintConfigReader.d.ts.map +1 -0
  49. package/dist/config/lintConfigReader.js +263 -0
  50. package/dist/config/lintConfigReader.js.map +1 -0
  51. package/dist/constants.d.ts +9 -2
  52. package/dist/constants.d.ts.map +1 -1
  53. package/dist/constants.js +10 -6
  54. package/dist/constants.js.map +1 -1
  55. package/dist/dataPaths.d.ts +8 -0
  56. package/dist/dataPaths.d.ts.map +1 -1
  57. package/dist/dataPaths.js +21 -0
  58. package/dist/dataPaths.js.map +1 -1
  59. package/dist/dismissals.d.ts +59 -0
  60. package/dist/dismissals.d.ts.map +1 -0
  61. package/dist/dismissals.js +128 -0
  62. package/dist/dismissals.js.map +1 -0
  63. package/dist/enforcement/gate.d.ts.map +1 -1
  64. package/dist/enforcement/gate.js +5 -0
  65. package/dist/enforcement/gate.js.map +1 -1
  66. package/dist/hooks/codex.d.ts.map +1 -1
  67. package/dist/hooks/codex.js +18 -6
  68. package/dist/hooks/codex.js.map +1 -1
  69. package/dist/hooks/core.d.ts +12 -0
  70. package/dist/hooks/core.d.ts.map +1 -1
  71. package/dist/hooks/core.js +12 -3
  72. package/dist/hooks/core.js.map +1 -1
  73. package/dist/hooks/cursor.d.ts.map +1 -1
  74. package/dist/hooks/cursor.js +17 -10
  75. package/dist/hooks/cursor.js.map +1 -1
  76. package/dist/installConfig.d.ts +40 -0
  77. package/dist/installConfig.d.ts.map +1 -0
  78. package/dist/installConfig.js +84 -0
  79. package/dist/installConfig.js.map +1 -0
  80. package/dist/installer.d.ts.map +1 -1
  81. package/dist/installer.js +2 -15
  82. package/dist/installer.js.map +1 -1
  83. package/dist/languages/go/analyzer +0 -0
  84. package/dist/languages/go/analyzer-src/solid.go +14 -6
  85. package/dist/mcp.js +54 -0
  86. package/dist/mcp.js.map +1 -1
  87. package/dist/pipeline.d.ts.map +1 -1
  88. package/dist/pipeline.js +12 -1
  89. package/dist/pipeline.js.map +1 -1
  90. package/dist/pipelineAdapters.d.ts.map +1 -1
  91. package/dist/pipelineAdapters.js +87 -7
  92. package/dist/pipelineAdapters.js.map +1 -1
  93. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  94. package/dist/reporting/csvReportGenerator.js +1 -0
  95. package/dist/reporting/csvReportGenerator.js.map +1 -1
  96. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  97. package/dist/reporting/htmlReportGenerator.js +5 -0
  98. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  99. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  100. package/dist/reporting/jsonReportGenerator.js +6 -0
  101. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  102. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  103. package/dist/reporting/sarifReportGenerator.js +3 -0
  104. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  105. package/dist/services/auditJobService.d.ts.map +1 -1
  106. package/dist/services/auditJobService.js +4 -7
  107. package/dist/services/auditJobService.js.map +1 -1
  108. package/dist/structuralSignature.d.ts +41 -0
  109. package/dist/structuralSignature.d.ts.map +1 -0
  110. package/dist/structuralSignature.js +93 -0
  111. package/dist/structuralSignature.js.map +1 -0
  112. package/dist/telemetry.d.ts +80 -0
  113. package/dist/telemetry.d.ts.map +1 -0
  114. package/dist/telemetry.js +148 -0
  115. package/dist/telemetry.js.map +1 -0
  116. package/dist/types.d.ts +72 -1
  117. package/dist/types.d.ts.map +1 -1
  118. package/dist/types.js.map +1 -1
  119. package/dist/version.generated.d.ts +2 -0
  120. package/dist/version.generated.d.ts.map +1 -0
  121. package/dist/version.generated.js +7 -0
  122. package/dist/version.generated.js.map +1 -0
  123. package/package.json +3 -2
  124. package/plugin/.claude-plugin/plugin.json +1 -1
  125. package/plugin/scripts/hook-common.sh +37 -9
  126. package/plugin/skills/code-auditor/SKILL.md +24 -2
  127. package/server.json +2 -2
package/dist/cli.js CHANGED
@@ -8,11 +8,10 @@ import { Command } from 'commander';
8
8
  import chalk from 'chalk';
9
9
  import { createAuditRunner } from './auditRunner.js';
10
10
  import { runAuditDispatch } from './auditRouter.js';
11
- import { readFileSync } from 'fs';
12
11
  import { promises as fs } from 'fs';
13
12
  import { createInterface } from 'readline';
14
- import { fileURLToPath } from 'url';
15
13
  import { dirname, isAbsolute, join, relative, resolve } from 'path';
14
+ import { PACKAGE_VERSION } from './constants.js';
16
15
  import inquirer from 'inquirer';
17
16
  import { CodeMapGenerator } from './services/CodeMapGenerator.js';
18
17
  import { initParsers } from './languages/index.js';
@@ -20,16 +19,16 @@ import { queryParser } from './search/QueryParser.js';
20
19
  import { CodeIndexDB } from './codeIndexDB.js';
21
20
  import { getFilesProcessed, getFactsConsumed, isVisitorStatus, isReducerStatus } from './pipeline.js';
22
21
  import { createBaselineFromFindings, saveBaseline, loadBaseline, diffBaselines } from './baseline.js';
22
+ import { fingerprint, buildFingerprintInput } from './fingerprint.js';
23
+ import { buildDismissalEntry, upsertDismissal } from './dismissals.js';
24
+ import { resolveTelemetryConfig, signatureForFinding, buildTelemetryPayload, formatTelemetryPreview, sendTelemetry, languageHint } from './telemetry.js';
25
+ import { getInstallId } from './installConfig.js';
23
26
  import { ALL_ANALYZERS } from './analyzers/ruleRegistry.js';
24
27
  import { computeGatingDecision } from './enforcement/gate.js';
25
28
  import { BLOCKING_SEVERITIES } from './types.js';
26
29
  import { rankFilesByPriority, orderFindingsWithinFile } from './nextFile.js';
27
30
  import { runNextFile } from './nextFileIncremental.js';
28
31
  import { describeSqliteBackend } from './sqlite/driver.js';
29
- // Get package.json for version info
30
- const __filename = fileURLToPath(import.meta.url);
31
- const __dirname = dirname(__filename);
32
- const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
33
32
  const program = new Command();
34
33
  // Spec 44 R4 — file accounting summary line + `--explain-skipped` breakdown.
35
34
  // The full per-reason file lists live in the JSON report (`metadata.fileAccounting`);
@@ -82,7 +81,7 @@ function printFileAccounting(result, explainSkipped) {
82
81
  program
83
82
  .name('code-auditor')
84
83
  .description('TypeScript/JavaScript code quality auditor with AI tool integration')
85
- .version(`${packageJson.version} (sqlite: ${describeSqliteBackend()})`);
84
+ .version(`${PACKAGE_VERSION} (sqlite: ${describeSqliteBackend()})`);
86
85
  // Legacy audit command (default behavior)
87
86
  program
88
87
  .command('audit', { isDefault: true })
@@ -139,6 +138,10 @@ program
139
138
  });
140
139
  const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
141
140
  const baseline = result.metadata?.baseline;
141
+ // Spec 57 — dismissed count is reported alongside the total, never
142
+ // subtracted from it ("43 findings, 3 dismissed").
143
+ const dismissedCount = result.summary.dismissed ?? 0;
144
+ const dismissedSuffix = dismissedCount > 0 ? `, ${dismissedCount} dismissed` : '';
142
145
  // ── Coverage panel leads the report (Spec 47 R2) ─────────────
143
146
  // A diagnostic report opens with what was measured before it lists any
144
147
  // readings, so a zero-reading report can't be mistaken for a clean tree.
@@ -153,6 +156,28 @@ program
153
156
  `${covUnassessed} unassessed · ${covNotApplicable} not-applicable · ` +
154
157
  `${covCannotFire} cannot-fire`));
155
158
  }
159
+ // ── Coverage gaps (Spec 58 follow-up) ─────────────────────────
160
+ // Coverage diagnostics are the analyzer saying "my visibility ends here",
161
+ // not "the code is wrong". They lead with counts, then per-occurrence
162
+ // file:line — visible and counted, never blocking (they never reach the
163
+ // gate). Rendered alongside the coverage panel that leads the report.
164
+ const coverageDiagnostics = (result.metadata?.diagnostics ?? []).filter((d) => d.kind === 'unresolved-query' || d.kind === 'unresolved-dynamic-import');
165
+ if (coverageDiagnostics.length > 0) {
166
+ const byKind = {};
167
+ for (const d of coverageDiagnostics)
168
+ byKind[d.kind] = (byKind[d.kind] ?? 0) + 1;
169
+ const totals = Object.entries(byKind)
170
+ .map(([kind, n]) => `${n} ${kind}`)
171
+ .join(' · ');
172
+ console.log(chalk.gray(`── Coverage gaps ── ${totals}`));
173
+ for (const d of coverageDiagnostics.slice(0, 20)) {
174
+ const loc = d.file ? `${d.file}${typeof d.line === 'number' ? `:${d.line}` : ''}` : '(unknown)';
175
+ console.log(chalk.gray(` ${loc} [${d.kind}] — ${d.message}`));
176
+ }
177
+ if (coverageDiagnostics.length > 20) {
178
+ console.log(chalk.gray(` … and ${coverageDiagnostics.length - 20} more`));
179
+ }
180
+ }
156
181
  // ── Delta output (Spec 18 R2) ─────────────────────────────────
157
182
  if (baseline && !options.full) {
158
183
  const newViolations = violations.filter((v) => v.new === true);
@@ -215,7 +240,7 @@ program
215
240
  }
216
241
  else if (!baseline) {
217
242
  // No baseline: current behavior + hint
218
- console.log(`\nFound ${result.summary.totalViolations} readings`);
243
+ console.log(`\nFound ${result.summary.totalViolations} findings${dismissedSuffix}`);
219
244
  console.log(`Critical: ${result.summary.criticalIssues}`);
220
245
  console.log(`Severe: ${result.summary.severe}`);
221
246
  console.log(`High: ${result.summary.high}`);
@@ -224,7 +249,7 @@ program
224
249
  }
225
250
  else {
226
251
  // --full with baseline: full itemized inventory (current behavior)
227
- console.log(`\nFound ${result.summary.totalViolations} readings`);
252
+ console.log(`\nFound ${result.summary.totalViolations} findings${dismissedSuffix}`);
228
253
  console.log(`Critical: ${result.summary.criticalIssues}`);
229
254
  console.log(`Severe: ${result.summary.severe}`);
230
255
  console.log(`High: ${result.summary.high}`);
@@ -381,6 +406,9 @@ program
381
406
  const severityOrder = ['critical', 'severe', 'high'];
382
407
  const failIndex = severityOrder.indexOf(failOnSeverity);
383
408
  const hasAtOrAbove = evaluableViolations.some((v) => {
409
+ // Spec 57 — a dismissed finding never blocks the gate.
410
+ if (v.dismissed)
411
+ return false;
384
412
  const vIndex = severityOrder.indexOf(v.severity);
385
413
  return vIndex >= 0 && vIndex <= failIndex;
386
414
  });
@@ -466,6 +494,10 @@ program
466
494
  }
467
495
  // Collect all violations
468
496
  const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
497
+ // Spec 57 — dismissed count is reported alongside the total, never
498
+ // subtracted from it. The hook surface (`changed`) is agent-facing, so it
499
+ // must show the same "N findings, M dismissed" figure as the full report.
500
+ const dismissedCount = result.summary.dismissed ?? 0;
469
501
  // Blocking gate decision (Spec 45 R1/R4, Spec 54 R3), computed once so the
470
502
  // agent-facing before/after count (Spec 45 A2) and the exit code agree on
471
503
  // the same number. Every registered rule participates (R1); enforcement is
@@ -481,7 +513,7 @@ program
481
513
  }
482
514
  else if (options.json) {
483
515
  const projectDir = resolve(options.path || process.cwd());
484
- const jsonOutput = violations.map((v) => {
516
+ const jsonViolations = violations.map((v) => {
485
517
  // Compute relative path if file resolves inside the project
486
518
  let filePath = v.file || '';
487
519
  if (filePath.startsWith('/') || filePath.startsWith('\\\\')) {
@@ -506,9 +538,23 @@ program
506
538
  enclosingSymbol: v.symbol || v.enclosingFunction || '',
507
539
  suggestion: v.suggestion || '',
508
540
  details: v.details || '',
509
- ...(v.new !== undefined && { new: v.new })
541
+ ...(v.new !== undefined && { new: v.new }),
542
+ ...(v.dismissed !== undefined && { dismissed: v.dismissed }),
543
+ fingerprint: fingerprint(buildFingerprintInput(v))
510
544
  };
511
545
  });
546
+ // Coverage diagnostics ride alongside violations so an agent sees "the
547
+ // analyzer couldn't resolve this" without a metadata flag — and without
548
+ // it ever gating (diagnostics never reach computeGatingDecision).
549
+ const jsonDiagnostics = (result.metadata?.diagnostics ?? []).map((d) => ({
550
+ analyzer: d.analyzerName ?? d.analyzer ?? '',
551
+ kind: d.kind,
552
+ message: d.message,
553
+ ...(d.file ? { file: d.file } : {}),
554
+ ...(typeof d.line === 'number' ? { line: d.line } : {}),
555
+ ...(d.details ? { details: d.details } : {}),
556
+ }));
557
+ const jsonOutput = { violations: jsonViolations, diagnostics: jsonDiagnostics };
512
558
  process.stdout.write(JSON.stringify(jsonOutput, null, 2) + '\n');
513
559
  }
514
560
  else if (!options.quiet || violations.length > 0) {
@@ -522,6 +568,9 @@ program
522
568
  console.log('');
523
569
  printCountSummary(violations);
524
570
  console.log(chalk.bold(`gate before/after: ${violations.length} → ${blocking.length} blocking`));
571
+ if (dismissedCount > 0) {
572
+ console.log(chalk.dim(` ${dismissedCount} dismissed — still counted above, excluded from the gate`));
573
+ }
525
574
  console.log(chalk.gray('── Violations ────────────────────────────────────'));
526
575
  for (const v of violations) {
527
576
  const icon = v.severity === 'critical' ? '🔴' :
@@ -529,7 +578,10 @@ program
529
578
  const statusTag = v.new === false
530
579
  ? chalk.dim(' [known — still open]')
531
580
  : '';
532
- console.log(`${icon} ${chalk.bold(v.file)}${v.line ? `:${v.line}` : ''} [${v.severity}] ${v.message}${statusTag}`);
581
+ const dismissedTag = v.dismissed
582
+ ? chalk.dim(' [dismissed]')
583
+ : '';
584
+ console.log(`${icon} ${chalk.bold(v.file)}${v.line ? `:${v.line}` : ''} [${v.severity}] ${v.message}${statusTag}${dismissedTag}`);
533
585
  }
534
586
  }
535
587
  else {
@@ -911,7 +963,7 @@ program
911
963
  }
912
964
  // Create new baseline
913
965
  const newBaseline = createBaselineFromFindings(allViolations, {
914
- toolVersion: packageJson.version,
966
+ toolVersion: PACKAGE_VERSION,
915
967
  totalFindings: allViolations.filter((v) => v.analyzer !== 'invariants').length,
916
968
  analyzerCounts,
917
969
  corpusStats,
@@ -947,6 +999,90 @@ program
947
999
  process.exit(1);
948
1000
  }
949
1001
  });
1002
+ // Dismiss command (Spec 57)
1003
+ program
1004
+ .command('dismiss <fingerprint>')
1005
+ .description('Dismiss one finding by fingerprint — a written reason is required')
1006
+ .requiredOption('--reason <reason>', 'Why this finding is dismissed (required — a dismissal without a reason is a config error)')
1007
+ .option('-p, --path <path>', 'Project path', process.cwd())
1008
+ .option('--telemetry-endpoint <url>', 'Feedback service endpoint override (defaults to the opt-in endpoint recorded by the telemetry tool)')
1009
+ .option('--json', 'Output as JSON')
1010
+ .action(async (fingerprintArg, options) => {
1011
+ try {
1012
+ const reason = (options.reason ?? '').trim();
1013
+ if (!reason) {
1014
+ 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.'));
1015
+ process.exit(1);
1016
+ }
1017
+ const projectRoot = resolve(options.path || process.cwd());
1018
+ await initParsers();
1019
+ // Re-audit so the fingerprint resolves against the live finding. The
1020
+ // fingerprint is a content hash of rule+file+symbol, so it is stable
1021
+ // across runs on the same machine (same tuple the baseline uses).
1022
+ const runner = createAuditRunner({ projectRoot });
1023
+ const result = await runner.run();
1024
+ const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
1025
+ const match = violations.find((v) => fingerprint(buildFingerprintInput(v)) === fingerprintArg);
1026
+ if (!match) {
1027
+ console.error(chalk.red(`Error: no finding matches fingerprint "${fingerprintArg}".`));
1028
+ console.error(chalk.gray(`The fingerprint is a content hash of rule+file+symbol. Get the exact value from `));
1029
+ console.error(chalk.gray(`the JSON report: run ${chalk.cyan('code-audit audit --format json')} (or ${chalk.cyan('code-audit changed --json')}) `));
1030
+ console.error(chalk.gray(`and copy the "fingerprint" field of the finding you want to dismiss.`));
1031
+ process.exit(1);
1032
+ }
1033
+ const entry = buildDismissalEntry(match, reason, PACKAGE_VERSION);
1034
+ const dismissals = upsertDismissal(projectRoot, entry);
1035
+ // Spec 57 — opt-in telemetry. Nothing is sent unless the user opted in via
1036
+ // the `telemetry` MCP tool (default OFF) AND an endpoint is configured. The
1037
+ // payload is printed for review before sending; any send failure is silent
1038
+ // and never affects the dismissal or the exit code.
1039
+ const telemetry = resolveTelemetryConfig({ endpoint: options.telemetryEndpoint });
1040
+ if (telemetry.enabled && telemetry.endpoint) {
1041
+ const signature = signatureForFinding(match.file, match.line ?? match.start?.line ?? 1, match.column ?? match.start?.column ?? 1) ?? 'unknown';
1042
+ const payload = buildTelemetryPayload({
1043
+ install_id: getInstallId(),
1044
+ toolVersion: PACKAGE_VERSION,
1045
+ rule: entry.rule,
1046
+ level: match.severity,
1047
+ reason: entry.reason,
1048
+ signature,
1049
+ lang: languageHint(match.file),
1050
+ });
1051
+ process.stderr.write(formatTelemetryPreview(payload));
1052
+ const sendResult = await sendTelemetry(payload, telemetry.endpoint);
1053
+ if (options.json) {
1054
+ // Surface the send outcome in machine output without ever failing on it.
1055
+ process.stderr.write(` telemetry: ${sendResult.sent ? 'sent' : 'not sent'}${sendResult.error ? ` (${sendResult.error})` : ''}\n`);
1056
+ }
1057
+ else if (!sendResult.sent) {
1058
+ console.log(chalk.gray(` Telemetry not sent${sendResult.error ? ` (${sendResult.error})` : ''} — offline is never an error.`));
1059
+ }
1060
+ else {
1061
+ console.log(chalk.gray(' Telemetry sent.'));
1062
+ }
1063
+ }
1064
+ if (options.json) {
1065
+ process.stdout.write(JSON.stringify({
1066
+ success: true,
1067
+ fingerprint: entry.fingerprint,
1068
+ rule: entry.rule,
1069
+ file: entry.file,
1070
+ symbol: entry.symbol,
1071
+ reason: entry.reason,
1072
+ totalDismissed: dismissals.entries.length,
1073
+ }, null, 2) + '\n');
1074
+ }
1075
+ else {
1076
+ console.log(chalk.green(`\n✓ Dismissed ${chalk.bold(entry.rule)} @ ${entry.symbol || entry.file} — "${entry.reason}"`));
1077
+ console.log(chalk.gray(` ${dismissals.entries.length} dismissal(s) recorded in .codeauditor.dismissals.json.`));
1078
+ console.log(chalk.gray(` The finding is still counted in every report, but excluded from the gate.`));
1079
+ }
1080
+ }
1081
+ catch (error) {
1082
+ console.error(chalk.red('Error:'), error);
1083
+ process.exit(1);
1084
+ }
1085
+ });
950
1086
  // Generate configuration command
951
1087
  program
952
1088
  .command('generate-config')
@@ -2903,6 +3039,7 @@ program
2903
3039
  const { getDefaultConfig, DEFAULT_CODE_INDEX_CONFIG } = await import('./config/defaults.js');
2904
3040
  const { computeEffectiveConfig, computeTopLevelConfig, } = await import('./config/effectiveConfig.js');
2905
3041
  const { getPreset, PRESET_IDS } = await import('./presets/presets.js');
3042
+ const { readProjectLintThresholds, thresholdsToAnalyzerConfig } = await import('./config/lintConfigReader.js');
2906
3043
  const projectRoot = pathModule.resolve(options.path);
2907
3044
  const filePath = pathModule.resolve(file);
2908
3045
  const configPath = pathModule.join(projectRoot, '.codeauditor.json');
@@ -2937,6 +3074,10 @@ program
2937
3074
  }
2938
3075
  }
2939
3076
  const presets = presetIds.map((id) => getPreset(id));
3077
+ // Spec 50 R2 — read the project's own lint config so size thresholds sourced
3078
+ // from ESLint are named `project-lint-config` rather than `default`.
3079
+ const lintResult = await readProjectLintThresholds(projectRoot);
3080
+ const lintConfig = lintResult ? thresholdsToAnalyzerConfig(lintResult.thresholds) : {};
2940
3081
  const effective = computeEffectiveConfig({
2941
3082
  filePath,
2942
3083
  projectRoot,
@@ -2944,6 +3085,7 @@ program
2944
3085
  pathProfiles,
2945
3086
  enabledAnalyzers,
2946
3087
  presets,
3088
+ lintConfig,
2947
3089
  });
2948
3090
  const topLevel = computeTopLevelConfig(pristine, config);
2949
3091
  if (options.json) {
@@ -2952,6 +3094,7 @@ program
2952
3094
  projectRoot,
2953
3095
  configPath,
2954
3096
  presets: presetIds,
3097
+ lintConfigPath: lintResult?.configPath ?? null,
2955
3098
  matchedProfiles: effective.matchedProfiles,
2956
3099
  excludeFromGate: effective.excludeFromGate ?? null,
2957
3100
  topLevel,
@@ -2964,6 +3107,7 @@ program
2964
3107
  console.log(`${chalk.bold('file:')} ${effective.relativePath}`);
2965
3108
  console.log(`${chalk.bold('project root:')} ${projectRoot}`);
2966
3109
  console.log(`${chalk.bold('presets:')} ${presetIds.length > 0 ? presetIds.join(', ') : chalk.gray('(none)')}`);
3110
+ console.log(`${chalk.bold('lint config:')} ${lintResult?.configPath ? chalk.cyan(lintResult.configKind) + ' ' + chalk.dim(lintResult.configPath) : chalk.gray('(none — defaults)')}`);
2967
3111
  console.log(`${chalk.bold('profiles:')} ${effective.matchedProfiles.length > 0 ? effective.matchedProfiles.join(', ') : chalk.gray('(none)')}`);
2968
3112
  if (effective.excludeFromGate) {
2969
3113
  console.log(`${chalk.bold('excludeFromGate:')} ${chalk.yellow('true')}`);