All files / src/sdk post-process.ts

85.71% Statements 18/21
70% Branches 7/10
100% Functions 1/1
85.71% Lines 18/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110                                                                            6x   6x 6x   6x 6x 4x                       4x 4x 1x       6x                 6x 6x       6x                 6x 6x       6x             6x                 6x    
import type { SkillDefinition } from '../config/schema.js';
import { emitDedupMetrics, emitFixGateMetrics, logger } from '../sentry.js';
import type { Finding } from '../types/index.js';
import { deduplicateFindings, mergeCrossLocationFindings } from './extract.js';
import { sanitizeFindingsSuggestedFixes } from './fix-quality.js';
import type { PromptPRContext } from './prompt-sections.js';
import type { RuntimeName } from './runtimes/index.js';
import type { AuxiliaryUsageEntry, FindingProcessingEvent } from './types.js';
import { verifyFindings } from './verify.js';
 
export interface PostProcessFindingsOptions {
  skill: SkillDefinition;
  repoPath: string;
  apiKey?: string;
  runtime?: RuntimeName;
  auxiliaryModel?: string;
  synthesisModel?: string;
  auxiliaryMaxRetries?: number;
  verifyFindings?: boolean;
  maxTurns?: number;
  abortController?: AbortController;
  pathToClaudeCodeExecutable?: string;
  prContext?: PromptPRContext;
  onFindingProcessing?: (event: FindingProcessingEvent) => void;
}
 
export interface PostProcessFindingsResult {
  findings: Finding[];
  auxiliaryUsage: AuxiliaryUsageEntry[];
}
 
/**
 * Run the shared post-analysis finding pipeline.
 */
export async function postProcessFindings(
  findings: Finding[],
  options: PostProcessFindingsOptions
): Promise<PostProcessFindingsResult> {
  const auxiliaryUsage: AuxiliaryUsageEntry[] = [];
 
  const uniqueFindings = deduplicateFindings(findings, options.onFindingProcessing);
  emitDedupMetrics(options.skill.name, findings.length, uniqueFindings.length);
 
  let currentFindings = uniqueFindings;
  if (options.verifyFindings !== false) {
    const verification = await verifyFindings(currentFindings, {
      repoPath: options.repoPath,
      skill: options.skill,
      apiKey: options.apiKey,
      runtime: options.runtime,
      model: options.auxiliaryModel,
      maxTurns: options.maxTurns,
      abortController: options.abortController,
      pathToClaudeCodeExecutable: options.pathToClaudeCodeExecutable,
      prContext: options.prContext,
      onFindingProcessing: options.onFindingProcessing,
    });
    currentFindings = verification.findings;
    if (verification.usage) {
      auxiliaryUsage.push({ agent: 'verification', usage: verification.usage });
    }
  }
 
  const mergeResult = await mergeCrossLocationFindings(currentFindings, {
    apiKey: options.apiKey,
    repoPath: options.repoPath,
    runtime: options.runtime,
    model: options.synthesisModel,
    maxRetries: options.auxiliaryMaxRetries,
    agentName: options.skill.name,
    onFindingProcessing: options.onFindingProcessing,
  });
  currentFindings = mergeResult.findings;
  Iif (mergeResult.usage) {
    auxiliaryUsage.push({ agent: 'merge', usage: mergeResult.usage });
  }
 
  const sanitized = await sanitizeFindingsSuggestedFixes(currentFindings, {
    repoPath: options.repoPath,
    apiKey: options.apiKey,
    runtime: options.runtime,
    model: options.auxiliaryModel,
    maxRetries: options.auxiliaryMaxRetries,
    agentName: options.skill.name,
    onFindingProcessing: options.onFindingProcessing,
  });
  currentFindings = sanitized.findings;
  Iif (sanitized.usage) {
    auxiliaryUsage.push({ agent: 'fix_gate', usage: sanitized.usage });
  }
 
  emitFixGateMetrics(
    options.skill.name,
    sanitized.stats.checked,
    sanitized.stats.strippedDeterministic,
    sanitized.stats.strippedSemantic,
    sanitized.stats.semanticUnavailable
  );
  Iif (sanitized.stats.checked > 0) {
    logger.info('Suggested fix quality gate', {
      'warden.fix_gate.checked': sanitized.stats.checked,
      'warden.fix_gate.stripped_deterministic': sanitized.stats.strippedDeterministic,
      'warden.fix_gate.stripped_semantic': sanitized.stats.strippedSemantic,
      'warden.fix_gate.semantic_unavailable': sanitized.stats.semanticUnavailable,
    });
  }
 
  return { findings: currentFindings, auxiliaryUsage };
}