@kb-labs/review-heuristic 2.94.0 → 2.96.0

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/dist/index.d.ts CHANGED
@@ -112,9 +112,9 @@ interface LinterEngine {
112
112
  *
113
113
  * @param files - Absolute paths to files to lint
114
114
  * @param cwd - Working directory (project root)
115
- * @returns CLI command string
115
+ * @returns argv array (cmd + args) — never a shell string, prevents injection
116
116
  */
117
- buildCommand(files: string[], cwd: string): string;
117
+ buildCommand(files: string[], cwd: string): string[];
118
118
  /**
119
119
  * Parse JSON output from linter CLI.
120
120
  *
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'crypto';
2
- import { execSync } from 'child_process';
2
+ import { spawnSync } from 'child_process';
3
3
  import { existsSync } from 'fs';
4
4
  import { dirname, join, extname, relative } from 'path';
5
5
 
@@ -224,8 +224,7 @@ var eslintEngine = {
224
224
  ".eslintrc"
225
225
  ],
226
226
  buildCommand(files, _cwd) {
227
- const quotedFiles = files.map((f) => `"${f}"`).join(" ");
228
- return `npx eslint --format json ${quotedFiles}`;
227
+ return ["npx", "eslint", "--format", "json", ...files];
229
228
  },
230
229
  parseOutput(json, _cwd) {
231
230
  const results = JSON.parse(json);
@@ -254,8 +253,8 @@ var eslintEngine = {
254
253
  },
255
254
  async isAvailable() {
256
255
  try {
257
- const { execSync: execSync2 } = await import('child_process');
258
- execSync2("npx eslint --version", { stdio: "pipe" });
256
+ const { execSync } = await import('child_process');
257
+ execSync("npx eslint --version", { stdio: "pipe" });
259
258
  return true;
260
259
  } catch {
261
260
  return false;
@@ -293,8 +292,7 @@ var ruffEngine = {
293
292
  ".ruff.toml"
294
293
  ],
295
294
  buildCommand(files, _cwd) {
296
- const quotedFiles = files.map((f) => `"${f}"`).join(" ");
297
- return `ruff check --output-format json ${quotedFiles}`;
295
+ return ["ruff", "check", "--output-format", "json", ...files];
298
296
  },
299
297
  parseOutput(json, _cwd) {
300
298
  const diagnostics = JSON.parse(json);
@@ -321,8 +319,8 @@ var ruffEngine = {
321
319
  },
322
320
  async isAvailable() {
323
321
  try {
324
- const { execSync: execSync2 } = await import('child_process');
325
- execSync2("ruff --version", { stdio: "pipe" });
322
+ const { execSync } = await import('child_process');
323
+ execSync("ruff --version", { stdio: "pipe" });
326
324
  return true;
327
325
  } catch {
328
326
  return false;
@@ -416,25 +414,18 @@ var LinterRunner = class {
416
414
  }
417
415
  return relative(projectRoot, f) || f;
418
416
  });
419
- const command = engine.buildCommand(relativeFiles, projectRoot);
420
- let output;
421
- try {
422
- output = execSync(command, {
423
- cwd: projectRoot,
424
- encoding: "utf-8",
425
- stdio: ["pipe", "pipe", "pipe"],
426
- maxBuffer: this.maxBuffer,
427
- timeout: this.timeout
428
- });
429
- } catch (execError) {
430
- const err = execError;
431
- if (err.stdout) {
432
- output = err.stdout;
433
- } else {
434
- errors.push(`${projectRoot}: ${err.message ?? "Unknown error"}`);
435
- continue;
436
- }
417
+ const [cmd, ...args] = engine.buildCommand(relativeFiles, projectRoot);
418
+ const proc = spawnSync(cmd, args, {
419
+ cwd: projectRoot,
420
+ encoding: "utf-8",
421
+ maxBuffer: this.maxBuffer,
422
+ timeout: this.timeout
423
+ });
424
+ if (proc.error) {
425
+ errors.push(`${projectRoot}: ${proc.error.message}`);
426
+ continue;
437
427
  }
428
+ const output = proc.stdout ?? "";
438
429
  if (output && output.trim()) {
439
430
  try {
440
431
  const findings = engine.parseOutput(output, projectRoot);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/engine-registry.ts","../src/deduplication.ts","../src/engines/eslint.ts","../src/engines/ruff.ts","../src/engines/index.ts","../src/runner.ts"],"names":["execSync","mapSeverity"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/review-heuristic/engine-registry\n * Registry of heuristic analysis engines with type mappings.\n *\n * Maps specific tools (eslint, ruff, clippy) to engine types (linter, compiler, sast).\n * Used for engine type priority deduplication.\n */\n\nimport type { HeuristicEngine } from '@kb-labs/review-contracts';\n\n/**\n * Registry of all supported heuristic engines.\n *\n * Engine types determine priority in deduplication:\n * - compiler (1): TypeScript compiler, rustc, go build\n * - linter (2): ESLint, Ruff, golangci-lint, Clippy, RuboCop\n * - sast (3): Semgrep, CodeQL, Bandit\n * - ast (4): tree-sitter (read-only AST analysis)\n */\nexport const ENGINE_REGISTRY: Record<string, HeuristicEngine> = {\n // JavaScript/TypeScript linters\n eslint: {\n id: 'eslint',\n name: 'ESLint',\n language: ['typescript', 'javascript', 'tsx', 'jsx'],\n type: 'linter',\n },\n\n // TypeScript compiler\n tsc: {\n id: 'tsc',\n name: 'TypeScript Compiler',\n language: ['typescript', 'tsx'],\n type: 'compiler',\n },\n\n // Python linters\n ruff: {\n id: 'ruff',\n name: 'Ruff',\n language: ['python'],\n type: 'linter',\n },\n\n pylint: {\n id: 'pylint',\n name: 'Pylint',\n language: ['python'],\n type: 'linter',\n },\n\n // Python SAST\n bandit: {\n id: 'bandit',\n name: 'Bandit',\n language: ['python'],\n type: 'sast',\n },\n\n // Go linter\n golangci: {\n id: 'golangci',\n name: 'golangci-lint',\n language: ['go'],\n type: 'linter',\n },\n\n // Rust linter\n clippy: {\n id: 'clippy',\n name: 'Clippy',\n language: ['rust'],\n type: 'linter',\n },\n\n // Ruby linter\n rubocop: {\n id: 'rubocop',\n name: 'RuboCop',\n language: ['ruby'],\n type: 'linter',\n },\n\n // Multi-language SAST\n semgrep: {\n id: 'semgrep',\n name: 'Semgrep',\n language: ['typescript', 'javascript', 'python', 'go', 'rust', 'ruby'],\n type: 'sast',\n },\n\n codeql: {\n id: 'codeql',\n name: 'CodeQL',\n language: ['typescript', 'javascript', 'python', 'go', 'rust', 'ruby'],\n type: 'sast',\n },\n\n // AST-based analysis (read-only)\n treesitter: {\n id: 'treesitter',\n name: 'Tree-sitter',\n language: ['typescript', 'javascript', 'python', 'go', 'rust', 'ruby'],\n type: 'ast',\n },\n};\n\n/**\n * Get engine by ID.\n */\nexport function getEngine(engineId: string): HeuristicEngine | undefined {\n return ENGINE_REGISTRY[engineId];\n}\n\n/**\n * Get all engines supporting a language.\n */\nexport function getEnginesForLanguage(language: string): HeuristicEngine[] {\n return Object.values(ENGINE_REGISTRY).filter((engine) =>\n engine.language.includes(language)\n );\n}\n\n/**\n * Get engine type priority for deduplication.\n *\n * Lower number = higher priority (kept in deduplication).\n * - compiler (1) - highest priority\n * - linter (2)\n * - sast (3)\n * - ast (4)\n * - llm (5) - lowest priority\n */\nexport function getEngineTypePriority(engineId: string): number {\n const engine = getEngine(engineId);\n if (!engine) {\n // Unknown engine, treat as lowest priority\n return 999;\n }\n\n const typePriority: Record<string, number> = {\n compiler: 1,\n linter: 2,\n sast: 3,\n ast: 4,\n llm: 5,\n };\n\n return typePriority[engine.type] ?? 999;\n}\n","/**\n * @module @kb-labs/review-heuristic/deduplication\n * Fingerprint-based deduplication with engine type priority.\n *\n * Deduplicates findings from multiple engines using:\n * 1. Fingerprint collision detection (sha1(ruleId|file|bucket|snippetHash))\n * 2. Engine type priority (compiler > linter > sast > ast > llm)\n * 3. Severity adjustment (higher severity wins if same type)\n */\n\nimport { createHash } from 'node:crypto';\nimport type { ReviewFinding, FindingSeverity } from '@kb-labs/review-contracts';\nimport { getEngineTypePriority } from './engine-registry.js';\n\n/**\n * Generate fingerprint for a finding.\n *\n * Fingerprint = sha1(ruleId|file|bucket|snippetHash)\n * - ruleId: Rule identifier\n * - file: File path\n * - bucket: Line bucket (e.g., lines 10-19 → bucket 1)\n * - snippetHash: Hash of code snippet (optional)\n */\nexport function generateFingerprint(\n finding: ReviewFinding,\n snippetHash?: string\n): string {\n const bucket = Math.floor(finding.line / 10);\n const parts = [finding.ruleId, finding.file, bucket.toString()];\n\n if (snippetHash) {\n parts.push(snippetHash);\n }\n\n const input = parts.join('|');\n return createHash('sha1').update(input).digest('hex');\n}\n\n/**\n * Hash code snippet for fingerprint.\n */\nexport function hashSnippet(snippet: string): string {\n return createHash('sha1').update(snippet.trim()).digest('hex');\n}\n\n/**\n * Get severity weight for comparison.\n *\n * Higher number = more severe.\n */\nfunction getSeverityWeight(severity: FindingSeverity): number {\n const weights: Record<FindingSeverity, number> = {\n blocker: 5,\n high: 4,\n medium: 3,\n low: 2,\n info: 1,\n };\n return weights[severity] ?? 0;\n}\n\n/**\n * Deduplicate findings using fingerprints and engine type priority.\n *\n * Algorithm:\n * 1. Group findings by fingerprint\n * 2. For each collision group:\n * - Sort by engine type priority (compiler > linter > sast > ast > llm)\n * - If same type, sort by severity (blocker > high > medium > low > info)\n * - Keep highest priority finding, discard rest\n *\n * @param findings - All findings from all engines\n * @returns Deduplicated findings\n */\nexport function deduplicateFindings(findings: ReviewFinding[]): ReviewFinding[] {\n // Generate fingerprints\n const fingerprintMap = new Map<string, ReviewFinding[]>();\n\n for (const finding of findings) {\n const fingerprint = generateFingerprint(finding);\n\n if (!fingerprintMap.has(fingerprint)) {\n fingerprintMap.set(fingerprint, []);\n }\n\n fingerprintMap.get(fingerprint)!.push(finding);\n }\n\n // Deduplicate each collision group\n const deduplicated: ReviewFinding[] = [];\n\n for (const group of fingerprintMap.values()) {\n if (group.length === 0) {\n continue;\n }\n\n if (group.length === 1) {\n // No collision, keep as-is\n deduplicated.push(group[0]!);\n continue;\n }\n\n // Sort by priority\n const sorted = group.sort((a, b) => {\n // 1. Engine type priority (lower number = higher priority)\n const aPriority = getEngineTypePriority(a.engine);\n const bPriority = getEngineTypePriority(b.engine);\n\n if (aPriority !== bPriority) {\n return aPriority - bPriority;\n }\n\n // 2. Severity (higher severity wins)\n const aSeverity = getSeverityWeight(a.severity);\n const bSeverity = getSeverityWeight(b.severity);\n\n return bSeverity - aSeverity;\n });\n\n // Keep highest priority finding\n const best = sorted[0];\n if (best) {\n deduplicated.push(best);\n }\n }\n\n return deduplicated;\n}\n\n/**\n * Deduplicate findings with snippet-based fingerprints.\n *\n * More precise deduplication using code snippets.\n *\n * @param findings - All findings\n * @param getSnippet - Function to get code snippet for a finding\n * @returns Deduplicated findings\n */\nexport async function deduplicateFindingsWithSnippets(\n findings: ReviewFinding[],\n getSnippet: (finding: ReviewFinding) => Promise<string>\n): Promise<ReviewFinding[]> {\n // Generate fingerprints with snippets\n const fingerprintMap = new Map<string, ReviewFinding[]>();\n\n for (const finding of findings) {\n // eslint-disable-next-line no-await-in-loop -- Sequential snippet fetching for fingerprinting\n const snippet = await getSnippet(finding);\n const snippetHash = hashSnippet(snippet);\n const fingerprint = generateFingerprint(finding, snippetHash);\n\n if (!fingerprintMap.has(fingerprint)) {\n fingerprintMap.set(fingerprint, []);\n }\n\n fingerprintMap.get(fingerprint)!.push(finding);\n }\n\n // Deduplicate each collision group\n const deduplicated: ReviewFinding[] = [];\n\n for (const group of fingerprintMap.values()) {\n if (group.length === 0) {\n continue;\n }\n\n if (group.length === 1) {\n deduplicated.push(group[0]!);\n continue;\n }\n\n // Sort by priority\n const sorted = group.sort((a, b) => {\n const aPriority = getEngineTypePriority(a.engine);\n const bPriority = getEngineTypePriority(b.engine);\n\n if (aPriority !== bPriority) {\n return aPriority - bPriority;\n }\n\n const aSeverity = getSeverityWeight(a.severity);\n const bSeverity = getSeverityWeight(b.severity);\n\n return bSeverity - aSeverity;\n });\n\n const best = sorted[0];\n if (best) {\n deduplicated.push(best);\n }\n }\n\n return deduplicated;\n}\n","/**\n * @module @kb-labs/review-heuristic/engines/eslint\n * ESLint engine - runs ESLint CLI and parses JSON output.\n */\n\nimport type { ReviewFinding, FindingSeverity } from '@kb-labs/review-contracts';\nimport type { LinterEngine } from './types.js';\n\n/**\n * Map ESLint severity (1=warn, 2=error) to ReviewFinding severity.\n */\nfunction mapSeverity(eslintSeverity: number): FindingSeverity {\n if (eslintSeverity === 2) {\n return 'high';\n }\n if (eslintSeverity === 1) {\n return 'medium';\n }\n return 'info';\n}\n\n/**\n * ESLint JSON output format (per file).\n */\ninterface ESLintFileResult {\n filePath: string;\n messages: Array<{\n ruleId: string | null;\n severity: number;\n message: string;\n line: number;\n column: number;\n endLine?: number;\n endColumn?: number;\n fix?: {\n range: [number, number];\n text: string;\n };\n }>;\n errorCount: number;\n warningCount: number;\n}\n\n/**\n * ESLint engine definition.\n */\nexport const eslintEngine: LinterEngine = {\n id: 'eslint',\n name: 'ESLint',\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'],\n configFiles: [\n 'eslint.config.js',\n 'eslint.config.mjs',\n 'eslint.config.cjs',\n '.eslintrc.js',\n '.eslintrc.cjs',\n '.eslintrc.json',\n '.eslintrc.yml',\n '.eslintrc.yaml',\n '.eslintrc',\n ],\n\n buildCommand(files: string[], _cwd: string): string {\n // Quote each file path to handle spaces\n const quotedFiles = files.map(f => `\"${f}\"`).join(' ');\n return `npx eslint --format json ${quotedFiles}`;\n },\n\n parseOutput(json: string, _cwd: string): ReviewFinding[] {\n const results: ESLintFileResult[] = JSON.parse(json);\n const findings: ReviewFinding[] = [];\n\n for (const result of results) {\n for (const msg of result.messages) {\n findings.push({\n id: `eslint:${result.filePath}:${msg.ruleId ?? 'unknown'}:${msg.line}:${msg.column}`,\n ruleId: msg.ruleId ?? 'eslint/unknown',\n type: 'code-quality',\n severity: mapSeverity(msg.severity),\n confidence: 'certain',\n file: result.filePath,\n line: msg.line ?? 1,\n column: msg.column ?? 1,\n endLine: msg.endLine ?? msg.line ?? 1,\n message: msg.message,\n engine: 'eslint',\n source: 'heuristic',\n scope: msg.fix ? 'local' : 'global',\n automated: !!msg.fix,\n });\n }\n }\n\n return findings;\n },\n\n async isAvailable(): Promise<boolean> {\n try {\n const { execSync } = await import('child_process');\n execSync('npx eslint --version', { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n },\n};\n","/**\n * @module @kb-labs/review-heuristic/engines/ruff\n * Ruff engine - runs Ruff CLI and parses JSON output.\n *\n * Ruff is an extremely fast Python linter written in Rust.\n */\n\nimport type { ReviewFinding, FindingSeverity } from '@kb-labs/review-contracts';\nimport type { LinterEngine } from './types.js';\n\n/**\n * Ruff JSON output format (per diagnostic).\n */\ninterface RuffDiagnostic {\n code: string;\n message: string;\n filename: string;\n location: {\n row: number;\n column: number;\n };\n end_location: {\n row: number;\n column: number;\n };\n fix?: {\n message: string;\n edits: Array<{\n content: string;\n location: { row: number; column: number };\n end_location: { row: number; column: number };\n }>;\n };\n noqa_row?: number;\n}\n\n/**\n * Map Ruff code prefix to severity.\n * E = Error, W = Warning, F = Fatal, etc.\n */\nfunction mapSeverity(code: string): FindingSeverity {\n const prefix = code.charAt(0).toUpperCase();\n\n switch (prefix) {\n case 'E': // Error\n case 'F': // Pyflakes\n return 'high';\n case 'W': // Warning\n case 'C': // Convention\n return 'medium';\n case 'I': // Isort\n case 'D': // Docstring\n return 'low';\n default:\n return 'medium';\n }\n}\n\n/**\n * Ruff engine definition.\n */\nexport const ruffEngine: LinterEngine = {\n id: 'ruff',\n name: 'Ruff',\n extensions: ['.py', '.pyi'],\n configFiles: [\n 'pyproject.toml',\n 'ruff.toml',\n '.ruff.toml',\n ],\n\n buildCommand(files: string[], _cwd: string): string {\n const quotedFiles = files.map(f => `\"${f}\"`).join(' ');\n return `ruff check --output-format json ${quotedFiles}`;\n },\n\n parseOutput(json: string, _cwd: string): ReviewFinding[] {\n const diagnostics: RuffDiagnostic[] = JSON.parse(json);\n const findings: ReviewFinding[] = [];\n\n for (const d of diagnostics) {\n findings.push({\n id: `ruff:${d.filename}:${d.code}:${d.location.row}:${d.location.column}`,\n ruleId: d.code,\n type: 'code-quality',\n severity: mapSeverity(d.code),\n confidence: 'certain',\n file: d.filename,\n line: d.location.row,\n column: d.location.column,\n endLine: d.end_location.row,\n message: d.message,\n engine: 'ruff',\n source: 'heuristic',\n scope: d.fix ? 'local' : 'global',\n automated: !!d.fix,\n });\n }\n\n return findings;\n },\n\n async isAvailable(): Promise<boolean> {\n try {\n const { execSync } = await import('child_process');\n execSync('ruff --version', { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n },\n};\n","/**\n * @module @kb-labs/review-heuristic/engines\n * Linter engine definitions.\n */\n\nexport type { LinterEngine, LinterResult } from './types.js';\nexport { eslintEngine } from './eslint.js';\nexport { ruffEngine } from './ruff.js';\n\nimport { eslintEngine } from './eslint.js';\nimport { ruffEngine } from './ruff.js';\nimport type { LinterEngine } from './types.js';\n\n/**\n * All available linter engines.\n */\nexport const LINTER_ENGINES: LinterEngine[] = [\n eslintEngine,\n ruffEngine,\n];\n\n/**\n * Get engine by ID.\n */\nexport function getLinterEngine(id: string): LinterEngine | undefined {\n return LINTER_ENGINES.find(e => e.id === id);\n}\n\n/**\n * Get engine for file extension.\n */\nexport function getLinterEngineForFile(filePath: string): LinterEngine | undefined {\n const ext = filePath.slice(filePath.lastIndexOf('.'));\n return LINTER_ENGINES.find(e => e.extensions.includes(ext));\n}\n","/**\n * @module @kb-labs/review-heuristic/runner\n * Linter runner - executes linters via CLI and collects findings.\n */\n\nimport { execSync } from 'child_process';\nimport { existsSync } from 'fs';\nimport { dirname, join, extname, relative } from 'path';\nimport type { ReviewFinding } from '@kb-labs/review-contracts';\nimport type { LinterEngine, LinterResult } from './engines/types.js';\nimport { LINTER_ENGINES } from './engines/index.js';\n\n/**\n * Linter runner configuration.\n */\nexport interface LinterRunnerConfig {\n /** Custom engines to use (defaults to all available) */\n engines?: LinterEngine[];\n\n /** Maximum buffer size for CLI output (default: 10MB) */\n maxBuffer?: number;\n\n /** Timeout for each linter run in ms (default: 60000) */\n timeout?: number;\n}\n\n/**\n * Linter runner.\n *\n * Executes linters via CLI subprocess and parses their JSON output.\n */\nexport class LinterRunner {\n private engines: Map<string, LinterEngine>;\n private maxBuffer: number;\n private timeout: number;\n\n constructor(config: LinterRunnerConfig = {}) {\n const engineList = config.engines ?? LINTER_ENGINES;\n this.engines = new Map(engineList.map(e => [e.id, e]));\n this.maxBuffer = config.maxBuffer ?? 10 * 1024 * 1024; // 10MB\n this.timeout = config.timeout ?? 60000; // 60s\n }\n\n /**\n * Find project root for a file based on engine's config files.\n */\n findProjectRoot(file: string, engine: LinterEngine): string {\n let dir = dirname(file);\n const root = '/';\n\n while (dir !== root && dir.length > 1) {\n for (const configFile of engine.configFiles) {\n if (existsSync(join(dir, configFile))) {\n return dir;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) {\n break;\n }\n dir = parent;\n }\n\n // Fallback to file's directory\n return dirname(file);\n }\n\n /**\n * Run a specific linter on files.\n */\n // eslint-disable-next-line sonarjs/cognitive-complexity -- Complex orchestration of linter execution with error handling, config discovery, and result processing\n async runEngine(engineId: string, files: string[]): Promise<LinterResult> {\n const startTime = Date.now();\n const engine = this.engines.get(engineId);\n\n if (!engine) {\n return {\n engineId,\n findings: [],\n files,\n durationMs: Date.now() - startTime,\n error: `Unknown engine: ${engineId}`,\n };\n }\n\n // Filter files by extension\n const relevantFiles = files.filter(f =>\n engine.extensions.includes(extname(f))\n );\n\n if (relevantFiles.length === 0) {\n return {\n engineId,\n findings: [],\n files: [],\n durationMs: Date.now() - startTime,\n };\n }\n\n // Group files by project root\n const filesByRoot = new Map<string, string[]>();\n for (const file of relevantFiles) {\n const root = this.findProjectRoot(file, engine);\n const group = filesByRoot.get(root) ?? [];\n group.push(file);\n filesByRoot.set(root, group);\n }\n\n // Run linter for each project root\n const allFindings: ReviewFinding[] = [];\n const errors: string[] = [];\n\n for (const [projectRoot, groupFiles] of filesByRoot) {\n try {\n // Make file paths relative to project root\n // Input files might be relative to a different cwd (e.g., monorepo root)\n // We need them relative to the project root where we'll run the linter\n const relativeFiles = groupFiles.map(f => {\n // If the file path starts with the project root, strip it\n if (f.startsWith(projectRoot + '/')) {\n return f.slice(projectRoot.length + 1);\n }\n // Otherwise, try to make it relative\n return relative(projectRoot, f) || f;\n });\n\n const command = engine.buildCommand(relativeFiles, projectRoot);\n let output: string;\n\n try {\n output = execSync(command, {\n cwd: projectRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n maxBuffer: this.maxBuffer,\n timeout: this.timeout,\n });\n } catch (execError: unknown) {\n // Most linters exit with non-zero when they find issues\n // Try to parse stdout anyway\n const err = execError as { stdout?: string; stderr?: string; message?: string };\n if (err.stdout) {\n output = err.stdout;\n } else {\n // Real error, not just lint findings\n errors.push(`${projectRoot}: ${err.message ?? 'Unknown error'}`);\n continue;\n }\n }\n\n // Parse output\n if (output && output.trim()) {\n try {\n const findings = engine.parseOutput(output, projectRoot);\n allFindings.push(...findings);\n } catch (parseError: unknown) {\n const err = parseError as { message?: string };\n errors.push(`${projectRoot}: Failed to parse output: ${err.message ?? 'Unknown error'}`);\n }\n }\n } catch (error: unknown) {\n const err = error as { message?: string };\n errors.push(`${projectRoot}: ${err.message ?? 'Unknown error'}`);\n }\n }\n\n return {\n engineId,\n findings: allFindings,\n files: relevantFiles,\n durationMs: Date.now() - startTime,\n error: errors.length > 0 ? errors.join('; ') : undefined,\n };\n }\n\n /**\n * Run all applicable linters on files.\n */\n async runAll(files: string[]): Promise<LinterResult[]> {\n const results: LinterResult[] = [];\n\n // Find which engines have relevant files\n const enginesWithFiles = new Set<string>();\n for (const file of files) {\n const ext = extname(file);\n for (const engine of this.engines.values()) {\n if (engine.extensions.includes(ext)) {\n enginesWithFiles.add(engine.id);\n }\n }\n }\n\n // Run each engine\n for (const engineId of enginesWithFiles) {\n // eslint-disable-next-line no-await-in-loop -- Sequential engine execution for resource control\n const result = await this.runEngine(engineId, files);\n results.push(result);\n }\n\n return results;\n }\n\n /**\n * Get all findings from multiple engine results.\n */\n static collectFindings(results: LinterResult[]): ReviewFinding[] {\n return results.flatMap(r => r.findings);\n }\n}\n\n/**\n * Quick helper to run all linters on files.\n */\nexport async function runLinters(files: string[]): Promise<ReviewFinding[]> {\n const runner = new LinterRunner();\n const results = await runner.runAll(files);\n return LinterRunner.collectFindings(results);\n}\n\n/**\n * Quick helper to run a specific linter.\n */\nexport async function runLinter(engineId: string, files: string[]): Promise<ReviewFinding[]> {\n const runner = new LinterRunner();\n const result = await runner.runEngine(engineId, files);\n return result.findings;\n}\n"]}
1
+ {"version":3,"sources":["../src/engine-registry.ts","../src/deduplication.ts","../src/engines/eslint.ts","../src/engines/ruff.ts","../src/engines/index.ts","../src/runner.ts"],"names":["mapSeverity"],"mappings":";;;;;;AAmBO,IAAM,eAAA,GAAmD;AAAA;AAAA,EAE9D,MAAA,EAAQ;AAAA,IACN,EAAA,EAAI,QAAA;AAAA,IACJ,IAAA,EAAM,QAAA;AAAA,IACN,QAAA,EAAU,CAAC,YAAA,EAAc,YAAA,EAAc,OAAO,KAAK,CAAA;AAAA,IACnD,IAAA,EAAM;AAAA,GACR;AAAA;AAAA,EAGA,GAAA,EAAK;AAAA,IACH,EAAA,EAAI,KAAA;AAAA,IACJ,IAAA,EAAM,qBAAA;AAAA,IACN,QAAA,EAAU,CAAC,YAAA,EAAc,KAAK,CAAA;AAAA,IAC9B,IAAA,EAAM;AAAA,GACR;AAAA;AAAA,EAGA,IAAA,EAAM;AAAA,IACJ,EAAA,EAAI,MAAA;AAAA,IACJ,IAAA,EAAM,MAAA;AAAA,IACN,QAAA,EAAU,CAAC,QAAQ,CAAA;AAAA,IACnB,IAAA,EAAM;AAAA,GACR;AAAA,EAEA,MAAA,EAAQ;AAAA,IACN,EAAA,EAAI,QAAA;AAAA,IACJ,IAAA,EAAM,QAAA;AAAA,IACN,QAAA,EAAU,CAAC,QAAQ,CAAA;AAAA,IACnB,IAAA,EAAM;AAAA,GACR;AAAA;AAAA,EAGA,MAAA,EAAQ;AAAA,IACN,EAAA,EAAI,QAAA;AAAA,IACJ,IAAA,EAAM,QAAA;AAAA,IACN,QAAA,EAAU,CAAC,QAAQ,CAAA;AAAA,IACnB,IAAA,EAAM;AAAA,GACR;AAAA;AAAA,EAGA,QAAA,EAAU;AAAA,IACR,EAAA,EAAI,UAAA;AAAA,IACJ,IAAA,EAAM,eAAA;AAAA,IACN,QAAA,EAAU,CAAC,IAAI,CAAA;AAAA,IACf,IAAA,EAAM;AAAA,GACR;AAAA;AAAA,EAGA,MAAA,EAAQ;AAAA,IACN,EAAA,EAAI,QAAA;AAAA,IACJ,IAAA,EAAM,QAAA;AAAA,IACN,QAAA,EAAU,CAAC,MAAM,CAAA;AAAA,IACjB,IAAA,EAAM;AAAA,GACR;AAAA;AAAA,EAGA,OAAA,EAAS;AAAA,IACP,EAAA,EAAI,SAAA;AAAA,IACJ,IAAA,EAAM,SAAA;AAAA,IACN,QAAA,EAAU,CAAC,MAAM,CAAA;AAAA,IACjB,IAAA,EAAM;AAAA,GACR;AAAA;AAAA,EAGA,OAAA,EAAS;AAAA,IACP,EAAA,EAAI,SAAA;AAAA,IACJ,IAAA,EAAM,SAAA;AAAA,IACN,UAAU,CAAC,YAAA,EAAc,cAAc,QAAA,EAAU,IAAA,EAAM,QAAQ,MAAM,CAAA;AAAA,IACrE,IAAA,EAAM;AAAA,GACR;AAAA,EAEA,MAAA,EAAQ;AAAA,IACN,EAAA,EAAI,QAAA;AAAA,IACJ,IAAA,EAAM,QAAA;AAAA,IACN,UAAU,CAAC,YAAA,EAAc,cAAc,QAAA,EAAU,IAAA,EAAM,QAAQ,MAAM,CAAA;AAAA,IACrE,IAAA,EAAM;AAAA,GACR;AAAA;AAAA,EAGA,UAAA,EAAY;AAAA,IACV,EAAA,EAAI,YAAA;AAAA,IACJ,IAAA,EAAM,aAAA;AAAA,IACN,UAAU,CAAC,YAAA,EAAc,cAAc,QAAA,EAAU,IAAA,EAAM,QAAQ,MAAM,CAAA;AAAA,IACrE,IAAA,EAAM;AAAA;AAEV;AAKO,SAAS,UAAU,QAAA,EAA+C;AACvE,EAAA,OAAO,gBAAgB,QAAQ,CAAA;AACjC;AAKO,SAAS,sBAAsB,QAAA,EAAqC;AACzE,EAAA,OAAO,MAAA,CAAO,MAAA,CAAO,eAAe,CAAA,CAAE,MAAA;AAAA,IAAO,CAAC,MAAA,KAC5C,MAAA,CAAO,QAAA,CAAS,SAAS,QAAQ;AAAA,GACnC;AACF;AAYO,SAAS,sBAAsB,QAAA,EAA0B;AAC9D,EAAA,MAAM,MAAA,GAAS,UAAU,QAAQ,CAAA;AACjC,EAAA,IAAI,CAAC,MAAA,EAAQ;AAEX,IAAA,OAAO,GAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAA,GAAuC;AAAA,IAC3C,QAAA,EAAU,CAAA;AAAA,IACV,MAAA,EAAQ,CAAA;AAAA,IACR,IAAA,EAAM,CAAA;AAAA,IACN,GAAA,EAAK,CAAA;AAAA,IACL,GAAA,EAAK;AAAA,GACP;AAEA,EAAA,OAAO,YAAA,CAAa,MAAA,CAAO,IAAI,CAAA,IAAK,GAAA;AACtC;AC9HO,SAAS,mBAAA,CACd,SACA,WAAA,EACQ;AACR,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC3C,EAAA,MAAM,KAAA,GAAQ,CAAC,OAAA,CAAQ,MAAA,EAAQ,QAAQ,IAAA,EAAM,MAAA,CAAO,UAAU,CAAA;AAE9D,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,KAAA,CAAM,KAAK,WAAW,CAAA;AAAA,EACxB;AAEA,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA;AAC5B,EAAA,OAAO,WAAW,MAAM,CAAA,CAAE,OAAO,KAAK,CAAA,CAAE,OAAO,KAAK,CAAA;AACtD;AAKO,SAAS,YAAY,OAAA,EAAyB;AACnD,EAAA,OAAO,UAAA,CAAW,MAAM,CAAA,CAAE,MAAA,CAAO,QAAQ,IAAA,EAAM,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA;AAC/D;AAOA,SAAS,kBAAkB,QAAA,EAAmC;AAC5D,EAAA,MAAM,OAAA,GAA2C;AAAA,IAC/C,OAAA,EAAS,CAAA;AAAA,IACT,IAAA,EAAM,CAAA;AAAA,IACN,MAAA,EAAQ,CAAA;AAAA,IACR,GAAA,EAAK,CAAA;AAAA,IACL,IAAA,EAAM;AAAA,GACR;AACA,EAAA,OAAO,OAAA,CAAQ,QAAQ,CAAA,IAAK,CAAA;AAC9B;AAeO,SAAS,oBAAoB,QAAA,EAA4C;AAE9E,EAAA,MAAM,cAAA,uBAAqB,GAAA,EAA6B;AAExD,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,MAAM,WAAA,GAAc,oBAAoB,OAAO,CAAA;AAE/C,IAAA,IAAI,CAAC,cAAA,CAAe,GAAA,CAAI,WAAW,CAAA,EAAG;AACpC,MAAA,cAAA,CAAe,GAAA,CAAI,WAAA,EAAa,EAAE,CAAA;AAAA,IACpC;AAEA,IAAA,cAAA,CAAe,GAAA,CAAI,WAAW,CAAA,CAAG,IAAA,CAAK,OAAO,CAAA;AAAA,EAC/C;AAGA,EAAA,MAAM,eAAgC,EAAC;AAEvC,EAAA,KAAA,MAAW,KAAA,IAAS,cAAA,CAAe,MAAA,EAAO,EAAG;AAC3C,IAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AAEtB,MAAA,YAAA,CAAa,IAAA,CAAK,KAAA,CAAM,CAAC,CAAE,CAAA;AAC3B,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAElC,MAAA,MAAM,SAAA,GAAY,qBAAA,CAAsB,CAAA,CAAE,MAAM,CAAA;AAChD,MAAA,MAAM,SAAA,GAAY,qBAAA,CAAsB,CAAA,CAAE,MAAM,CAAA;AAEhD,MAAA,IAAI,cAAc,SAAA,EAAW;AAC3B,QAAA,OAAO,SAAA,GAAY,SAAA;AAAA,MACrB;AAGA,MAAA,MAAM,SAAA,GAAY,iBAAA,CAAkB,CAAA,CAAE,QAAQ,CAAA;AAC9C,MAAA,MAAM,SAAA,GAAY,iBAAA,CAAkB,CAAA,CAAE,QAAQ,CAAA;AAE9C,MAAA,OAAO,SAAA,GAAY,SAAA;AAAA,IACrB,CAAC,CAAA;AAGD,IAAA,MAAM,IAAA,GAAO,OAAO,CAAC,CAAA;AACrB,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,YAAA,CAAa,KAAK,IAAI,CAAA;AAAA,IACxB;AAAA,EACF;AAEA,EAAA,OAAO,YAAA;AACT;AAWA,eAAsB,+BAAA,CACpB,UACA,UAAA,EAC0B;AAE1B,EAAA,MAAM,cAAA,uBAAqB,GAAA,EAA6B;AAExD,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAE9B,IAAA,MAAM,OAAA,GAAU,MAAM,UAAA,CAAW,OAAO,CAAA;AACxC,IAAA,MAAM,WAAA,GAAc,YAAY,OAAO,CAAA;AACvC,IAAA,MAAM,WAAA,GAAc,mBAAA,CAAoB,OAAA,EAAS,WAAW,CAAA;AAE5D,IAAA,IAAI,CAAC,cAAA,CAAe,GAAA,CAAI,WAAW,CAAA,EAAG;AACpC,MAAA,cAAA,CAAe,GAAA,CAAI,WAAA,EAAa,EAAE,CAAA;AAAA,IACpC;AAEA,IAAA,cAAA,CAAe,GAAA,CAAI,WAAW,CAAA,CAAG,IAAA,CAAK,OAAO,CAAA;AAAA,EAC/C;AAGA,EAAA,MAAM,eAAgC,EAAC;AAEvC,EAAA,KAAA,MAAW,KAAA,IAAS,cAAA,CAAe,MAAA,EAAO,EAAG;AAC3C,IAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,MAAA,YAAA,CAAa,IAAA,CAAK,KAAA,CAAM,CAAC,CAAE,CAAA;AAC3B,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAClC,MAAA,MAAM,SAAA,GAAY,qBAAA,CAAsB,CAAA,CAAE,MAAM,CAAA;AAChD,MAAA,MAAM,SAAA,GAAY,qBAAA,CAAsB,CAAA,CAAE,MAAM,CAAA;AAEhD,MAAA,IAAI,cAAc,SAAA,EAAW;AAC3B,QAAA,OAAO,SAAA,GAAY,SAAA;AAAA,MACrB;AAEA,MAAA,MAAM,SAAA,GAAY,iBAAA,CAAkB,CAAA,CAAE,QAAQ,CAAA;AAC9C,MAAA,MAAM,SAAA,GAAY,iBAAA,CAAkB,CAAA,CAAE,QAAQ,CAAA;AAE9C,MAAA,OAAO,SAAA,GAAY,SAAA;AAAA,IACrB,CAAC,CAAA;AAED,IAAA,MAAM,IAAA,GAAO,OAAO,CAAC,CAAA;AACrB,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,YAAA,CAAa,KAAK,IAAI,CAAA;AAAA,IACxB;AAAA,EACF;AAEA,EAAA,OAAO,YAAA;AACT;;;ACtLA,SAAS,YAAY,cAAA,EAAyC;AAC5D,EAAA,IAAI,mBAAmB,CAAA,EAAG;AACxB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,mBAAmB,CAAA,EAAG;AACxB,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,OAAO,MAAA;AACT;AA2BO,IAAM,YAAA,GAA6B;AAAA,EACxC,EAAA,EAAI,QAAA;AAAA,EACJ,IAAA,EAAM,QAAA;AAAA,EACN,UAAA,EAAY,CAAC,KAAA,EAAO,MAAA,EAAQ,OAAO,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,MAAM,CAAA;AAAA,EACzE,WAAA,EAAa;AAAA,IACX,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,mBAAA;AAAA,IACA,cAAA;AAAA,IACA,eAAA;AAAA,IACA,gBAAA;AAAA,IACA,eAAA;AAAA,IACA,gBAAA;AAAA,IACA;AAAA,GACF;AAAA,EAEA,YAAA,CAAa,OAAiB,IAAA,EAAwB;AACpD,IAAA,OAAO,CAAC,KAAA,EAAO,QAAA,EAAU,UAAA,EAAY,MAAA,EAAQ,GAAG,KAAK,CAAA;AAAA,EACvD,CAAA;AAAA,EAEA,WAAA,CAAY,MAAc,IAAA,EAA+B;AACvD,IAAA,MAAM,OAAA,GAA8B,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AACnD,IAAA,MAAM,WAA4B,EAAC;AAEnC,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,KAAA,MAAW,GAAA,IAAO,OAAO,QAAA,EAAU;AACjC,QAAA,QAAA,CAAS,IAAA,CAAK;AAAA,UACZ,EAAA,EAAI,CAAA,OAAA,EAAU,MAAA,CAAO,QAAQ,CAAA,CAAA,EAAI,GAAA,CAAI,MAAA,IAAU,SAAS,CAAA,CAAA,EAAI,GAAA,CAAI,IAAI,CAAA,CAAA,EAAI,IAAI,MAAM,CAAA,CAAA;AAAA,UAClF,MAAA,EAAQ,IAAI,MAAA,IAAU,gBAAA;AAAA,UACtB,IAAA,EAAM,cAAA;AAAA,UACN,QAAA,EAAU,WAAA,CAAY,GAAA,CAAI,QAAQ,CAAA;AAAA,UAClC,UAAA,EAAY,SAAA;AAAA,UACZ,MAAM,MAAA,CAAO,QAAA;AAAA,UACb,IAAA,EAAM,IAAI,IAAA,IAAQ,CAAA;AAAA,UAClB,MAAA,EAAQ,IAAI,MAAA,IAAU,CAAA;AAAA,UACtB,OAAA,EAAS,GAAA,CAAI,OAAA,IAAW,GAAA,CAAI,IAAA,IAAQ,CAAA;AAAA,UACpC,SAAS,GAAA,CAAI,OAAA;AAAA,UACb,MAAA,EAAQ,QAAA;AAAA,UACR,MAAA,EAAQ,WAAA;AAAA,UACR,KAAA,EAAO,GAAA,CAAI,GAAA,GAAM,OAAA,GAAU,QAAA;AAAA,UAC3B,SAAA,EAAW,CAAC,CAAC,GAAA,CAAI;AAAA,SAClB,CAAA;AAAA,MACH;AAAA,IACF;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AAAA,EAEA,MAAM,WAAA,GAAgC;AACpC,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,OAAO,eAAe,CAAA;AACjD,MAAA,QAAA,CAAS,sBAAA,EAAwB,EAAE,KAAA,EAAO,MAAA,EAAQ,CAAA;AAClD,MAAA,OAAO,IAAA;AAAA,IACT,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACF;;;AC/DA,SAASA,aAAY,IAAA,EAA+B;AAClD,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,CAAC,EAAE,WAAA,EAAY;AAE1C,EAAA,QAAQ,MAAA;AAAQ,IACd,KAAK,GAAA;AAAA;AAAA,IACL,KAAK,GAAA;AACH,MAAA,OAAO,MAAA;AAAA,IACT,KAAK,GAAA;AAAA;AAAA,IACL,KAAK,GAAA;AACH,MAAA,OAAO,QAAA;AAAA,IACT,KAAK,GAAA;AAAA;AAAA,IACL,KAAK,GAAA;AACH,MAAA,OAAO,KAAA;AAAA,IACT;AACE,MAAA,OAAO,QAAA;AAAA;AAEb;AAKO,IAAM,UAAA,GAA2B;AAAA,EACtC,EAAA,EAAI,MAAA;AAAA,EACJ,IAAA,EAAM,MAAA;AAAA,EACN,UAAA,EAAY,CAAC,KAAA,EAAO,MAAM,CAAA;AAAA,EAC1B,WAAA,EAAa;AAAA,IACX,gBAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF;AAAA,EAEA,YAAA,CAAa,OAAiB,IAAA,EAAwB;AACpD,IAAA,OAAO,CAAC,MAAA,EAAQ,OAAA,EAAS,iBAAA,EAAmB,MAAA,EAAQ,GAAG,KAAK,CAAA;AAAA,EAC9D,CAAA;AAAA,EAEA,WAAA,CAAY,MAAc,IAAA,EAA+B;AACvD,IAAA,MAAM,WAAA,GAAgC,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AACrD,IAAA,MAAM,WAA4B,EAAC;AAEnC,IAAA,KAAA,MAAW,KAAK,WAAA,EAAa;AAC3B,MAAA,QAAA,CAAS,IAAA,CAAK;AAAA,QACZ,EAAA,EAAI,CAAA,KAAA,EAAQ,CAAA,CAAE,QAAQ,IAAI,CAAA,CAAE,IAAI,CAAA,CAAA,EAAI,CAAA,CAAE,QAAA,CAAS,GAAG,CAAA,CAAA,EAAI,CAAA,CAAE,SAAS,MAAM,CAAA,CAAA;AAAA,QACvE,QAAQ,CAAA,CAAE,IAAA;AAAA,QACV,IAAA,EAAM,cAAA;AAAA,QACN,QAAA,EAAUA,YAAAA,CAAY,CAAA,CAAE,IAAI,CAAA;AAAA,QAC5B,UAAA,EAAY,SAAA;AAAA,QACZ,MAAM,CAAA,CAAE,QAAA;AAAA,QACR,IAAA,EAAM,EAAE,QAAA,CAAS,GAAA;AAAA,QACjB,MAAA,EAAQ,EAAE,QAAA,CAAS,MAAA;AAAA,QACnB,OAAA,EAAS,EAAE,YAAA,CAAa,GAAA;AAAA,QACxB,SAAS,CAAA,CAAE,OAAA;AAAA,QACX,MAAA,EAAQ,MAAA;AAAA,QACR,MAAA,EAAQ,WAAA;AAAA,QACR,KAAA,EAAO,CAAA,CAAE,GAAA,GAAM,OAAA,GAAU,QAAA;AAAA,QACzB,SAAA,EAAW,CAAC,CAAC,CAAA,CAAE;AAAA,OAChB,CAAA;AAAA,IACH;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AAAA,EAEA,MAAM,WAAA,GAAgC;AACpC,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,OAAO,eAAe,CAAA;AACjD,MAAA,QAAA,CAAS,gBAAA,EAAkB,EAAE,KAAA,EAAO,MAAA,EAAQ,CAAA;AAC5C,MAAA,OAAO,IAAA;AAAA,IACT,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACF;;;AC9FO,IAAM,cAAA,GAAiC;AAAA,EAC5C,YAAA;AAAA,EACA;AACF;AAKO,SAAS,gBAAgB,EAAA,EAAsC;AACpE,EAAA,OAAO,cAAA,CAAe,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,EAAE,CAAA;AAC7C;AAKO,SAAS,uBAAuB,QAAA,EAA4C;AACjF,EAAA,MAAM,MAAM,QAAA,CAAS,KAAA,CAAM,QAAA,CAAS,WAAA,CAAY,GAAG,CAAC,CAAA;AACpD,EAAA,OAAO,eAAe,IAAA,CAAK,CAAA,CAAA,KAAK,EAAE,UAAA,CAAW,QAAA,CAAS,GAAG,CAAC,CAAA;AAC5D;ACHO,IAAM,eAAN,MAAmB;AAAA,EAChB,OAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EAER,WAAA,CAAY,MAAA,GAA6B,EAAC,EAAG;AAC3C,IAAA,MAAM,UAAA,GAAa,OAAO,OAAA,IAAW,cAAA;AACrC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAI,GAAA,CAAI,UAAA,CAAW,GAAA,CAAI,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,EAAA,EAAI,CAAC,CAAC,CAAC,CAAA;AACrD,IAAA,IAAA,CAAK,SAAA,GAAY,MAAA,CAAO,SAAA,IAAa,EAAA,GAAK,IAAA,GAAO,IAAA;AACjD,IAAA,IAAA,CAAK,OAAA,GAAU,OAAO,OAAA,IAAW,GAAA;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAA,CAAgB,MAAc,MAAA,EAA8B;AAC1D,IAAA,IAAI,GAAA,GAAM,QAAQ,IAAI,CAAA;AACtB,IAAA,MAAM,IAAA,GAAO,GAAA;AAEb,IAAA,OAAO,GAAA,KAAQ,IAAA,IAAQ,GAAA,CAAI,MAAA,GAAS,CAAA,EAAG;AACrC,MAAA,KAAA,MAAW,UAAA,IAAc,OAAO,WAAA,EAAa;AAC3C,QAAA,IAAI,UAAA,CAAW,IAAA,CAAK,GAAA,EAAK,UAAU,CAAC,CAAA,EAAG;AACrC,UAAA,OAAO,GAAA;AAAA,QACT;AAAA,MACF;AACA,MAAA,MAAM,MAAA,GAAS,QAAQ,GAAG,CAAA;AAC1B,MAAA,IAAI,WAAW,GAAA,EAAK;AAClB,QAAA;AAAA,MACF;AACA,MAAA,GAAA,GAAM,MAAA;AAAA,IACR;AAGA,IAAA,OAAO,QAAQ,IAAI,CAAA;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SAAA,CAAU,QAAA,EAAkB,KAAA,EAAwC;AACxE,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA;AAExC,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO;AAAA,QACL,QAAA;AAAA,QACA,UAAU,EAAC;AAAA,QACX,KAAA;AAAA,QACA,UAAA,EAAY,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAAA,QACzB,KAAA,EAAO,mBAAmB,QAAQ,CAAA;AAAA,OACpC;AAAA,IACF;AAGA,IAAA,MAAM,gBAAgB,KAAA,CAAM,MAAA;AAAA,MAAO,OACjC,MAAA,CAAO,UAAA,CAAW,QAAA,CAAS,OAAA,CAAQ,CAAC,CAAC;AAAA,KACvC;AAEA,IAAA,IAAI,aAAA,CAAc,WAAW,CAAA,EAAG;AAC9B,MAAA,OAAO;AAAA,QACL,QAAA;AAAA,QACA,UAAU,EAAC;AAAA,QACX,OAAO,EAAC;AAAA,QACR,UAAA,EAAY,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA,OAC3B;AAAA,IACF;AAGA,IAAA,MAAM,WAAA,uBAAkB,GAAA,EAAsB;AAC9C,IAAA,KAAA,MAAW,QAAQ,aAAA,EAAe;AAChC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,eAAA,CAAgB,IAAA,EAAM,MAAM,CAAA;AAC9C,MAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,GAAA,CAAI,IAAI,KAAK,EAAC;AACxC,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AACf,MAAA,WAAA,CAAY,GAAA,CAAI,MAAM,KAAK,CAAA;AAAA,IAC7B;AAGA,IAAA,MAAM,cAA+B,EAAC;AACtC,IAAA,MAAM,SAAmB,EAAC;AAE1B,IAAA,KAAA,MAAW,CAAC,WAAA,EAAa,UAAU,CAAA,IAAK,WAAA,EAAa;AACnD,MAAA,IAAI;AAIF,QAAA,MAAM,aAAA,GAAgB,UAAA,CAAW,GAAA,CAAI,CAAA,CAAA,KAAK;AAExC,UAAA,IAAI,CAAA,CAAE,UAAA,CAAW,WAAA,GAAc,GAAG,CAAA,EAAG;AACnC,YAAA,OAAO,CAAA,CAAE,KAAA,CAAM,WAAA,CAAY,MAAA,GAAS,CAAC,CAAA;AAAA,UACvC;AAEA,UAAA,OAAO,QAAA,CAAS,WAAA,EAAa,CAAC,CAAA,IAAK,CAAA;AAAA,QACrC,CAAC,CAAA;AAED,QAAA,MAAM,CAAC,KAAK,GAAG,IAAI,IAAI,MAAA,CAAO,YAAA,CAAa,eAAe,WAAW,CAAA;AACrE,QAAA,MAAM,IAAA,GAAO,SAAA,CAAU,GAAA,EAAM,IAAA,EAAM;AAAA,UACjC,GAAA,EAAK,WAAA;AAAA,UACL,QAAA,EAAU,OAAA;AAAA,UACV,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,SAAS,IAAA,CAAK;AAAA,SACf,CAAA;AAED,QAAA,IAAI,KAAK,KAAA,EAAO;AACd,UAAA,MAAA,CAAO,KAAK,CAAA,EAAG,WAAW,KAAK,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AACnD,UAAA;AAAA,QACF;AAGA,QAAA,MAAM,MAAA,GAAS,KAAK,MAAA,IAAU,EAAA;AAG9B,QAAA,IAAI,MAAA,IAAU,MAAA,CAAO,IAAA,EAAK,EAAG;AAC3B,UAAA,IAAI;AACF,YAAA,MAAM,QAAA,GAAW,MAAA,CAAO,WAAA,CAAY,MAAA,EAAQ,WAAW,CAAA;AACvD,YAAA,WAAA,CAAY,IAAA,CAAK,GAAG,QAAQ,CAAA;AAAA,UAC9B,SAAS,UAAA,EAAqB;AAC5B,YAAA,MAAM,GAAA,GAAM,UAAA;AACZ,YAAA,MAAA,CAAO,KAAK,CAAA,EAAG,WAAW,6BAA6B,GAAA,CAAI,OAAA,IAAW,eAAe,CAAA,CAAE,CAAA;AAAA,UACzF;AAAA,QACF;AAAA,MACF,SAAS,KAAA,EAAgB;AACvB,QAAA,MAAM,GAAA,GAAM,KAAA;AACZ,QAAA,MAAA,CAAO,KAAK,CAAA,EAAG,WAAW,KAAK,GAAA,CAAI,OAAA,IAAW,eAAe,CAAA,CAAE,CAAA;AAAA,MACjE;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA,QAAA,EAAU,WAAA;AAAA,MACV,KAAA,EAAO,aAAA;AAAA,MACP,UAAA,EAAY,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAAA,MACzB,OAAO,MAAA,CAAO,MAAA,GAAS,IAAI,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,GAAI;AAAA,KACjD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,KAAA,EAA0C;AACrD,IAAA,MAAM,UAA0B,EAAC;AAGjC,IAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAY;AACzC,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,MAAM,GAAA,GAAM,QAAQ,IAAI,CAAA;AACxB,MAAA,KAAA,MAAW,MAAA,IAAU,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAO,EAAG;AAC1C,QAAA,IAAI,MAAA,CAAO,UAAA,CAAW,QAAA,CAAS,GAAG,CAAA,EAAG;AACnC,UAAA,gBAAA,CAAiB,GAAA,CAAI,OAAO,EAAE,CAAA;AAAA,QAChC;AAAA,MACF;AAAA,IACF;AAGA,IAAA,KAAA,MAAW,YAAY,gBAAA,EAAkB;AAEvC,MAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,SAAA,CAAU,UAAU,KAAK,CAAA;AACnD,MAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AAAA,IACrB;AAEA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,gBAAgB,OAAA,EAA0C;AAC/D,IAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,CAAA,CAAA,KAAK,CAAA,CAAE,QAAQ,CAAA;AAAA,EACxC;AACF;AAKA,eAAsB,WAAW,KAAA,EAA2C;AAC1E,EAAA,MAAM,MAAA,GAAS,IAAI,YAAA,EAAa;AAChC,EAAA,MAAM,OAAA,GAAU,MAAM,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA;AACzC,EAAA,OAAO,YAAA,CAAa,gBAAgB,OAAO,CAAA;AAC7C;AAKA,eAAsB,SAAA,CAAU,UAAkB,KAAA,EAA2C;AAC3F,EAAA,MAAM,MAAA,GAAS,IAAI,YAAA,EAAa;AAChC,EAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,SAAA,CAAU,UAAU,KAAK,CAAA;AACrD,EAAA,OAAO,MAAA,CAAO,QAAA;AAChB","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/review-heuristic/engine-registry\n * Registry of heuristic analysis engines with type mappings.\n *\n * Maps specific tools (eslint, ruff, clippy) to engine types (linter, compiler, sast).\n * Used for engine type priority deduplication.\n */\n\nimport type { HeuristicEngine } from '@kb-labs/review-contracts';\n\n/**\n * Registry of all supported heuristic engines.\n *\n * Engine types determine priority in deduplication:\n * - compiler (1): TypeScript compiler, rustc, go build\n * - linter (2): ESLint, Ruff, golangci-lint, Clippy, RuboCop\n * - sast (3): Semgrep, CodeQL, Bandit\n * - ast (4): tree-sitter (read-only AST analysis)\n */\nexport const ENGINE_REGISTRY: Record<string, HeuristicEngine> = {\n // JavaScript/TypeScript linters\n eslint: {\n id: 'eslint',\n name: 'ESLint',\n language: ['typescript', 'javascript', 'tsx', 'jsx'],\n type: 'linter',\n },\n\n // TypeScript compiler\n tsc: {\n id: 'tsc',\n name: 'TypeScript Compiler',\n language: ['typescript', 'tsx'],\n type: 'compiler',\n },\n\n // Python linters\n ruff: {\n id: 'ruff',\n name: 'Ruff',\n language: ['python'],\n type: 'linter',\n },\n\n pylint: {\n id: 'pylint',\n name: 'Pylint',\n language: ['python'],\n type: 'linter',\n },\n\n // Python SAST\n bandit: {\n id: 'bandit',\n name: 'Bandit',\n language: ['python'],\n type: 'sast',\n },\n\n // Go linter\n golangci: {\n id: 'golangci',\n name: 'golangci-lint',\n language: ['go'],\n type: 'linter',\n },\n\n // Rust linter\n clippy: {\n id: 'clippy',\n name: 'Clippy',\n language: ['rust'],\n type: 'linter',\n },\n\n // Ruby linter\n rubocop: {\n id: 'rubocop',\n name: 'RuboCop',\n language: ['ruby'],\n type: 'linter',\n },\n\n // Multi-language SAST\n semgrep: {\n id: 'semgrep',\n name: 'Semgrep',\n language: ['typescript', 'javascript', 'python', 'go', 'rust', 'ruby'],\n type: 'sast',\n },\n\n codeql: {\n id: 'codeql',\n name: 'CodeQL',\n language: ['typescript', 'javascript', 'python', 'go', 'rust', 'ruby'],\n type: 'sast',\n },\n\n // AST-based analysis (read-only)\n treesitter: {\n id: 'treesitter',\n name: 'Tree-sitter',\n language: ['typescript', 'javascript', 'python', 'go', 'rust', 'ruby'],\n type: 'ast',\n },\n};\n\n/**\n * Get engine by ID.\n */\nexport function getEngine(engineId: string): HeuristicEngine | undefined {\n return ENGINE_REGISTRY[engineId];\n}\n\n/**\n * Get all engines supporting a language.\n */\nexport function getEnginesForLanguage(language: string): HeuristicEngine[] {\n return Object.values(ENGINE_REGISTRY).filter((engine) =>\n engine.language.includes(language)\n );\n}\n\n/**\n * Get engine type priority for deduplication.\n *\n * Lower number = higher priority (kept in deduplication).\n * - compiler (1) - highest priority\n * - linter (2)\n * - sast (3)\n * - ast (4)\n * - llm (5) - lowest priority\n */\nexport function getEngineTypePriority(engineId: string): number {\n const engine = getEngine(engineId);\n if (!engine) {\n // Unknown engine, treat as lowest priority\n return 999;\n }\n\n const typePriority: Record<string, number> = {\n compiler: 1,\n linter: 2,\n sast: 3,\n ast: 4,\n llm: 5,\n };\n\n return typePriority[engine.type] ?? 999;\n}\n","/**\n * @module @kb-labs/review-heuristic/deduplication\n * Fingerprint-based deduplication with engine type priority.\n *\n * Deduplicates findings from multiple engines using:\n * 1. Fingerprint collision detection (sha1(ruleId|file|bucket|snippetHash))\n * 2. Engine type priority (compiler > linter > sast > ast > llm)\n * 3. Severity adjustment (higher severity wins if same type)\n */\n\nimport { createHash } from 'node:crypto';\nimport type { ReviewFinding, FindingSeverity } from '@kb-labs/review-contracts';\nimport { getEngineTypePriority } from './engine-registry.js';\n\n/**\n * Generate fingerprint for a finding.\n *\n * Fingerprint = sha1(ruleId|file|bucket|snippetHash)\n * - ruleId: Rule identifier\n * - file: File path\n * - bucket: Line bucket (e.g., lines 10-19 → bucket 1)\n * - snippetHash: Hash of code snippet (optional)\n */\nexport function generateFingerprint(\n finding: ReviewFinding,\n snippetHash?: string\n): string {\n const bucket = Math.floor(finding.line / 10);\n const parts = [finding.ruleId, finding.file, bucket.toString()];\n\n if (snippetHash) {\n parts.push(snippetHash);\n }\n\n const input = parts.join('|');\n return createHash('sha1').update(input).digest('hex');\n}\n\n/**\n * Hash code snippet for fingerprint.\n */\nexport function hashSnippet(snippet: string): string {\n return createHash('sha1').update(snippet.trim()).digest('hex');\n}\n\n/**\n * Get severity weight for comparison.\n *\n * Higher number = more severe.\n */\nfunction getSeverityWeight(severity: FindingSeverity): number {\n const weights: Record<FindingSeverity, number> = {\n blocker: 5,\n high: 4,\n medium: 3,\n low: 2,\n info: 1,\n };\n return weights[severity] ?? 0;\n}\n\n/**\n * Deduplicate findings using fingerprints and engine type priority.\n *\n * Algorithm:\n * 1. Group findings by fingerprint\n * 2. For each collision group:\n * - Sort by engine type priority (compiler > linter > sast > ast > llm)\n * - If same type, sort by severity (blocker > high > medium > low > info)\n * - Keep highest priority finding, discard rest\n *\n * @param findings - All findings from all engines\n * @returns Deduplicated findings\n */\nexport function deduplicateFindings(findings: ReviewFinding[]): ReviewFinding[] {\n // Generate fingerprints\n const fingerprintMap = new Map<string, ReviewFinding[]>();\n\n for (const finding of findings) {\n const fingerprint = generateFingerprint(finding);\n\n if (!fingerprintMap.has(fingerprint)) {\n fingerprintMap.set(fingerprint, []);\n }\n\n fingerprintMap.get(fingerprint)!.push(finding);\n }\n\n // Deduplicate each collision group\n const deduplicated: ReviewFinding[] = [];\n\n for (const group of fingerprintMap.values()) {\n if (group.length === 0) {\n continue;\n }\n\n if (group.length === 1) {\n // No collision, keep as-is\n deduplicated.push(group[0]!);\n continue;\n }\n\n // Sort by priority\n const sorted = group.sort((a, b) => {\n // 1. Engine type priority (lower number = higher priority)\n const aPriority = getEngineTypePriority(a.engine);\n const bPriority = getEngineTypePriority(b.engine);\n\n if (aPriority !== bPriority) {\n return aPriority - bPriority;\n }\n\n // 2. Severity (higher severity wins)\n const aSeverity = getSeverityWeight(a.severity);\n const bSeverity = getSeverityWeight(b.severity);\n\n return bSeverity - aSeverity;\n });\n\n // Keep highest priority finding\n const best = sorted[0];\n if (best) {\n deduplicated.push(best);\n }\n }\n\n return deduplicated;\n}\n\n/**\n * Deduplicate findings with snippet-based fingerprints.\n *\n * More precise deduplication using code snippets.\n *\n * @param findings - All findings\n * @param getSnippet - Function to get code snippet for a finding\n * @returns Deduplicated findings\n */\nexport async function deduplicateFindingsWithSnippets(\n findings: ReviewFinding[],\n getSnippet: (finding: ReviewFinding) => Promise<string>\n): Promise<ReviewFinding[]> {\n // Generate fingerprints with snippets\n const fingerprintMap = new Map<string, ReviewFinding[]>();\n\n for (const finding of findings) {\n // eslint-disable-next-line no-await-in-loop -- Sequential snippet fetching for fingerprinting\n const snippet = await getSnippet(finding);\n const snippetHash = hashSnippet(snippet);\n const fingerprint = generateFingerprint(finding, snippetHash);\n\n if (!fingerprintMap.has(fingerprint)) {\n fingerprintMap.set(fingerprint, []);\n }\n\n fingerprintMap.get(fingerprint)!.push(finding);\n }\n\n // Deduplicate each collision group\n const deduplicated: ReviewFinding[] = [];\n\n for (const group of fingerprintMap.values()) {\n if (group.length === 0) {\n continue;\n }\n\n if (group.length === 1) {\n deduplicated.push(group[0]!);\n continue;\n }\n\n // Sort by priority\n const sorted = group.sort((a, b) => {\n const aPriority = getEngineTypePriority(a.engine);\n const bPriority = getEngineTypePriority(b.engine);\n\n if (aPriority !== bPriority) {\n return aPriority - bPriority;\n }\n\n const aSeverity = getSeverityWeight(a.severity);\n const bSeverity = getSeverityWeight(b.severity);\n\n return bSeverity - aSeverity;\n });\n\n const best = sorted[0];\n if (best) {\n deduplicated.push(best);\n }\n }\n\n return deduplicated;\n}\n","/**\n * @module @kb-labs/review-heuristic/engines/eslint\n * ESLint engine - runs ESLint CLI and parses JSON output.\n */\n\nimport type { ReviewFinding, FindingSeverity } from '@kb-labs/review-contracts';\nimport type { LinterEngine } from './types.js';\n\n/**\n * Map ESLint severity (1=warn, 2=error) to ReviewFinding severity.\n */\nfunction mapSeverity(eslintSeverity: number): FindingSeverity {\n if (eslintSeverity === 2) {\n return 'high';\n }\n if (eslintSeverity === 1) {\n return 'medium';\n }\n return 'info';\n}\n\n/**\n * ESLint JSON output format (per file).\n */\ninterface ESLintFileResult {\n filePath: string;\n messages: Array<{\n ruleId: string | null;\n severity: number;\n message: string;\n line: number;\n column: number;\n endLine?: number;\n endColumn?: number;\n fix?: {\n range: [number, number];\n text: string;\n };\n }>;\n errorCount: number;\n warningCount: number;\n}\n\n/**\n * ESLint engine definition.\n */\nexport const eslintEngine: LinterEngine = {\n id: 'eslint',\n name: 'ESLint',\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'],\n configFiles: [\n 'eslint.config.js',\n 'eslint.config.mjs',\n 'eslint.config.cjs',\n '.eslintrc.js',\n '.eslintrc.cjs',\n '.eslintrc.json',\n '.eslintrc.yml',\n '.eslintrc.yaml',\n '.eslintrc',\n ],\n\n buildCommand(files: string[], _cwd: string): string[] {\n return ['npx', 'eslint', '--format', 'json', ...files];\n },\n\n parseOutput(json: string, _cwd: string): ReviewFinding[] {\n const results: ESLintFileResult[] = JSON.parse(json);\n const findings: ReviewFinding[] = [];\n\n for (const result of results) {\n for (const msg of result.messages) {\n findings.push({\n id: `eslint:${result.filePath}:${msg.ruleId ?? 'unknown'}:${msg.line}:${msg.column}`,\n ruleId: msg.ruleId ?? 'eslint/unknown',\n type: 'code-quality',\n severity: mapSeverity(msg.severity),\n confidence: 'certain',\n file: result.filePath,\n line: msg.line ?? 1,\n column: msg.column ?? 1,\n endLine: msg.endLine ?? msg.line ?? 1,\n message: msg.message,\n engine: 'eslint',\n source: 'heuristic',\n scope: msg.fix ? 'local' : 'global',\n automated: !!msg.fix,\n });\n }\n }\n\n return findings;\n },\n\n async isAvailable(): Promise<boolean> {\n try {\n const { execSync } = await import('child_process');\n execSync('npx eslint --version', { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n },\n};\n","/**\n * @module @kb-labs/review-heuristic/engines/ruff\n * Ruff engine - runs Ruff CLI and parses JSON output.\n *\n * Ruff is an extremely fast Python linter written in Rust.\n */\n\nimport type { ReviewFinding, FindingSeverity } from '@kb-labs/review-contracts';\nimport type { LinterEngine } from './types.js';\n\n/**\n * Ruff JSON output format (per diagnostic).\n */\ninterface RuffDiagnostic {\n code: string;\n message: string;\n filename: string;\n location: {\n row: number;\n column: number;\n };\n end_location: {\n row: number;\n column: number;\n };\n fix?: {\n message: string;\n edits: Array<{\n content: string;\n location: { row: number; column: number };\n end_location: { row: number; column: number };\n }>;\n };\n noqa_row?: number;\n}\n\n/**\n * Map Ruff code prefix to severity.\n * E = Error, W = Warning, F = Fatal, etc.\n */\nfunction mapSeverity(code: string): FindingSeverity {\n const prefix = code.charAt(0).toUpperCase();\n\n switch (prefix) {\n case 'E': // Error\n case 'F': // Pyflakes\n return 'high';\n case 'W': // Warning\n case 'C': // Convention\n return 'medium';\n case 'I': // Isort\n case 'D': // Docstring\n return 'low';\n default:\n return 'medium';\n }\n}\n\n/**\n * Ruff engine definition.\n */\nexport const ruffEngine: LinterEngine = {\n id: 'ruff',\n name: 'Ruff',\n extensions: ['.py', '.pyi'],\n configFiles: [\n 'pyproject.toml',\n 'ruff.toml',\n '.ruff.toml',\n ],\n\n buildCommand(files: string[], _cwd: string): string[] {\n return ['ruff', 'check', '--output-format', 'json', ...files];\n },\n\n parseOutput(json: string, _cwd: string): ReviewFinding[] {\n const diagnostics: RuffDiagnostic[] = JSON.parse(json);\n const findings: ReviewFinding[] = [];\n\n for (const d of diagnostics) {\n findings.push({\n id: `ruff:${d.filename}:${d.code}:${d.location.row}:${d.location.column}`,\n ruleId: d.code,\n type: 'code-quality',\n severity: mapSeverity(d.code),\n confidence: 'certain',\n file: d.filename,\n line: d.location.row,\n column: d.location.column,\n endLine: d.end_location.row,\n message: d.message,\n engine: 'ruff',\n source: 'heuristic',\n scope: d.fix ? 'local' : 'global',\n automated: !!d.fix,\n });\n }\n\n return findings;\n },\n\n async isAvailable(): Promise<boolean> {\n try {\n const { execSync } = await import('child_process');\n execSync('ruff --version', { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n },\n};\n","/**\n * @module @kb-labs/review-heuristic/engines\n * Linter engine definitions.\n */\n\nexport type { LinterEngine, LinterResult } from './types.js';\nexport { eslintEngine } from './eslint.js';\nexport { ruffEngine } from './ruff.js';\n\nimport { eslintEngine } from './eslint.js';\nimport { ruffEngine } from './ruff.js';\nimport type { LinterEngine } from './types.js';\n\n/**\n * All available linter engines.\n */\nexport const LINTER_ENGINES: LinterEngine[] = [\n eslintEngine,\n ruffEngine,\n];\n\n/**\n * Get engine by ID.\n */\nexport function getLinterEngine(id: string): LinterEngine | undefined {\n return LINTER_ENGINES.find(e => e.id === id);\n}\n\n/**\n * Get engine for file extension.\n */\nexport function getLinterEngineForFile(filePath: string): LinterEngine | undefined {\n const ext = filePath.slice(filePath.lastIndexOf('.'));\n return LINTER_ENGINES.find(e => e.extensions.includes(ext));\n}\n","/**\n * @module @kb-labs/review-heuristic/runner\n * Linter runner - executes linters via CLI and collects findings.\n */\n\nimport { spawnSync } from 'child_process';\nimport { existsSync } from 'fs';\nimport { dirname, join, extname, relative } from 'path';\nimport type { ReviewFinding } from '@kb-labs/review-contracts';\nimport type { LinterEngine, LinterResult } from './engines/types.js';\nimport { LINTER_ENGINES } from './engines/index.js';\n\n/**\n * Linter runner configuration.\n */\nexport interface LinterRunnerConfig {\n /** Custom engines to use (defaults to all available) */\n engines?: LinterEngine[];\n\n /** Maximum buffer size for CLI output (default: 10MB) */\n maxBuffer?: number;\n\n /** Timeout for each linter run in ms (default: 60000) */\n timeout?: number;\n}\n\n/**\n * Linter runner.\n *\n * Executes linters via CLI subprocess and parses their JSON output.\n */\nexport class LinterRunner {\n private engines: Map<string, LinterEngine>;\n private maxBuffer: number;\n private timeout: number;\n\n constructor(config: LinterRunnerConfig = {}) {\n const engineList = config.engines ?? LINTER_ENGINES;\n this.engines = new Map(engineList.map(e => [e.id, e]));\n this.maxBuffer = config.maxBuffer ?? 10 * 1024 * 1024; // 10MB\n this.timeout = config.timeout ?? 60000; // 60s\n }\n\n /**\n * Find project root for a file based on engine's config files.\n */\n findProjectRoot(file: string, engine: LinterEngine): string {\n let dir = dirname(file);\n const root = '/';\n\n while (dir !== root && dir.length > 1) {\n for (const configFile of engine.configFiles) {\n if (existsSync(join(dir, configFile))) {\n return dir;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) {\n break;\n }\n dir = parent;\n }\n\n // Fallback to file's directory\n return dirname(file);\n }\n\n /**\n * Run a specific linter on files.\n */\n // eslint-disable-next-line sonarjs/cognitive-complexity -- Complex orchestration of linter execution with error handling, config discovery, and result processing\n async runEngine(engineId: string, files: string[]): Promise<LinterResult> {\n const startTime = Date.now();\n const engine = this.engines.get(engineId);\n\n if (!engine) {\n return {\n engineId,\n findings: [],\n files,\n durationMs: Date.now() - startTime,\n error: `Unknown engine: ${engineId}`,\n };\n }\n\n // Filter files by extension\n const relevantFiles = files.filter(f =>\n engine.extensions.includes(extname(f))\n );\n\n if (relevantFiles.length === 0) {\n return {\n engineId,\n findings: [],\n files: [],\n durationMs: Date.now() - startTime,\n };\n }\n\n // Group files by project root\n const filesByRoot = new Map<string, string[]>();\n for (const file of relevantFiles) {\n const root = this.findProjectRoot(file, engine);\n const group = filesByRoot.get(root) ?? [];\n group.push(file);\n filesByRoot.set(root, group);\n }\n\n // Run linter for each project root\n const allFindings: ReviewFinding[] = [];\n const errors: string[] = [];\n\n for (const [projectRoot, groupFiles] of filesByRoot) {\n try {\n // Make file paths relative to project root\n // Input files might be relative to a different cwd (e.g., monorepo root)\n // We need them relative to the project root where we'll run the linter\n const relativeFiles = groupFiles.map(f => {\n // If the file path starts with the project root, strip it\n if (f.startsWith(projectRoot + '/')) {\n return f.slice(projectRoot.length + 1);\n }\n // Otherwise, try to make it relative\n return relative(projectRoot, f) || f;\n });\n\n const [cmd, ...args] = engine.buildCommand(relativeFiles, projectRoot);\n const proc = spawnSync(cmd!, args, {\n cwd: projectRoot,\n encoding: 'utf-8',\n maxBuffer: this.maxBuffer,\n timeout: this.timeout,\n });\n\n if (proc.error) {\n errors.push(`${projectRoot}: ${proc.error.message}`);\n continue;\n }\n\n // Most linters exit non-zero when they find issues but still write JSON to stdout\n const output = proc.stdout ?? '';\n\n // Parse output\n if (output && output.trim()) {\n try {\n const findings = engine.parseOutput(output, projectRoot);\n allFindings.push(...findings);\n } catch (parseError: unknown) {\n const err = parseError as { message?: string };\n errors.push(`${projectRoot}: Failed to parse output: ${err.message ?? 'Unknown error'}`);\n }\n }\n } catch (error: unknown) {\n const err = error as { message?: string };\n errors.push(`${projectRoot}: ${err.message ?? 'Unknown error'}`);\n }\n }\n\n return {\n engineId,\n findings: allFindings,\n files: relevantFiles,\n durationMs: Date.now() - startTime,\n error: errors.length > 0 ? errors.join('; ') : undefined,\n };\n }\n\n /**\n * Run all applicable linters on files.\n */\n async runAll(files: string[]): Promise<LinterResult[]> {\n const results: LinterResult[] = [];\n\n // Find which engines have relevant files\n const enginesWithFiles = new Set<string>();\n for (const file of files) {\n const ext = extname(file);\n for (const engine of this.engines.values()) {\n if (engine.extensions.includes(ext)) {\n enginesWithFiles.add(engine.id);\n }\n }\n }\n\n // Run each engine\n for (const engineId of enginesWithFiles) {\n // eslint-disable-next-line no-await-in-loop -- Sequential engine execution for resource control\n const result = await this.runEngine(engineId, files);\n results.push(result);\n }\n\n return results;\n }\n\n /**\n * Get all findings from multiple engine results.\n */\n static collectFindings(results: LinterResult[]): ReviewFinding[] {\n return results.flatMap(r => r.findings);\n }\n}\n\n/**\n * Quick helper to run all linters on files.\n */\nexport async function runLinters(files: string[]): Promise<ReviewFinding[]> {\n const runner = new LinterRunner();\n const results = await runner.runAll(files);\n return LinterRunner.collectFindings(results);\n}\n\n/**\n * Quick helper to run a specific linter.\n */\nexport async function runLinter(engineId: string, files: string[]): Promise<ReviewFinding[]> {\n const runner = new LinterRunner();\n const result = await runner.runEngine(engineId, files);\n return result.findings;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kb-labs/review-heuristic",
3
- "version": "2.94.0",
3
+ "version": "2.96.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -12,15 +12,15 @@
12
12
  "dist"
13
13
  ],
14
14
  "dependencies": {
15
- "@kb-labs/review-contracts": "2.94.0"
15
+ "@kb-labs/review-contracts": "2.96.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^24.3.3",
19
19
  "rimraf": "^6.0.1",
20
20
  "tsup": "^8.5.0",
21
21
  "typescript": "^5.6.3",
22
- "vitest": "^3.2.4",
23
- "@kb-labs/devkit": "2.94.0"
22
+ "vitest": "^3.2.6",
23
+ "@kb-labs/devkit": "2.96.0"
24
24
  },
25
25
  "engines": {
26
26
  "node": ">=20.0.0",