@codraoss/core 0.9.4 → 0.9.12

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 (70) hide show
  1. package/dist/{chunk-XD6CGYHO.js → chunk-4XEMXRNG.js} +62 -39
  2. package/dist/chunk-4XEMXRNG.js.map +1 -0
  3. package/dist/{chunk-ILZUCIZH.js → chunk-EQL5OOEF.js} +7 -7
  4. package/dist/chunk-EQL5OOEF.js.map +1 -0
  5. package/dist/{chunk-RQ5S67XB.js → chunk-FKRCBUWZ.js} +8 -2
  6. package/dist/chunk-FKRCBUWZ.js.map +1 -0
  7. package/dist/{chunk-JC74XH7Q.js → chunk-GDX2ZEBA.js} +1 -1
  8. package/dist/chunk-GDX2ZEBA.js.map +1 -0
  9. package/dist/{chunk-OIQRTDYR.js → chunk-I5S3NBFL.js} +20 -4
  10. package/dist/chunk-I5S3NBFL.js.map +1 -0
  11. package/dist/{chunk-U3VKVDZ7.js → chunk-IKVUW4WC.js} +92 -19
  12. package/dist/chunk-IKVUW4WC.js.map +1 -0
  13. package/dist/{chunk-R2HRWFRH.js → chunk-JAF7JIZQ.js} +1 -1
  14. package/dist/chunk-JAF7JIZQ.js.map +1 -0
  15. package/dist/{chunk-V3RF6GQW.js → chunk-KTDBDKL5.js} +1 -1
  16. package/dist/chunk-KTDBDKL5.js.map +1 -0
  17. package/dist/{chunk-MMVQPGK3.js → chunk-ONJZXUQT.js} +112 -24
  18. package/dist/chunk-ONJZXUQT.js.map +1 -0
  19. package/dist/{chunk-PVXFURDR.js → chunk-QPPLO2YV.js} +2 -2
  20. package/dist/chunk-QPPLO2YV.js.map +1 -0
  21. package/dist/{chunk-FGXA7JNR.js → chunk-W3RY755V.js} +14 -2
  22. package/dist/chunk-W3RY755V.js.map +1 -0
  23. package/dist/{chunk-NSTV4TRP.js → chunk-XPYVLHSX.js} +1 -1
  24. package/dist/chunk-XPYVLHSX.js.map +1 -0
  25. package/dist/{chunk-WSK45HW4.js → chunk-Z5B5X7QP.js} +1 -1
  26. package/dist/chunk-Z5B5X7QP.js.map +1 -0
  27. package/dist/claim-checks.d.ts +8 -13
  28. package/dist/claim-checks.js +2 -2
  29. package/dist/diff/index.d.ts +3 -3
  30. package/dist/diff/index.js +2 -1
  31. package/dist/fingerprint.js +1 -1
  32. package/dist/formatter-CDFpqbDP.d.ts +24 -0
  33. package/dist/formatter.d.ts +29 -0
  34. package/dist/formatter.js +111 -0
  35. package/dist/formatter.js.map +1 -0
  36. package/dist/{index-BTDWXDck.d.ts → index-CrtHz4vN.d.ts} +3 -1
  37. package/dist/index.d.ts +12 -7
  38. package/dist/index.js +239 -62
  39. package/dist/index.js.map +1 -1
  40. package/dist/logger.js +1 -1
  41. package/dist/{model-gRDTk8yk.d.ts → model-BkqfyVh9.d.ts} +27 -6
  42. package/dist/model-output/index.d.ts +2 -2
  43. package/dist/model-output/index.js +9 -9
  44. package/dist/ports/index.d.ts +22 -18
  45. package/dist/ports/index.js +1 -1
  46. package/dist/{position-h_jdn6ZH.d.ts → position-zZl8KGVu.d.ts} +1 -1
  47. package/dist/prompts/file-review.d.ts +3 -3
  48. package/dist/prompts/file-review.js +11 -5
  49. package/dist/prompts/languages.d.ts +2 -1
  50. package/dist/prompts/languages.js +5 -3
  51. package/dist/prompts/verify.d.ts +1 -1
  52. package/dist/prompts/verify.js +1 -1
  53. package/dist/rules/detect.d.ts +1 -1
  54. package/dist/rules/detect.js +5 -5
  55. package/dist/rules/table.js +1 -1
  56. package/dist/token-tracker.js +2 -2
  57. package/package.json +10 -2
  58. package/dist/chunk-FGXA7JNR.js.map +0 -1
  59. package/dist/chunk-ILZUCIZH.js.map +0 -1
  60. package/dist/chunk-JC74XH7Q.js.map +0 -1
  61. package/dist/chunk-MMVQPGK3.js.map +0 -1
  62. package/dist/chunk-NSTV4TRP.js.map +0 -1
  63. package/dist/chunk-OIQRTDYR.js.map +0 -1
  64. package/dist/chunk-PVXFURDR.js.map +0 -1
  65. package/dist/chunk-R2HRWFRH.js.map +0 -1
  66. package/dist/chunk-RQ5S67XB.js.map +0 -1
  67. package/dist/chunk-U3VKVDZ7.js.map +0 -1
  68. package/dist/chunk-V3RF6GQW.js.map +0 -1
  69. package/dist/chunk-WSK45HW4.js.map +0 -1
  70. package/dist/chunk-XD6CGYHO.js.map +0 -1
@@ -1,37 +1,37 @@
1
+ import {
2
+ findPositionForLine,
3
+ getValidPositions
4
+ } from "./chunk-I5S3NBFL.js";
1
5
  import {
2
6
  generatorFindingCap
3
- } from "./chunk-MMVQPGK3.js";
7
+ } from "./chunk-ONJZXUQT.js";
4
8
  import {
5
9
  renderDiffSnippet
6
- } from "./chunk-V3RF6GQW.js";
10
+ } from "./chunk-KTDBDKL5.js";
7
11
  import {
8
12
  MAX_LOGGED_JSON_CHARS,
9
13
  MIN_DISCRIMINATING_EVIDENCE_CHARS,
10
14
  NON_ANSWER_MAX_RESPONSE_CHARS,
11
15
  NON_ANSWER_MIN_DIFF_LINES,
12
16
  SEVERITY_ORDER
13
- } from "./chunk-FGXA7JNR.js";
17
+ } from "./chunk-W3RY755V.js";
14
18
  import {
15
19
  buildPresenceIndex,
16
20
  checkAbsenceClaim,
17
21
  isVersionClaimRefutedByPin,
18
22
  looksLikeExternalVersionClaim,
19
23
  refuteUndecidableClaim
20
- } from "./chunk-U3VKVDZ7.js";
24
+ } from "./chunk-IKVUW4WC.js";
21
25
  import {
22
26
  buildAnchorHash,
23
27
  buildFindingFingerprint,
24
28
  buildFindingFingerprintV2,
25
29
  foldEvidenceText,
26
30
  normalizeFindingTitle
27
- } from "./chunk-NSTV4TRP.js";
31
+ } from "./chunk-XPYVLHSX.js";
28
32
  import {
29
33
  logger
30
- } from "./chunk-WSK45HW4.js";
31
- import {
32
- findPositionForLine,
33
- getValidPositions
34
- } from "./chunk-OIQRTDYR.js";
34
+ } from "./chunk-Z5B5X7QP.js";
35
35
 
36
36
  // src/model-output/index.ts
37
37
  import {
@@ -356,10 +356,11 @@ function buildEvidenceIndex(file) {
356
356
  if (line.kind === "del" || line.newLineNumber === void 0) {
357
357
  anchor = hunk.lines.slice(lineIndex + 1).find((l) => l.kind !== "del" && l.newLineNumber !== void 0) ?? hunk.lines.slice(0, lineIndex).reverse().find((l) => l.kind !== "del" && l.newLineNumber !== void 0) ?? postable[0];
358
358
  }
359
- lines.push({ normalized, line: anchor });
359
+ const entry = { anchor, sourceKind: line.kind };
360
+ lines.push({ normalized, line: entry });
360
361
  const existing = byContent.get(normalized);
361
- if (existing) existing.push(anchor);
362
- else byContent.set(normalized, [anchor]);
362
+ if (existing) existing.push(entry);
363
+ else byContent.set(normalized, [entry]);
363
364
  });
364
365
  }
365
366
  return { byContent, lines };
@@ -392,16 +393,22 @@ function resolveEvidence(evidence, index, reportedLine) {
392
393
  const firstLine = foldFirstEvidenceLine(evidence);
393
394
  if (!firstLine) return { status: "absent" };
394
395
  if (firstLine.length < MIN_DISCRIMINATING_EVIDENCE_CHARS) return { status: "weak" };
396
+ const anyTouched = (candidates) => candidates.some((c) => c.sourceKind !== "context");
395
397
  const nearest = (candidates) => {
396
- if (reportedLine === void 0) return candidates[0];
397
- return candidates.reduce(
398
- (best, candidate) => Math.abs((candidate.newLineNumber ?? 0) - reportedLine) < Math.abs((best.newLineNumber ?? 0) - reportedLine) ? candidate : best
399
- );
398
+ const preferred = candidates.some((c) => c.sourceKind !== "context") ? candidates.filter((c) => c.sourceKind !== "context") : candidates;
399
+ if (reportedLine === void 0) return preferred[0].anchor;
400
+ return preferred.reduce(
401
+ (best, candidate) => Math.abs((candidate.anchor.newLineNumber ?? 0) - reportedLine) < Math.abs((best.anchor.newLineNumber ?? 0) - reportedLine) ? candidate : best
402
+ ).anchor;
400
403
  };
401
404
  const exact = index.byContent.get(firstLine);
402
- if (exact && exact.length > 0) return { status: "matched", line: nearest(exact) };
405
+ if (exact && exact.length > 0) {
406
+ return { status: "matched", line: nearest(exact), touched: anyTouched(exact) };
407
+ }
403
408
  const contained = index.lines.flatMap(({ normalized, line }) => normalized.length >= MIN_DISCRIMINATING_EVIDENCE_CHARS && (normalized.includes(firstLine) || firstLine.includes(normalized)) ? [line] : []);
404
- if (contained.length > 0) return { status: "matched", line: nearest(contained) };
409
+ if (contained.length > 0) {
410
+ return { status: "matched", line: nearest(contained), touched: anyTouched(contained) };
411
+ }
405
412
  return { status: "unmatched" };
406
413
  }
407
414
 
@@ -411,11 +418,12 @@ var NUL = String.fromCharCode(0);
411
418
  function dedupeFindings(comments) {
412
419
  const best = /* @__PURE__ */ new Map();
413
420
  for (const comment of comments) {
414
- const key = comment.source === "rule" ? `rule${NUL}${comment.ruleId ?? ""}${NUL}${comment.path}${NUL}${comment.anchorHash ?? ""}` : normalizeFindingTitle(comment.title);
415
- if (!key) {
421
+ const normalizedTitle = comment.source === "rule" ? "" : normalizeFindingTitle(comment.title);
422
+ if (comment.source !== "rule" && !normalizedTitle) {
416
423
  best.set(`__unique__${best.size}`, comment);
417
424
  continue;
418
425
  }
426
+ const key = comment.source === "rule" ? `rule${NUL}${comment.ruleId ?? ""}${NUL}${comment.path}${NUL}${comment.anchorHash ?? ""}` : `llm${NUL}${comment.path}${NUL}${comment.anchorHash ?? comment.line ?? ""}${NUL}${normalizedTitle}`;
419
427
  const existing = best.get(key);
420
428
  if (!existing) {
421
429
  best.set(key, comment);
@@ -691,6 +699,10 @@ function groundFindingInEvidence(finding, evidenceIndex, evidenceStats, ambiguit
691
699
  if (evidence.status !== "matched") {
692
700
  return { withheld: { title: finding.title, body: finding.body, tag: `unverified:${evidence.status}` } };
693
701
  }
702
+ if (!evidence.touched) {
703
+ evidenceStats.contextOnly += 1;
704
+ return { withheld: { title: finding.title, body: finding.body, tag: "unverified:context-only" } };
705
+ }
694
706
  if (ambiguity) {
695
707
  const firstLine = foldFirstEvidenceLine(finding.evidence);
696
708
  const claimedPath = finding.code_location.absolute_file_path?.trim();
@@ -764,18 +776,7 @@ function applyClaimGate(finding, title, body, anchorContent, deniedClaimTypes, c
764
776
  return { claimType };
765
777
  }
766
778
  function buildParsedComment(params) {
767
- const { file, line, position, severity, title, body, claimType, anchorContent, finding, presenceIndex, absenceCheckStats } = params;
768
- const absence = checkAbsenceClaim({ title, body, anchorLine: line, index: presenceIndex });
769
- if (absence.status === "refuted") {
770
- absenceCheckStats.absenceShaped += 1;
771
- absenceCheckStats.identifierExtracted += 1;
772
- absenceCheckStats.refuted += 1;
773
- } else if (absence.reason !== "not_absence_shaped") {
774
- absenceCheckStats.absenceShaped += 1;
775
- if (absence.reason !== "no_identifier" && absence.reason !== "ambiguous_identifier") {
776
- absenceCheckStats.identifierExtracted += 1;
777
- }
778
- }
779
+ const { file, line, position, severity, title, body, claimType, anchorContent, finding } = params;
779
780
  const confidenceScore = typeof finding.confidence_score === "number" ? finding.confidence_score : 0;
780
781
  const codeSuggestion = typeof finding.code_suggestion === "string" && finding.code_suggestion.trim() ? finding.code_suggestion : void 0;
781
782
  return parsedReviewCommentSchema.parse({
@@ -803,11 +804,11 @@ function buildParsedComment(params) {
803
804
  function groundParsedFindings(parsed, file, options) {
804
805
  const validPositions = getValidPositions(file);
805
806
  const evidenceIndex = buildEvidenceIndex(file);
806
- const evidenceStats = { total: 0, matched: 0, unmatched: 0, weak: 0, absent: 0 };
807
+ const evidenceStats = { total: 0, matched: 0, unmatched: 0, weak: 0, absent: 0, contextOnly: 0 };
807
808
  const claimTypeCounts = {};
808
809
  const deniedClaimCounts = {};
809
810
  const deniedClaimTypes = new Set(options?.deniedClaimTypes ?? []);
810
- const presenceIndex = buildPresenceIndex(file);
811
+ const presenceIndex = buildPresenceIndex(file, options?.fileContent);
811
812
  const absenceCheckStats = { absenceShaped: 0, identifierExtracted: 0, refuted: 0 };
812
813
  const orphanedComments = [];
813
814
  const comments = (parsed.findings || []).map((finding) => {
@@ -828,6 +829,30 @@ function groundParsedFindings(parsed, file, options) {
828
829
  orphanedComments.push(formatWithheld(gated.withheld));
829
830
  return null;
830
831
  }
832
+ const absence = checkAbsenceClaim({ title, body, anchorLine: anchored.line, index: presenceIndex });
833
+ if (absence.status === "refuted") {
834
+ absenceCheckStats.absenceShaped += 1;
835
+ absenceCheckStats.identifierExtracted += 1;
836
+ absenceCheckStats.refuted += 1;
837
+ if (severity !== "P0") {
838
+ logger.info(`Refuted an absence claim in ${file.path}`, {
839
+ identifier: absence.identifier,
840
+ foundAtLine: absence.line,
841
+ title
842
+ });
843
+ orphanedComments.push(formatWithheld({
844
+ title,
845
+ body,
846
+ tag: `refuted:absence:${absence.identifier}`
847
+ }));
848
+ return null;
849
+ }
850
+ } else if (absence.reason !== "not_absence_shaped") {
851
+ absenceCheckStats.absenceShaped += 1;
852
+ if (absence.reason !== "no_identifier" && absence.reason !== "ambiguous_identifier") {
853
+ absenceCheckStats.identifierExtracted += 1;
854
+ }
855
+ }
831
856
  try {
832
857
  return buildParsedComment({
833
858
  file,
@@ -838,9 +863,7 @@ function groundParsedFindings(parsed, file, options) {
838
863
  body,
839
864
  claimType: gated.claimType,
840
865
  anchorContent,
841
- finding,
842
- presenceIndex,
843
- absenceCheckStats
866
+ finding
844
867
  });
845
868
  } catch (error) {
846
869
  if (!(error instanceof z3.ZodError)) throw error;
@@ -890,4 +913,4 @@ export {
890
913
  groundParsedFindings,
891
914
  parseFileReviewResponse
892
915
  };
893
- //# sourceMappingURL=chunk-XD6CGYHO.js.map
916
+ //# sourceMappingURL=chunk-4XEMXRNG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/model-output/index.ts","../src/model-output/json.ts","../src/model-output/evidence.ts","../src/model-output/dedupe.ts","../src/model-output/non-answer.ts","../src/model-output/json-batch.ts","../src/model-output/batch.ts"],"sourcesContent":["import {\n fileReviewModelOutputSchema,\n parsedReviewCommentSchema,\n toClaimType,\n CLAIM_TYPE_CATEGORY,\n type ClaimType,\n type ParsedReviewComment,\n reviewSeverities,\n} from '@codraoss/schema';\nimport { renderDiffSnippet } from '../prompts/verify';\nimport { logger } from '../logger';\nimport { z } from 'zod';\nimport { findPositionForLine, getValidPositions, type DiffLine, type FileDiff } from '../diff';\nimport {\n buildAnchorHash,\n buildFindingFingerprint,\n buildFindingFingerprintV2,\n} from '../fingerprint';\nimport {\n buildPresenceIndex,\n checkAbsenceClaim,\n isVersionClaimRefutedByPin,\n looksLikeExternalVersionClaim,\n refuteUndecidableClaim,\n} from '../claim-checks';\nimport { parseRawPayload } from './json';\nimport {\n type BinAmbiguityIndex,\n type EvidenceIndex,\n buildEvidenceIndex,\n foldFirstEvidenceLine,\n resolveEvidence,\n} from './evidence';\n\nexport function samePath(a: string, b: string): boolean {\n const strip = (p: string) => p.trim().replace(/^\\.\\//, '').replace(/^[ab]\\//, '').replace(/^\\//, '');\n return strip(a) === strip(b);\n}\n\nexport type BinAmbiguity = {\n index: BinAmbiguityIndex;\n filePath: string;\n stats: { ambiguousAcrossBin: number };\n};\n\nfunction withSuggestion(body: string, codeSuggestion?: string) {\n if (!codeSuggestion) return body;\n\n const cleanSuggestion = codeSuggestion.replace(/```suggestion\\n?|```/g, '').trim();\n\n const cleanBody = body.split('```suggestion')[0].trim();\n\n return `${cleanBody}\\n\\n\\`\\`\\`suggestion\\n${cleanSuggestion}\\n\\`\\`\\``;\n}\n\nconst CLAIM_TYPE_REPAIRS: ReadonlyArray<{ pattern: RegExp; claimType: ClaimType }> = [\n { pattern: /dependenc(?:y|ies)\\s+array|exhaustive[- ]deps/i, claimType: 'react_hook_missing_deps' },\n { pattern: /redos|catastrophic backtrack|exponential backtrack/i, claimType: 'redos_regex' },\n];\n\nfunction repairClaimType(claimType: ClaimType, title: string, body: string, onRepair: () => void): ClaimType {\n if (claimType !== 'other') return claimType;\n const text = `${title}\\n${body}`;\n\n if (looksLikeExternalVersionClaim(title, body)) {\n onRepair();\n return 'external_version_claim';\n }\n\n for (const { pattern, claimType: repaired } of CLAIM_TYPE_REPAIRS) {\n if (pattern.test(text)) {\n onRepair();\n return repaired;\n }\n }\n return claimType;\n}\n\ntype RawFinding = z.infer<typeof fileReviewModelOutputSchema>['findings'][number];\n\ntype Withheld = { title: string; body: string; tag?: string };\n\nfunction formatWithheld(w: Withheld): string {\n return w.tag ? `- **[${w.tag}] ${w.title}:** ${w.body}` : `- **${w.title}:** ${w.body}`;\n}\n\nfunction groundFindingInEvidence(\n finding: RawFinding,\n evidenceIndex: EvidenceIndex,\n evidenceStats: { total: number; matched: number; unmatched: number; weak: number; absent: number; contextOnly: number },\n ambiguity?: BinAmbiguity,\n): { diffLine: DiffLine } | { withheld: Withheld } {\n const reportedLine = finding.code_location.line || finding.code_location.line_range?.start;\n\n evidenceStats.total += 1;\n const evidence = resolveEvidence(finding.evidence, evidenceIndex, reportedLine);\n if (evidence.status === 'matched') evidenceStats.matched += 1;\n else if (evidence.status === 'unmatched') evidenceStats.unmatched += 1;\n else if (evidence.status === 'weak') evidenceStats.weak += 1;\n else if (evidence.status === 'absent') evidenceStats.absent += 1;\n\n if (evidence.status !== 'matched') {\n return { withheld: { title: finding.title, body: finding.body, tag: `unverified:${evidence.status}` } };\n }\n\n // The evidence exists, but only on lines this pull request did not touch. That is a review of the\n // repository, not of the change -- and it is the enforcement half of whole-file context: the prompt\n // says the context block is not evidence, and this is what makes that true. Deletions count as\n // touched: a finding about removed code is a finding about the change.\n if (!evidence.touched) {\n evidenceStats.contextOnly += 1;\n return { withheld: { title: finding.title, body: finding.body, tag: 'unverified:context-only' } };\n }\n\n if (ambiguity) {\n const firstLine = foldFirstEvidenceLine(finding.evidence);\n const claimedPath = finding.code_location.absolute_file_path?.trim();\n const ambiguousAcrossBin = firstLine ? (ambiguity.index.get(firstLine) ?? 0) > 1 : false;\n if (ambiguousAcrossBin && claimedPath && !samePath(claimedPath, ambiguity.filePath)) {\n ambiguity.stats.ambiguousAcrossBin += 1;\n return {\n withheld: {\n title: finding.title,\n body: finding.body,\n tag: 'unverified:ambiguous-across-bin',\n },\n };\n }\n }\n\n return { diffLine: evidence.line };\n}\n\nfunction anchorToDiffPosition(\n file: FileDiff,\n diffLine: DiffLine,\n validPositions: Set<number>,\n finding: RawFinding,\n): { line: number; position: number } | { withheld: Withheld } {\n const line = diffLine.newLineNumber!;\n const position = findPositionForLine(file, line);\n\n if (position === undefined || !validPositions.has(position)) {\n return { withheld: { title: finding.title, body: finding.body } };\n }\n\n return { line, position };\n}\n\nfunction validateFindingShape(finding: RawFinding): { severity: typeof reviewSeverities[number]; title: string; body: string } {\n const priorityMap: Record<number, typeof reviewSeverities[number]> = {\n 0: 'P0',\n 1: 'P1',\n 2: 'P2',\n 3: 'P3',\n 4: 'nit',\n };\n const severity = finding.priority !== undefined\n ? priorityMap[finding.priority] || 'P3'\n : 'P3';\n\n const cleanText = (text: string) => {\n let current = text.trim();\n let prev = '';\n while (current !== prev) {\n prev = current;\n current = current\n .replace(/^(?:[^\\w\\s]+|(?:QUALITY|SECURITY|BUG|PERFORMANCE|CORRECTNESS|P[0-3]|NIT)\\b)+/giu, '')\n .replace(/\\n\\s*/g, ' ')\n .trim();\n }\n return current;\n };\n\n const title = cleanText(finding.title);\n let body = cleanText(finding.body);\n\n const bodyPrefix = cleanText(body.split('\\n')[0]);\n if (bodyPrefix.toLowerCase().startsWith(title.toLowerCase()) || title.toLowerCase().startsWith(bodyPrefix.toLowerCase())) {\n body = cleanText(body.slice(body.split('\\n')[0].length));\n }\n\n return { severity, title, body };\n}\n\nfunction applyClaimGate(\n finding: RawFinding,\n title: string,\n body: string,\n anchorContent: string,\n deniedClaimTypes: Set<ClaimType>,\n claimTypeCounts: Record<string, number>,\n deniedClaimCounts: Record<string, number>,\n): { claimType: ClaimType } | { withheld: Withheld } {\n const claimType = repairClaimType(toClaimType(finding.claim_type), title, body, () => {\n claimTypeCounts.__repaired = (claimTypeCounts.__repaired ?? 0) + 1;\n });\n\n claimTypeCounts[claimType] = (claimTypeCounts[claimType] ?? 0) + 1;\n\n if (deniedClaimTypes.has(claimType)) {\n deniedClaimCounts[claimType] = (deniedClaimCounts[claimType] ?? 0) + 1;\n return { withheld: { title, body, tag: `claim-denied:${claimType}` } };\n }\n\n if (isVersionClaimRefutedByPin({ title, body, anchorContent })) {\n deniedClaimCounts.version_claim_on_pinned_sha = (deniedClaimCounts.version_claim_on_pinned_sha ?? 0) + 1;\n return { withheld: { title, body, tag: 'refuted:pinned-sha' } };\n }\n\n const undecidable = refuteUndecidableClaim({ title, body });\n if (undecidable) {\n const key = `undecidable_${undecidable.replace('-', '_')}`;\n deniedClaimCounts[key] = (deniedClaimCounts[key] ?? 0) + 1;\n return { withheld: { title, body, tag: `refuted:${undecidable}` } };\n }\n\n return { claimType };\n}\n\nfunction buildParsedComment(params: {\n file: FileDiff;\n line: number;\n position: number;\n severity: typeof reviewSeverities[number];\n title: string;\n body: string;\n claimType: ClaimType;\n anchorContent: string;\n finding: RawFinding;\n}): ParsedReviewComment {\n const { file, line, position, severity, title, body, claimType, anchorContent, finding } = params;\n\n const confidenceScore = typeof finding.confidence_score === 'number'\n ? finding.confidence_score\n : 0;\n\n const codeSuggestion = typeof finding.code_suggestion === 'string' && finding.code_suggestion.trim()\n ? finding.code_suggestion\n : undefined;\n\n return parsedReviewCommentSchema.parse({\n path: file.path,\n line,\n position,\n severity,\n category: CLAIM_TYPE_CATEGORY[claimType],\n claimType,\n contextSnippet: renderDiffSnippet(file, line) || undefined,\n title,\n body: withSuggestion(body, codeSuggestion),\n codeSuggestion,\n confidenceScore,\n evidence: typeof finding.evidence === 'string' && finding.evidence.trim() ? finding.evidence.trim() : undefined,\n fingerprint: buildFindingFingerprint(file.path, title),\n anchorHash: anchorContent ? buildAnchorHash(anchorContent) : undefined,\n fingerprintV2: buildFindingFingerprintV2(\n file.path,\n claimType,\n anchorContent ? buildAnchorHash(anchorContent) : undefined,\n ) ?? undefined,\n });\n}\n\nexport type FileReviewPayload = z.infer<typeof fileReviewModelOutputSchema>;\n\nexport type GroundingOptions = {\n deniedClaimTypes?: readonly ClaimType[];\n ambiguity?: BinAmbiguity;\n // The file's validated post-change content, when `full_file_context` fetched one. Only widens the\n // absence check: an identifier the diff never showed is still present in the file, and a claim that\n // it is missing is refutable by looking. Evidence stays diff-anchored regardless.\n fileContent?: string | null;\n};\n\nexport type GroundedFileReview = {\n comments: ParsedReviewComment[];\n verdict: 'approve' | 'comment';\n fileSummary: string;\n overallCorrectness?: string;\n confidenceScore?: number;\n evidenceStats: { total: number; matched: number; unmatched: number; weak: number; absent: number; contextOnly: number };\n claimTypeCounts: Record<string, number>;\n deniedClaimCounts: Record<string, number>;\n absenceCheckStats: { absenceShaped: number; identifierExtracted: number; refuted: number };\n};\n\nexport function groundParsedFindings(\n parsed: FileReviewPayload,\n file: FileDiff,\n options?: GroundingOptions,\n): GroundedFileReview {\n const validPositions = getValidPositions(file);\n const evidenceIndex = buildEvidenceIndex(file);\n const evidenceStats = { total: 0, matched: 0, unmatched: 0, weak: 0, absent: 0, contextOnly: 0 };\n const claimTypeCounts: Record<string, number> = {};\n const deniedClaimCounts: Record<string, number> = {};\n const deniedClaimTypes = new Set<ClaimType>(options?.deniedClaimTypes ?? []);\n const presenceIndex = buildPresenceIndex(file, options?.fileContent);\n const absenceCheckStats = { absenceShaped: 0, identifierExtracted: 0, refuted: 0 };\n const orphanedComments: string[] = [];\n\n const comments = (parsed.findings || [])\n .map((finding): ParsedReviewComment | null => {\n const grounded = groundFindingInEvidence(finding, evidenceIndex, evidenceStats, options?.ambiguity);\n if ('withheld' in grounded) {\n orphanedComments.push(formatWithheld(grounded.withheld));\n return null;\n }\n\n const anchored = anchorToDiffPosition(file, grounded.diffLine, validPositions, finding);\n if ('withheld' in anchored) {\n orphanedComments.push(formatWithheld(anchored.withheld));\n return null;\n }\n\n const { severity, title, body } = validateFindingShape(finding);\n\n const anchorContent = grounded.diffLine.content\n ?? file.hunks.flatMap((h) => h.lines).find((l) => l.newLineNumber === anchored.line)?.content\n ?? '';\n\n const gated = applyClaimGate(finding, title, body, anchorContent, deniedClaimTypes, claimTypeCounts, deniedClaimCounts);\n if ('withheld' in gated) {\n orphanedComments.push(formatWithheld(gated.withheld));\n return null;\n }\n\n // \"X is missing / was removed / is never awaited\", answered by looking. This verdict was computed\n // and then thrown away for a long time -- the finding was posted regardless -- so the machinery\n // was there and simply had no teeth.\n const absence = checkAbsenceClaim({ title, body, anchorLine: anchored.line, index: presenceIndex });\n if (absence.status === 'refuted') {\n absenceCheckStats.absenceShaped += 1;\n absenceCheckStats.identifierExtracted += 1;\n absenceCheckStats.refuted += 1;\n\n // Not at P0. The matcher is a literal search over stripped source: it cannot tell a call from a\n // definition, or a live path from a dead one. Silencing a wrong nit is cheap; silencing a\n // correct P0 is not, and `claim-checks.ts` is written to be sound in exactly this direction.\n if (severity !== 'P0') {\n logger.info(`Refuted an absence claim in ${file.path}`, {\n identifier: absence.identifier,\n foundAtLine: absence.line,\n title,\n });\n orphanedComments.push(formatWithheld({\n title,\n body,\n tag: `refuted:absence:${absence.identifier}`,\n }));\n return null;\n }\n } else if (absence.reason !== 'not_absence_shaped') {\n absenceCheckStats.absenceShaped += 1;\n if (absence.reason !== 'no_identifier' && absence.reason !== 'ambiguous_identifier') {\n absenceCheckStats.identifierExtracted += 1;\n }\n }\n\n\n try {\n return buildParsedComment({\n file,\n line: anchored.line,\n position: anchored.position,\n severity,\n title,\n body,\n claimType: gated.claimType,\n anchorContent,\n finding,\n });\n } catch (error) {\n if (!(error instanceof z.ZodError)) throw error;\n\n orphanedComments.push(formatWithheld({\n title: finding.title,\n body: finding.body,\n tag: 'unverified:unassemblable',\n }));\n logger.warn('Dropped a finding that could not be assembled', {\n path: file.path,\n title: finding.title,\n error: error.message,\n });\n return null;\n }\n })\n .filter((comment): comment is ParsedReviewComment => Boolean(comment));\n\n const verdict = parsed.overall_correctness.toLowerCase().includes('patch is correct') ? 'approve' : 'comment';\n let fileSummary = parsed.overall_explanation;\n\n if (orphanedComments.length > 0) {\n fileSummary += `\\n\\n### Additional Comments (Off-diff)\\n${orphanedComments.join('\\n')}`;\n }\n\n return {\n comments,\n verdict: comments.length > 0 ? 'comment' : verdict,\n fileSummary,\n overallCorrectness: parsed.overall_correctness,\n confidenceScore: parsed.overall_confidence_score,\n evidenceStats,\n claimTypeCounts,\n deniedClaimCounts,\n absenceCheckStats,\n };\n}\n\nexport function parseFileReviewResponse(\n raw: string,\n file: FileDiff,\n options?: GroundingOptions,\n): GroundedFileReview {\n return groundParsedFindings(parseRawPayload(raw), file, options);\n}\n\n\nexport { dedupeFindings } from './dedupe';\nexport { isNonAnswerReview } from './non-answer';\nexport {\n NON_ANSWER_MAX_RESPONSE_CHARS,\n NON_ANSWER_MIN_DIFF_LINES,\n} from '../constants';\nexport { parseRawBatchPayload, type RawBatchPayload } from './json-batch';\nexport { parseBatchReviewResponse, type BatchParseStats, type BatchReviewResult } from './batch';\n","import { fileReviewModelOutputSchema } from '@codraoss/schema';\nimport { jsonrepair } from 'jsonrepair';\nimport { z } from 'zod';\nimport { logger } from '../logger';\n\nimport { MAX_LOGGED_JSON_CHARS } from '../constants';\n\nexport function truncateJsonForLog(value: string) {\n if (value.length <= MAX_LOGGED_JSON_CHARS) return value;\n return `${value.slice(0, MAX_LOGGED_JSON_CHARS)}... [truncated ${value.length - MAX_LOGGED_JSON_CHARS} chars]`;\n}\n\nexport function hasReviewKeys(input: string) {\n return /\"(files|findings|overall_explanation|overall_correctness|overall_confidence_score|summary)\"\\s*:/.test(input);\n}\n\nfunction scanBalanced(raw: string, startIdx: number, open: string, close: string): string | null {\n let stack = 0;\n let inString = false;\n let escape = false;\n\n for (let i = startIdx; i < raw.length; i++) {\n const char = raw[i];\n\n if (escape) {\n escape = false;\n continue;\n }\n if (char === '\\\\') {\n escape = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === open) stack++;\n else if (char === close) {\n stack--;\n if (stack === 0) return raw.slice(startIdx, i + 1);\n }\n }\n\n return null;\n}\n\nexport function extractJson(raw: string, anchorKey: 'findings' | 'files' = 'findings') {\n const jsonBlocks = Array.from(raw.matchAll(/```json([\\s\\S]*?)```/gi));\n if (jsonBlocks.length > 0) {\n return jsonBlocks[jsonBlocks.length - 1][1].trim();\n }\n\n const genericBlocks = Array.from(raw.matchAll(/```(?:[\\w+-]+)?([\\s\\S]*?)```/gi));\n if (genericBlocks.length > 0) {\n const candidates = genericBlocks.filter(b => b[1].includes('{') && b[1].includes('}') && hasReviewKeys(b[1]));\n if (candidates.length > 0) {\n const content = candidates[candidates.length - 1][1].trim();\n const start = content.indexOf('{');\n const end = content.lastIndexOf('}');\n if (start !== -1 && end !== -1 && end > start) {\n return content.slice(start, end + 1);\n }\n return content;\n }\n }\n\n const arrayStart = raw.indexOf('[');\n if (arrayStart !== -1 && raw.slice(0, arrayStart).trim() === '') {\n const matched = scanBalanced(raw, arrayStart, '[', ']');\n if (matched && hasReviewKeys(matched)) return matched;\n }\n\n const anchorIdx = anchorKey === 'files' ? raw.indexOf('\"files\"') : -1;\n const findingsIdx = anchorIdx !== -1 ? anchorIdx : raw.indexOf('\"findings\"');\n const summaryIdx = raw.indexOf('\"summary\"');\n const targetIdx = findingsIdx !== -1 ? findingsIdx : (summaryIdx !== -1 ? summaryIdx : -1);\n\n let firstBrace = -1;\n if (targetIdx !== -1) {\n firstBrace = raw.lastIndexOf('{', targetIdx);\n }\n\n if (firstBrace === -1) {\n const allBraces = Array.from(raw.matchAll(/\\{/g));\n let bestIdx = -1;\n let bestScore = -1;\n\n for (const match of allBraces) {\n const idx = match.index!;\n const excerpt = raw.slice(idx, idx + 200);\n let score = 0;\n\n if (excerpt.includes('\"files\"')) score += 100;\n if (excerpt.includes('\"findings\"')) score += 100;\n if (excerpt.includes('\"summary\"')) score += 50;\n if (excerpt.includes('\"overall_explanation\"')) score += 50;\n\n if (excerpt.includes('\" : ') || excerpt.includes('\":')) score += 10;\n if (excerpt.includes('\"[')) score += 5;\n\n if (excerpt.includes(': number;') || excerpt.includes(': string;')) score -= 80;\n if (excerpt.includes('export ') || excerpt.includes('function ')) score -= 80;\n if (excerpt.includes('interface ') || excerpt.includes('type ')) score -= 80;\n if (excerpt.includes(' + ')) score -= 20; // Looks like a diff hunk\n\n if (score > bestScore) {\n bestScore = score;\n bestIdx = idx;\n }\n }\n\n if (bestIdx !== -1 && bestScore > 0) {\n firstBrace = bestIdx;\n }\n }\n\n if (firstBrace === -1) {\n const start = raw.indexOf('{');\n if (start !== -1) {\n const excerpt = raw.slice(start, start + 50);\n if (excerpt.includes('\"') && excerpt.includes(':')) {\n firstBrace = start;\n }\n }\n }\n\n if (firstBrace !== -1) {\n let stack = 0;\n let inString = false;\n let escape = false;\n\n for (let i = firstBrace; i < raw.length; i++) {\n const char = raw[i];\n\n if (escape) {\n escape = false;\n continue;\n }\n\n if (char === '\\\\') {\n escape = true;\n continue;\n }\n\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n\n if (!inString) {\n if (char === '{') stack++;\n else if (char === '}') {\n stack--;\n if (stack === 0) {\n return raw.slice(firstBrace, i + 1);\n }\n }\n }\n }\n\n const partial = raw.slice(firstBrace).trim();\n let closing = '';\n if (inString) closing += '\"';\n closing += '}'.repeat(Math.max(1, stack));\n return `${partial}${closing}`;\n }\n\n return raw.trim();\n}\n\nexport function preprocessJson(json: string): string {\n let result = '';\n let inString = false;\n let escape = false;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escape) {\n result += char;\n escape = false;\n continue;\n }\n\n if (char === '\\\\') {\n result += char;\n escape = true;\n continue;\n }\n\n if (char === '\"') {\n inString = !inString;\n result += char;\n continue;\n }\n\n if (inString) {\n if (char === '\\n') {\n result += '\\\\n';\n } else if (char === '\\r') {\n result += '\\\\r';\n } else {\n result += char;\n }\n } else {\n result += char;\n }\n }\n\n return result;\n}\n\n/**\n * Deletes every `null`-valued key, recursively, before Zod sees the payload.\n *\n * The model output schemas mark optional fields `.optional()`, which accepts an ABSENT key and rejects\n * an explicit `null` -- and these models routinely emit `\"code_suggestion\": null` for a finding that\n * carries no suggestion. On the batched path that single null failed\n * `batchReviewModelOutputSchema.parse`, so `parseBatchReviewResponse` threw and the response for EVERY\n * file in the bin was discarded, then reported as an unreadable answer and failed over to the next\n * model. Measured on this repository's own review: 37 of 88 rejected payloads were otherwise complete\n * and readable.\n *\n * Stripping rather than widening each field is deliberate: absent and null mean the same thing to every\n * one of these schemas, one pass covers the fields nobody has thought of yet, and no downstream type\n * has to learn about `null`.\n */\nexport function stripNulls<T>(value: T): T {\n if (Array.isArray(value)) return value.map(stripNulls) as unknown as T;\n if (value === null || typeof value !== 'object') return value;\n\n const out: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n if (entry === null) continue;\n out[key] = stripNulls(entry);\n }\n return out as T;\n}\n\nfunction isPlaceholderString(value: unknown) {\n return typeof value === 'string' && /^<[^>]+>$/.test(value.trim());\n}\n\nfunction coerceReviewNumber(value: unknown) {\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'string' && !isPlaceholderString(value)) {\n const parsed = Number(value);\n if (Number.isFinite(parsed)) return parsed;\n }\n return undefined;\n}\n\nexport function normalizeFinding(finding: unknown) {\n if (!finding || typeof finding !== 'object') return null;\n const f = finding as Record<string, unknown>;\n if (isPlaceholderString(f.title) || isPlaceholderString(f.body) || isPlaceholderString(f.evidence)) return null;\n\n const location = f.code_location && typeof f.code_location === 'object' ? (f.code_location as Record<string, unknown>) : {};\n const line = coerceReviewNumber(location.line);\n const start = coerceReviewNumber(location.line_range && typeof location.line_range === 'object' ? (location.line_range as Record<string, unknown>).start : undefined);\n const end = coerceReviewNumber(location.line_range && typeof location.line_range === 'object' ? (location.line_range as Record<string, unknown>).end : undefined);\n const priority = coerceReviewNumber(f.priority);\n\n const codeLocation: Record<string, unknown> = {\n absolute_file_path: location.absolute_file_path || f.path || '',\n };\n if (line !== undefined) {\n codeLocation.line = Math.trunc(line as number);\n }\n if (start !== undefined || end !== undefined) {\n codeLocation.line_range = {\n start: Math.trunc((start as number) ?? (end as number)!),\n end: Math.trunc((end as number) ?? (start as number)!),\n };\n }\n\n return {\n ...f,\n title: (f.title ? String(f.title) : '').trim().slice(0, 100).replace(/[\\uD800-\\uDBFF]$/, '') || 'Code finding',\n priority: priority === undefined ? undefined : Math.max(0, Math.min(4, Math.trunc(priority as number))),\n code_location: codeLocation,\n confidence_score: typeof f.confidence_score === 'number'\n ? Math.max(0, Math.min(1, f.confidence_score > 1 ? f.confidence_score / 10 : f.confidence_score))\n : undefined,\n };\n}\n\nexport function parseRawPayload(raw: string): z.infer<typeof fileReviewModelOutputSchema> {\n let extracted: string;\n try {\n extracted = extractJson(raw);\n if (!hasReviewKeys(extracted)) {\n throw new Error('Model response did not contain review JSON keys.');\n }\n } catch (e) {\n logger.error('Failed to extract JSON from model response', {\n rawLength: raw.length,\n rawPrefix: raw.slice(0, 500),\n error: e instanceof Error ? e.message : String(e),\n });\n throw new Error('Could not find JSON root in model response.', { cause: e });\n }\n\n let preprocessed: string;\n try {\n preprocessed = preprocessJson(extracted);\n } catch (e) {\n logger.warn('JSON preprocessing partially failed, continuing...', { extracted, error: e });\n preprocessed = extracted;\n }\n\n let repaired = preprocessed;\n try {\n repaired = jsonrepair(preprocessed);\n } catch (e) {\n logger.warn('jsonrepair failed to fix model output, using preprocessed text', { preprocessed: truncateJsonForLog(preprocessed), error: e });\n }\n\n let parsedJson: unknown;\n try {\n parsedJson = stripNulls(JSON.parse(repaired));\n } catch (e) {\n logger.error('Critical JSON parse error after extraction and repair', { repaired: truncateJsonForLog(repaired), error: e });\n throw new Error(`Invalid JSON format: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e });\n }\n\n try {\n const findReviewObject = (arr: unknown[]): unknown | null => {\n const best = arr.find(i => i && typeof i === 'object' && Array.isArray((i as Record<string, unknown>).findings) && typeof (i as Record<string, unknown>).summary === 'string');\n if (best) return best;\n\n const good = arr.find(i => i && typeof i === 'object' && Array.isArray((i as Record<string, unknown>).findings));\n if (good) return good;\n\n return arr.find(i =>\n i && typeof i === 'object' &&\n ('findings' in i || 'overall_explanation' in i || 'summary' in i || 'overall_correctness' in i)\n );\n };\n\n let data = Array.isArray(parsedJson) ? (findReviewObject(parsedJson) || parsedJson[0] || {}) : parsedJson;\n\n if (data && typeof data === 'object') {\n const obj = data as Record<string, unknown>;\n if (!obj.findings) obj.findings = [];\n if (!obj.overall_explanation) obj.overall_explanation = 'No explanation provided.';\n if (!obj.overall_correctness) obj.overall_correctness = 'Uncertain';\n\n if (typeof obj.overall_confidence_score === 'number') {\n if (obj.overall_confidence_score > 1) {\n obj.overall_confidence_score = Math.min(obj.overall_confidence_score / 10, 1);\n } else if (obj.overall_confidence_score < 0) {\n obj.overall_confidence_score = 0;\n }\n } else {\n obj.overall_confidence_score = 0.5;\n }\n\n if (Array.isArray(obj.findings)) {\n obj.findings = obj.findings.flatMap((finding: unknown) => {\n const normalized = normalizeFinding(finding);\n return normalized ? [normalized] : [];\n });\n }\n data = obj;\n }\n\n return fileReviewModelOutputSchema.parse(data);\n } catch (e) {\n logger.error('Model response failed schema validation', { parsedJson, error: e });\n throw new Error(`Response schema mismatch: ${e instanceof Error ? e.message : 'Check logs'}`, { cause: e });\n }\n}\n","import { foldEvidenceText } from '../fingerprint';\nimport type { DiffLine, FileDiff } from '../diff';\n\nimport { MIN_DISCRIMINATING_EVIDENCE_CHARS } from '../constants';\n\n/** `anchor` is where a quoting finding gets posted; `sourceKind` is the text's kind pre-re-anchoring. */\nexport type IndexedLine = { anchor: DiffLine; sourceKind: DiffLine['kind'] };\n\nexport type EvidenceIndex = {\n byContent: Map<string, IndexedLine[]>;\n lines: { normalized: string; line: IndexedLine }[];\n};\n\nexport function buildEvidenceIndex(file: FileDiff): EvidenceIndex {\n const byContent = new Map<string, IndexedLine[]>();\n const lines: { normalized: string; line: IndexedLine }[] = [];\n\n for (const hunk of file.hunks) {\n const postable = hunk.lines.filter((line) => line.kind !== 'del' && line.newLineNumber !== undefined);\n if (postable.length === 0) continue;\n\n hunk.lines.forEach((line, lineIndex) => {\n const normalized = foldEvidenceText(line.content);\n if (!normalized) return;\n\n let anchor = line;\n if (line.kind === 'del' || line.newLineNumber === undefined) {\n anchor = hunk.lines.slice(lineIndex + 1).find((l) => l.kind !== 'del' && l.newLineNumber !== undefined)\n ?? hunk.lines.slice(0, lineIndex).reverse().find((l) => l.kind !== 'del' && l.newLineNumber !== undefined)\n ?? postable[0];\n }\n\n const entry: IndexedLine = { anchor, sourceKind: line.kind };\n lines.push({ normalized, line: entry });\n const existing = byContent.get(normalized);\n if (existing) existing.push(entry);\n else byContent.set(normalized, [entry]);\n });\n }\n\n return { byContent, lines };\n}\n\nexport function foldFirstEvidenceLine(evidence: unknown): string | null {\n if (typeof evidence !== 'string') return null;\n return evidence.split('\\n').map(foldEvidenceText).find((l) => l.length > 0) ?? null;\n}\n\nexport type BinAmbiguityIndex = Map<string, number>;\n\nexport function buildBinAmbiguityIndex(files: readonly FileDiff[]): BinAmbiguityIndex {\n const filesPerLine = new Map<string, Set<string>>();\n\n for (const file of files) {\n for (const hunk of file.hunks) {\n for (const line of hunk.lines) {\n const normalized = foldEvidenceText(line.content);\n if (normalized.length < MIN_DISCRIMINATING_EVIDENCE_CHARS) continue;\n const paths = filesPerLine.get(normalized);\n if (paths) paths.add(file.path);\n else filesPerLine.set(normalized, new Set([file.path]));\n }\n }\n }\n\n const index: BinAmbiguityIndex = new Map();\n for (const [normalized, paths] of filesPerLine) {\n if (paths.size > 1) index.set(normalized, paths.size);\n }\n return index;\n}\n\nexport type EvidenceResolution =\n | { status: 'absent' }\n | { status: 'weak' }\n // `touched` is false only when every occurrence of the quoted text is an untouched context line.\n | { status: 'matched'; line: DiffLine; touched: boolean }\n | { status: 'unmatched' };\n\nexport function resolveEvidence(\n evidence: unknown,\n index: EvidenceIndex,\n reportedLine: number | undefined,\n): EvidenceResolution {\n if (typeof evidence !== 'string') return { status: 'absent' };\n\n const firstLine = foldFirstEvidenceLine(evidence);\n if (!firstLine) return { status: 'absent' };\n if (firstLine.length < MIN_DISCRIMINATING_EVIDENCE_CHARS) return { status: 'weak' };\n\n // Judged over the whole candidate set, so one context occurrence cannot refuse a changed one.\n const anyTouched = (candidates: IndexedLine[]) => candidates.some((c) => c.sourceKind !== 'context');\n\n const nearest = (candidates: IndexedLine[]) => {\n const preferred = candidates.some((c) => c.sourceKind !== 'context')\n ? candidates.filter((c) => c.sourceKind !== 'context')\n : candidates;\n if (reportedLine === undefined) return preferred[0].anchor;\n return preferred.reduce((best, candidate) =>\n Math.abs((candidate.anchor.newLineNumber ?? 0) - reportedLine)\n < Math.abs((best.anchor.newLineNumber ?? 0) - reportedLine)\n ? candidate\n : best,\n ).anchor;\n };\n\n const exact = index.byContent.get(firstLine);\n if (exact && exact.length > 0) {\n return { status: 'matched', line: nearest(exact), touched: anyTouched(exact) };\n }\n\n const contained = index.lines.flatMap(({ normalized, line }) =>\n normalized.length >= MIN_DISCRIMINATING_EVIDENCE_CHARS\n && (normalized.includes(firstLine) || firstLine.includes(normalized))\n ? [line]\n : []);\n if (contained.length > 0) {\n return { status: 'matched', line: nearest(contained), touched: anyTouched(contained) };\n }\n\n return { status: 'unmatched' };\n}\n","import type { ParsedReviewComment } from '@codraoss/schema';\nimport { normalizeFindingTitle } from '../fingerprint';\n\nconst SEVERITY_RANK: Record<string, number> = { P0: 0, P1: 1, P2: 2, P3: 3, nit: 4 };\n\nconst NUL = String.fromCharCode(0);\n\nexport function dedupeFindings(comments: ParsedReviewComment[]): ParsedReviewComment[] {\n const best = new Map<string, ParsedReviewComment>();\n for (const comment of comments) {\n // Union, keyed per location. Never weight by agreement: 7 configs agreeing measured 7% correct, 1 config 20%.\n const normalizedTitle = comment.source === 'rule' ? '' : normalizeFindingTitle(comment.title);\n if (comment.source !== 'rule' && !normalizedTitle) {\n best.set(`__unique__${best.size}`, comment);\n continue;\n }\n\n const key = comment.source === 'rule'\n ? `rule${NUL}${comment.ruleId ?? ''}${NUL}${comment.path}${NUL}${comment.anchorHash ?? ''}`\n : `llm${NUL}${comment.path}${NUL}${comment.anchorHash ?? comment.line ?? ''}${NUL}${normalizedTitle}`;\n const existing = best.get(key);\n if (!existing) {\n best.set(key, comment);\n continue;\n }\n const rank = SEVERITY_RANK[comment.severity] ?? 4;\n const existingRank = SEVERITY_RANK[existing.severity] ?? 4;\n const isBetter =\n rank < existingRank ||\n (rank === existingRank && (comment.confidenceScore ?? 0) > (existing.confidenceScore ?? 0));\n if (isBetter) best.set(key, comment);\n }\n return Array.from(best.values());\n}\n","\nimport type { FileDiff } from '../diff';\n\nimport {\n NON_ANSWER_MAX_RESPONSE_CHARS,\n NON_ANSWER_MIN_DIFF_LINES,\n} from '../constants';\n\n/**\n * True when a review response is a non-answer: a substantive diff dismissed in a sentence with no\n * findings. Deliberately conservative -- it must never fire on a small diff, and never when the model\n * actually engaged, because the cost of a false positive is an escalation that spends real quota.\n */\nexport function isNonAnswerReview(input: {\n rawText: string;\n file: Pick<FileDiff, 'lineCount'>;\n findingCount: number;\n minDiffLines?: number;\n}): boolean {\n if (input.findingCount > 0) return false;\n if (input.file.lineCount < (input.minDiffLines ?? NON_ANSWER_MIN_DIFF_LINES)) return false;\n return input.rawText.trim().length < NON_ANSWER_MAX_RESPONSE_CHARS;\n}\n","import { batchReviewModelOutputSchema, fileReviewModelOutputSchema } from '@codraoss/schema';\nimport { jsonrepair } from 'jsonrepair';\nimport { z } from 'zod';\nimport { logger } from '../logger';\nimport {\n extractJson,\n hasReviewKeys,\n normalizeFinding,\n parseRawPayload,\n preprocessJson,\n stripNulls,\n truncateJsonForLog,\n} from './json';\n\nfunction readEntryPath(entry: Record<string, unknown>): string | null {\n for (const key of ['absolute_file_path', 'path', 'file', 'file_path', 'filename'] as const) {\n const value = entry[key];\n if (typeof value === 'string' && value.trim()) return value.trim();\n }\n return null;\n}\n\nfunction normalizeConfidence(value: unknown): number | undefined {\n if (typeof value !== 'number' || !Number.isFinite(value)) return undefined;\n if (value > 1) return Math.min(value / 10, 1);\n if (value < 0) return 0;\n return value;\n}\n\nfunction normalizeBatchFileEntry(entry: unknown, fallbackPath?: string): unknown | null {\n if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return null;\n const e = entry as Record<string, unknown>;\n const path = readEntryPath(e) ?? fallbackPath;\n if (!path) return null;\n\n if (!Array.isArray(e.findings)) return null;\n\n return {\n absolute_file_path: path,\n findings: e.findings.flatMap((finding) => {\n const normalized = normalizeFinding(finding);\n return normalized ? [normalized] : [];\n }),\n overall_correctness: typeof e.overall_correctness === 'string' && e.overall_correctness ? e.overall_correctness : undefined,\n overall_explanation: typeof e.overall_explanation === 'string' && e.overall_explanation ? e.overall_explanation : undefined,\n overall_confidence_score: normalizeConfidence(e.overall_confidence_score),\n };\n}\n\nfunction collectBatchEntries(parsedJson: unknown): unknown[] | null {\n const root = parsedJson && typeof parsedJson === 'object' ? (parsedJson as Record<string, unknown>) : null;\n const files = root?.files ?? (Array.isArray(parsedJson) ? parsedJson : undefined);\n\n if (Array.isArray(files)) {\n return files.flatMap((entry) => {\n const normalized = normalizeBatchFileEntry(entry);\n return normalized ? [normalized] : [];\n });\n }\n if (files && typeof files === 'object') {\n return Object.entries(files as Record<string, unknown>).flatMap(([path, entry]) => {\n const normalized = normalizeBatchFileEntry(entry, path);\n return normalized ? [normalized] : [];\n });\n }\n return null;\n}\n\nexport type RawBatchPayload =\n | { shape: 'nested'; data: z.infer<typeof batchReviewModelOutputSchema> }\n | { shape: 'flat'; data: z.infer<typeof fileReviewModelOutputSchema> };\n\nexport function parseRawBatchPayload(raw: string): RawBatchPayload {\n let extracted: string;\n try {\n extracted = extractJson(raw, 'files');\n if (!hasReviewKeys(extracted)) {\n throw new Error('Model response did not contain review JSON keys.');\n }\n } catch (e) {\n logger.error('Failed to extract JSON from batched model response', {\n rawLength: raw.length,\n rawPrefix: raw.slice(0, 500),\n error: e instanceof Error ? e.message : String(e),\n });\n throw new Error('Could not find JSON root in batched model response.', { cause: e });\n }\n\n let preprocessed: string;\n try {\n preprocessed = preprocessJson(extracted);\n } catch (e) {\n logger.warn('JSON preprocessing partially failed, continuing...', { extracted, error: e });\n preprocessed = extracted;\n }\n\n let repaired = preprocessed;\n try {\n repaired = jsonrepair(preprocessed);\n } catch (e) {\n logger.warn('jsonrepair failed to fix batched model output, using preprocessed text', { preprocessed: truncateJsonForLog(preprocessed), error: e });\n }\n\n let parsedJson: unknown;\n try {\n parsedJson = stripNulls(JSON.parse(repaired));\n } catch (e) {\n logger.error('Critical JSON parse error after extraction and repair', { repaired: truncateJsonForLog(repaired), error: e });\n throw new Error(`Invalid JSON format: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e });\n }\n\n const entries = collectBatchEntries(parsedJson);\n const root = parsedJson && typeof parsedJson === 'object' && !Array.isArray(parsedJson)\n ? (parsedJson as Record<string, unknown>)\n : {};\n\n if (!entries?.length) {\n if (Array.isArray(root.findings)) return { shape: 'flat', data: parseRawPayload(raw) };\n logger.error('Batched model response contained no recognisable file entries', {\n parsedJson: truncateJsonForLog(JSON.stringify(parsedJson ?? null)),\n });\n throw new Error('Batched response contained no recognisable file entries.');\n }\n\n try {\n return {\n shape: 'nested',\n data: batchReviewModelOutputSchema.parse({\n files: entries,\n overall_confidence_score: normalizeConfidence(root.overall_confidence_score) ?? 0.5,\n }),\n };\n } catch (e) {\n logger.error('Batched model response failed schema validation', { parsedJson, error: e });\n throw new Error(`Batched response schema mismatch: ${e instanceof Error ? e.message : 'Check logs'}`, { cause: e });\n }\n}\n","import type { ClaimType } from '@codraoss/schema';\nimport type { FileDiff } from '../diff';\nimport { generatorFindingCap } from '../prompts/file-review';\nimport { logger } from '../logger';\nimport { buildBinAmbiguityIndex } from './evidence';\nimport { type GroundedFileReview, groundParsedFindings, samePath } from './index';\nimport { parseRawBatchPayload } from './json-batch';\n\nexport type BatchParseStats = {\n unroutableEntries: number;\n pathMismatchFindings: number;\n ambiguousAcrossBin: number;\n flatFallback: number;\n overCap: number;\n entriesReturned: number;\n};\n\nexport type BatchReviewResult = {\n reviews: Map<string, GroundedFileReview>;\n missing: string[];\n stats: BatchParseStats;\n};\n\ntype Ambiguity = { index: ReturnType<typeof buildBinAmbiguityIndex>; stats: { ambiguousAcrossBin: number } };\ntype RawEntry = { findings: unknown[]; overall_correctness: string; overall_explanation: string };\n\nconst basename = (path: string) => path.split('/').pop() ?? path;\nimport { SEVERITY_ORDER } from '../constants';\n\nfunction resolveEntryPath(reported: string, candidates: readonly FileDiff[], claimed: Set<string>): FileDiff | null {\n const unclaimed = (matches: readonly FileDiff[]) => matches.find((f) => !claimed.has(f.path)) ?? null;\n\n const exact = candidates.filter((f) => samePath(f.path, reported));\n const renamed = candidates.filter((f) => f.previousPath && samePath(f.previousPath, reported));\n if (exact.length > 0 || renamed.length > 0) return unclaimed(exact) ?? unclaimed(renamed);\n\n const stripped = reported.trim().replace(/^\\.\\//, '').replace(/^[ab]\\//, '').replace(/^\\//, '');\n const suffixed = candidates.filter((f) => f.path.endsWith(`/${stripped}`));\n if (suffixed.length === 1) return unclaimed(suffixed);\n\n const named = candidates.filter((f) => basename(f.path) === basename(stripped));\n return named.length === 1 ? unclaimed(named) : null;\n}\n\nfunction groundEntry(\n file: FileDiff,\n entry: RawEntry,\n deniedClaimTypes: readonly ClaimType[] | undefined,\n ambiguity: Ambiguity,\n confidenceScore: number | undefined,\n stats: BatchParseStats,\n): GroundedFileReview {\n for (const finding of entry.findings as Array<{ code_location?: { absolute_file_path?: string } }>) {\n const claimed = finding.code_location?.absolute_file_path?.trim();\n if (claimed && !samePath(claimed, file.path)) stats.pathMismatchFindings += 1;\n }\n\n return groundParsedFindings(\n { ...entry, findings: entry.findings as never, overall_confidence_score: confidenceScore },\n file,\n { deniedClaimTypes, ambiguity: { index: ambiguity.index, filePath: file.path, stats: ambiguity.stats } },\n );\n}\n\nfunction trimOverCap(reviews: Map<string, GroundedFileReview>, cap: number, stats: BatchParseStats) {\n for (const [path, review] of reviews) {\n if (review.comments.length <= cap) continue;\n\n const ranked = review.comments.toSorted((a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity));\n const dropped = ranked.slice(cap);\n stats.overCap += dropped.length;\n\n const header = '### Additional Comments (Off-diff)';\n const bullets = dropped.map((c) => `- **[over-cap] ${c.title}:** ${c.body}`).join('\\n');\n reviews.set(path, {\n ...review,\n comments: ranked.slice(0, cap),\n fileSummary: review.fileSummary.includes(header)\n ? `${review.fileSummary}\\n${bullets}`\n : `${review.fileSummary}\\n\\n${header}\\n${bullets}`,\n });\n }\n}\n\nexport function parseBatchReviewResponse(\n raw: string,\n files: readonly FileDiff[],\n options?: { deniedClaimTypes?: readonly ClaimType[]; maxCommentsPerFile?: number },\n): BatchReviewResult {\n const stats: BatchParseStats = {\n unroutableEntries: 0, pathMismatchFindings: 0, ambiguousAcrossBin: 0,\n flatFallback: 0, overCap: 0, entriesReturned: 0,\n };\n const payload = parseRawBatchPayload(raw);\n const reviews = new Map<string, GroundedFileReview>();\n const ambiguity: Ambiguity = { index: buildBinAmbiguityIndex(files), stats: { ambiguousAcrossBin: 0 } };\n const claimed = new Set<string>();\n\n const ground = (file: FileDiff, entry: RawEntry, confidence: number | undefined) =>\n reviews.set(file.path, groundEntry(file, entry, options?.deniedClaimTypes, ambiguity, confidence, stats));\n\n if (payload.shape === 'flat') {\n stats.flatFallback = 1;\n stats.entriesReturned = 1;\n\n const byFile = new Map<string, { file: FileDiff; findings: typeof payload.data.findings }>();\n for (const finding of payload.data.findings) {\n const reported = finding.code_location.absolute_file_path?.trim();\n const target = !reported && files.length === 1 ? files[0] : resolveEntryPath(reported ?? '', files, claimed);\n if (!target) {\n stats.unroutableEntries += 1;\n continue;\n }\n const bucket = byFile.get(target.path);\n if (bucket) bucket.findings.push(finding);\n else byFile.set(target.path, { file: target, findings: [finding] });\n }\n\n for (const { file, findings } of byFile.values()) {\n ground(file, {\n findings,\n overall_correctness: payload.data.overall_correctness,\n overall_explanation: payload.data.overall_explanation,\n }, payload.data.overall_confidence_score);\n }\n } else {\n stats.entriesReturned = payload.data.files.length;\n for (const entry of payload.data.files) {\n const file = resolveEntryPath(entry.absolute_file_path, files, claimed);\n if (!file) {\n stats.unroutableEntries += 1;\n logger.warn('Batched review returned an entry for an unknown path', { reported: entry.absolute_file_path });\n continue;\n }\n claimed.add(file.path);\n ground(file, entry, entry.overall_confidence_score ?? payload.data.overall_confidence_score);\n }\n }\n\n stats.ambiguousAcrossBin = ambiguity.stats.ambiguousAcrossBin;\n if (options?.maxCommentsPerFile) trimOverCap(reviews, generatorFindingCap(options.maxCommentsPerFile), stats);\n\n return { reviews, missing: files.flatMap((f) => (reviews.has(f.path) ? [] : [f.path])), stats };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP,SAAS,KAAAA,UAAS;;;ACXlB,SAAS,mCAAmC;AAC5C,SAAS,kBAAkB;AAC3B,OAAkB;AAKX,SAAS,mBAAmB,OAAe;AAChD,MAAI,MAAM,UAAU,sBAAuB,QAAO;AAClD,SAAO,GAAG,MAAM,MAAM,GAAG,qBAAqB,CAAC,kBAAkB,MAAM,SAAS,qBAAqB;AACvG;AAEO,SAAS,cAAc,OAAe;AAC3C,SAAO,kGAAkG,KAAK,KAAK;AACrH;AAEA,SAAS,aAAa,KAAa,UAAkB,MAAc,OAA8B;AAC/F,MAAI,QAAQ;AACZ,MAAI,WAAW;AACf,MAAI,SAAS;AAEb,WAAS,IAAI,UAAU,IAAI,IAAI,QAAQ,KAAK;AAC1C,UAAM,OAAO,IAAI,CAAC;AAElB,QAAI,QAAQ;AACV,eAAS;AACT;AAAA,IACF;AACA,QAAI,SAAS,MAAM;AACjB,eAAS;AACT;AAAA,IACF;AACA,QAAI,SAAS,KAAK;AAChB,iBAAW,CAAC;AACZ;AAAA,IACF;AACA,QAAI,SAAU;AAEd,QAAI,SAAS,KAAM;AAAA,aACV,SAAS,OAAO;AACvB;AACA,UAAI,UAAU,EAAG,QAAO,IAAI,MAAM,UAAU,IAAI,CAAC;AAAA,IACnD;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,YAAY,KAAa,YAAkC,YAAY;AACrF,QAAM,aAAa,MAAM,KAAK,IAAI,SAAS,wBAAwB,CAAC;AACpE,MAAI,WAAW,SAAS,GAAG;AACzB,WAAO,WAAW,WAAW,SAAS,CAAC,EAAE,CAAC,EAAE,KAAK;AAAA,EACnD;AAEA,QAAM,gBAAgB,MAAM,KAAK,IAAI,SAAS,gCAAgC,CAAC;AAC/E,MAAI,cAAc,SAAS,GAAG;AAC5B,UAAM,aAAa,cAAc,OAAO,OAAK,EAAE,CAAC,EAAE,SAAS,GAAG,KAAK,EAAE,CAAC,EAAE,SAAS,GAAG,KAAK,cAAc,EAAE,CAAC,CAAC,CAAC;AAC5G,QAAI,WAAW,SAAS,GAAG;AACzB,YAAM,UAAU,WAAW,WAAW,SAAS,CAAC,EAAE,CAAC,EAAE,KAAK;AAC1D,YAAM,QAAQ,QAAQ,QAAQ,GAAG;AACjC,YAAM,MAAM,QAAQ,YAAY,GAAG;AACnC,UAAI,UAAU,MAAM,QAAQ,MAAM,MAAM,OAAO;AAC7C,eAAO,QAAQ,MAAM,OAAO,MAAM,CAAC;AAAA,MACrC;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,aAAa,IAAI,QAAQ,GAAG;AAClC,MAAI,eAAe,MAAM,IAAI,MAAM,GAAG,UAAU,EAAE,KAAK,MAAM,IAAI;AAC/D,UAAM,UAAU,aAAa,KAAK,YAAY,KAAK,GAAG;AACtD,QAAI,WAAW,cAAc,OAAO,EAAG,QAAO;AAAA,EAChD;AAEA,QAAM,YAAY,cAAc,UAAU,IAAI,QAAQ,SAAS,IAAI;AACnE,QAAM,cAAc,cAAc,KAAK,YAAY,IAAI,QAAQ,YAAY;AAC3E,QAAM,aAAa,IAAI,QAAQ,WAAW;AAC1C,QAAM,YAAY,gBAAgB,KAAK,cAAe,eAAe,KAAK,aAAa;AAEvF,MAAI,aAAa;AACjB,MAAI,cAAc,IAAI;AACpB,iBAAa,IAAI,YAAY,KAAK,SAAS;AAAA,EAC7C;AAEA,MAAI,eAAe,IAAI;AACrB,UAAM,YAAY,MAAM,KAAK,IAAI,SAAS,KAAK,CAAC;AAChD,QAAI,UAAU;AACd,QAAI,YAAY;AAEhB,eAAW,SAAS,WAAW;AAC7B,YAAM,MAAM,MAAM;AAClB,YAAM,UAAU,IAAI,MAAM,KAAK,MAAM,GAAG;AACxC,UAAI,QAAQ;AAEZ,UAAI,QAAQ,SAAS,SAAS,EAAG,UAAS;AAC1C,UAAI,QAAQ,SAAS,YAAY,EAAG,UAAS;AAC7C,UAAI,QAAQ,SAAS,WAAW,EAAG,UAAS;AAC5C,UAAI,QAAQ,SAAS,uBAAuB,EAAG,UAAS;AAExD,UAAI,QAAQ,SAAS,MAAM,KAAK,QAAQ,SAAS,IAAI,EAAG,UAAS;AACjE,UAAI,QAAQ,SAAS,IAAI,EAAG,UAAS;AAErC,UAAI,QAAQ,SAAS,WAAW,KAAK,QAAQ,SAAS,WAAW,EAAG,UAAS;AAC7E,UAAI,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,WAAW,EAAG,UAAS;AAC3E,UAAI,QAAQ,SAAS,YAAY,KAAK,QAAQ,SAAS,OAAO,EAAG,UAAS;AAC1E,UAAI,QAAQ,SAAS,KAAK,EAAG,UAAS;AAEtC,UAAI,QAAQ,WAAW;AACrB,oBAAY;AACZ,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,QAAI,YAAY,MAAM,YAAY,GAAG;AACnC,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,MAAI,eAAe,IAAI;AACrB,UAAM,QAAQ,IAAI,QAAQ,GAAG;AAC7B,QAAI,UAAU,IAAI;AAChB,YAAM,UAAU,IAAI,MAAM,OAAO,QAAQ,EAAE;AAC3C,UAAI,QAAQ,SAAS,GAAG,KAAK,QAAQ,SAAS,GAAG,GAAG;AAClD,qBAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAEA,MAAI,eAAe,IAAI;AACrB,QAAI,QAAQ;AACZ,QAAI,WAAW;AACf,QAAI,SAAS;AAEb,aAAS,IAAI,YAAY,IAAI,IAAI,QAAQ,KAAK;AAC5C,YAAM,OAAO,IAAI,CAAC;AAElB,UAAI,QAAQ;AACV,iBAAS;AACT;AAAA,MACF;AAEA,UAAI,SAAS,MAAM;AACjB,iBAAS;AACT;AAAA,MACF;AAEA,UAAI,SAAS,KAAK;AAChB,mBAAW,CAAC;AACZ;AAAA,MACF;AAEA,UAAI,CAAC,UAAU;AACb,YAAI,SAAS,IAAK;AAAA,iBACT,SAAS,KAAK;AACrB;AACA,cAAI,UAAU,GAAG;AACf,mBAAO,IAAI,MAAM,YAAY,IAAI,CAAC;AAAA,UACpC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,IAAI,MAAM,UAAU,EAAE,KAAK;AAC3C,QAAI,UAAU;AACd,QAAI,SAAU,YAAW;AACzB,eAAW,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK,CAAC;AACxC,WAAO,GAAG,OAAO,GAAG,OAAO;AAAA,EAC7B;AAEA,SAAO,IAAI,KAAK;AAClB;AAEO,SAAS,eAAe,MAAsB;AACnD,MAAI,SAAS;AACb,MAAI,WAAW;AACf,MAAI,SAAS;AAEb,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,OAAO,KAAK,CAAC;AAEnB,QAAI,QAAQ;AACV,gBAAU;AACV,eAAS;AACT;AAAA,IACF;AAEA,QAAI,SAAS,MAAM;AACjB,gBAAU;AACV,eAAS;AACT;AAAA,IACF;AAEA,QAAI,SAAS,KAAK;AAChB,iBAAW,CAAC;AACZ,gBAAU;AACV;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,UAAI,SAAS,MAAM;AACjB,kBAAU;AAAA,MACZ,WAAW,SAAS,MAAM;AACxB,kBAAU;AAAA,MACZ,OAAO;AACL,kBAAU;AAAA,MACZ;AAAA,IACF,OAAO;AACL,gBAAU;AAAA,IACZ;AAAA,EACF;AAEA,SAAO;AACT;AAiBO,SAAS,WAAc,OAAa;AACzC,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,UAAU;AACrD,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AAExD,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAgC,GAAG;AAC3E,QAAI,UAAU,KAAM;AACpB,QAAI,GAAG,IAAI,WAAW,KAAK;AAAA,EAC7B;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAgB;AAC3C,SAAO,OAAO,UAAU,YAAY,YAAY,KAAK,MAAM,KAAK,CAAC;AACnE;AAEA,SAAS,mBAAmB,OAAgB;AAC1C,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,YAAY,CAAC,oBAAoB,KAAK,GAAG;AAC5D,UAAM,SAAS,OAAO,KAAK;AAC3B,QAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,SAAkB;AACjD,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,IAAI;AACV,MAAI,oBAAoB,EAAE,KAAK,KAAK,oBAAoB,EAAE,IAAI,KAAK,oBAAoB,EAAE,QAAQ,EAAG,QAAO;AAE3G,QAAM,WAAW,EAAE,iBAAiB,OAAO,EAAE,kBAAkB,WAAY,EAAE,gBAA4C,CAAC;AAC1H,QAAM,OAAO,mBAAmB,SAAS,IAAI;AAC7C,QAAM,QAAQ,mBAAmB,SAAS,cAAc,OAAO,SAAS,eAAe,WAAY,SAAS,WAAuC,QAAQ,MAAS;AACpK,QAAM,MAAM,mBAAmB,SAAS,cAAc,OAAO,SAAS,eAAe,WAAY,SAAS,WAAuC,MAAM,MAAS;AAChK,QAAM,WAAW,mBAAmB,EAAE,QAAQ;AAE9C,QAAM,eAAwC;AAAA,IAC5C,oBAAoB,SAAS,sBAAsB,EAAE,QAAQ;AAAA,EAC/D;AACA,MAAI,SAAS,QAAW;AACtB,iBAAa,OAAO,KAAK,MAAM,IAAc;AAAA,EAC/C;AACA,MAAI,UAAU,UAAa,QAAQ,QAAW;AAC5C,iBAAa,aAAa;AAAA,MACxB,OAAO,KAAK,MAAO,SAAqB,GAAe;AAAA,MACvD,KAAK,KAAK,MAAO,OAAmB,KAAiB;AAAA,IACvD;AAAA,EACF;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ,EAAE,QAAQ,OAAO,EAAE,KAAK,IAAI,IAAI,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,QAAQ,oBAAoB,EAAE,KAAK;AAAA,IAChG,UAAU,aAAa,SAAY,SAAY,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,QAAkB,CAAC,CAAC;AAAA,IACtG,eAAe;AAAA,IACf,kBAAkB,OAAO,EAAE,qBAAqB,WAC5C,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE,mBAAmB,IAAI,EAAE,mBAAmB,KAAK,EAAE,gBAAgB,CAAC,IAC9F;AAAA,EACN;AACF;AAEO,SAAS,gBAAgB,KAA0D;AACxF,MAAI;AACJ,MAAI;AACF,gBAAY,YAAY,GAAG;AAC3B,QAAI,CAAC,cAAc,SAAS,GAAG;AAC7B,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACpE;AAAA,EACF,SAAS,GAAG;AACV,WAAO,MAAM,8CAA8C;AAAA,MACzD,WAAW,IAAI;AAAA,MACf,WAAW,IAAI,MAAM,GAAG,GAAG;AAAA,MAC3B,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,IAClD,CAAC;AACD,UAAM,IAAI,MAAM,+CAA+C,EAAE,OAAO,EAAE,CAAC;AAAA,EAC7E;AAEA,MAAI;AACJ,MAAI;AACF,mBAAe,eAAe,SAAS;AAAA,EACzC,SAAS,GAAG;AACV,WAAO,KAAK,sDAAsD,EAAE,WAAW,OAAO,EAAE,CAAC;AACzF,mBAAe;AAAA,EACjB;AAEA,MAAI,WAAW;AACf,MAAI;AACF,eAAW,WAAW,YAAY;AAAA,EACpC,SAAS,GAAG;AACV,WAAO,KAAK,kEAAkE,EAAE,cAAc,mBAAmB,YAAY,GAAG,OAAO,EAAE,CAAC;AAAA,EAC5I;AAEA,MAAI;AACJ,MAAI;AACF,iBAAa,WAAW,KAAK,MAAM,QAAQ,CAAC;AAAA,EAC9C,SAAS,GAAG;AACV,WAAO,MAAM,yDAAyD,EAAE,UAAU,mBAAmB,QAAQ,GAAG,OAAO,EAAE,CAAC;AAC1H,UAAM,IAAI,MAAM,wBAAwB,aAAa,QAAQ,EAAE,UAAU,eAAe,IAAI,EAAE,OAAO,EAAE,CAAC;AAAA,EAC1G;AAEA,MAAI;AACF,UAAM,mBAAmB,CAAC,QAAmC;AAC3D,YAAM,OAAO,IAAI,KAAK,OAAK,KAAK,OAAO,MAAM,YAAY,MAAM,QAAS,EAA8B,QAAQ,KAAK,OAAQ,EAA8B,YAAY,QAAQ;AAC7K,UAAI,KAAM,QAAO;AAEjB,YAAM,OAAO,IAAI,KAAK,OAAK,KAAK,OAAO,MAAM,YAAY,MAAM,QAAS,EAA8B,QAAQ,CAAC;AAC/G,UAAI,KAAM,QAAO;AAEjB,aAAO,IAAI;AAAA,QAAK,OACd,KAAK,OAAO,MAAM,aACjB,cAAc,KAAK,yBAAyB,KAAK,aAAa,KAAK,yBAAyB;AAAA,MAC/F;AAAA,IACF;AAEA,QAAI,OAAO,MAAM,QAAQ,UAAU,IAAK,iBAAiB,UAAU,KAAK,WAAW,CAAC,KAAK,CAAC,IAAK;AAE/F,QAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,YAAM,MAAM;AACZ,UAAI,CAAC,IAAI,SAAU,KAAI,WAAW,CAAC;AACnC,UAAI,CAAC,IAAI,oBAAqB,KAAI,sBAAsB;AACxD,UAAI,CAAC,IAAI,oBAAqB,KAAI,sBAAsB;AAExD,UAAI,OAAO,IAAI,6BAA6B,UAAU;AACpD,YAAI,IAAI,2BAA2B,GAAG;AACpC,cAAI,2BAA2B,KAAK,IAAI,IAAI,2BAA2B,IAAI,CAAC;AAAA,QAC9E,WAAW,IAAI,2BAA2B,GAAG;AAC3C,cAAI,2BAA2B;AAAA,QACjC;AAAA,MACF,OAAO;AACL,YAAI,2BAA2B;AAAA,MACjC;AAEA,UAAI,MAAM,QAAQ,IAAI,QAAQ,GAAG;AAC/B,YAAI,WAAW,IAAI,SAAS,QAAQ,CAAC,YAAqB;AACxD,gBAAM,aAAa,iBAAiB,OAAO;AAC3C,iBAAO,aAAa,CAAC,UAAU,IAAI,CAAC;AAAA,QACtC,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT;AAEA,WAAO,4BAA4B,MAAM,IAAI;AAAA,EAC/C,SAAS,GAAG;AACV,WAAO,MAAM,2CAA2C,EAAE,YAAY,OAAO,EAAE,CAAC;AAChF,UAAM,IAAI,MAAM,6BAA6B,aAAa,QAAQ,EAAE,UAAU,YAAY,IAAI,EAAE,OAAO,EAAE,CAAC;AAAA,EAC5G;AACF;;;ACzWO,SAAS,mBAAmB,MAA+B;AAChE,QAAM,YAAY,oBAAI,IAA2B;AACjD,QAAM,QAAqD,CAAC;AAE5D,aAAW,QAAQ,KAAK,OAAO;AAC7B,UAAM,WAAW,KAAK,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,SAAS,KAAK,kBAAkB,MAAS;AACpG,QAAI,SAAS,WAAW,EAAG;AAE3B,SAAK,MAAM,QAAQ,CAAC,MAAM,cAAc;AACtC,YAAM,aAAa,iBAAiB,KAAK,OAAO;AAChD,UAAI,CAAC,WAAY;AAEjB,UAAI,SAAS;AACb,UAAI,KAAK,SAAS,SAAS,KAAK,kBAAkB,QAAW;AAC3D,iBAAS,KAAK,MAAM,MAAM,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,kBAAkB,MAAS,KACjG,KAAK,MAAM,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,kBAAkB,MAAS,KACtG,SAAS,CAAC;AAAA,MACjB;AAEA,YAAM,QAAqB,EAAE,QAAQ,YAAY,KAAK,KAAK;AAC3D,YAAM,KAAK,EAAE,YAAY,MAAM,MAAM,CAAC;AACtC,YAAM,WAAW,UAAU,IAAI,UAAU;AACzC,UAAI,SAAU,UAAS,KAAK,KAAK;AAAA,UAC5B,WAAU,IAAI,YAAY,CAAC,KAAK,CAAC;AAAA,IACxC,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,WAAW,MAAM;AAC5B;AAEO,SAAS,sBAAsB,UAAkC;AACtE,MAAI,OAAO,aAAa,SAAU,QAAO;AACzC,SAAO,SAAS,MAAM,IAAI,EAAE,IAAI,gBAAgB,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,KAAK;AACjF;AAIO,SAAS,uBAAuB,OAA+C;AACpF,QAAM,eAAe,oBAAI,IAAyB;AAElD,aAAW,QAAQ,OAAO;AACxB,eAAW,QAAQ,KAAK,OAAO;AAC7B,iBAAW,QAAQ,KAAK,OAAO;AAC7B,cAAM,aAAa,iBAAiB,KAAK,OAAO;AAChD,YAAI,WAAW,SAAS,kCAAmC;AAC3D,cAAM,QAAQ,aAAa,IAAI,UAAU;AACzC,YAAI,MAAO,OAAM,IAAI,KAAK,IAAI;AAAA,YACzB,cAAa,IAAI,YAAY,oBAAI,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAA2B,oBAAI,IAAI;AACzC,aAAW,CAAC,YAAY,KAAK,KAAK,cAAc;AAC9C,QAAI,MAAM,OAAO,EAAG,OAAM,IAAI,YAAY,MAAM,IAAI;AAAA,EACtD;AACA,SAAO;AACT;AASO,SAAS,gBACd,UACA,OACA,cACoB;AACpB,MAAI,OAAO,aAAa,SAAU,QAAO,EAAE,QAAQ,SAAS;AAE5D,QAAM,YAAY,sBAAsB,QAAQ;AAChD,MAAI,CAAC,UAAW,QAAO,EAAE,QAAQ,SAAS;AAC1C,MAAI,UAAU,SAAS,kCAAmC,QAAO,EAAE,QAAQ,OAAO;AAGlF,QAAM,aAAa,CAAC,eAA8B,WAAW,KAAK,CAAC,MAAM,EAAE,eAAe,SAAS;AAEnG,QAAM,UAAU,CAAC,eAA8B;AAC7C,UAAM,YAAY,WAAW,KAAK,CAAC,MAAM,EAAE,eAAe,SAAS,IAC/D,WAAW,OAAO,CAAC,MAAM,EAAE,eAAe,SAAS,IACnD;AACJ,QAAI,iBAAiB,OAAW,QAAO,UAAU,CAAC,EAAE;AACpD,WAAO,UAAU;AAAA,MAAO,CAAC,MAAM,cAC7B,KAAK,KAAK,UAAU,OAAO,iBAAiB,KAAK,YAAY,IACzD,KAAK,KAAK,KAAK,OAAO,iBAAiB,KAAK,YAAY,IACxD,YACA;AAAA,IACN,EAAE;AAAA,EACJ;AAEA,QAAM,QAAQ,MAAM,UAAU,IAAI,SAAS;AAC3C,MAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,WAAO,EAAE,QAAQ,WAAW,MAAM,QAAQ,KAAK,GAAG,SAAS,WAAW,KAAK,EAAE;AAAA,EAC/E;AAEA,QAAM,YAAY,MAAM,MAAM,QAAQ,CAAC,EAAE,YAAY,KAAK,MACxD,WAAW,UAAU,sCACjB,WAAW,SAAS,SAAS,KAAK,UAAU,SAAS,UAAU,KAC/D,CAAC,IAAI,IACL,CAAC,CAAC;AACR,MAAI,UAAU,SAAS,GAAG;AACxB,WAAO,EAAE,QAAQ,WAAW,MAAM,QAAQ,SAAS,GAAG,SAAS,WAAW,SAAS,EAAE;AAAA,EACvF;AAEA,SAAO,EAAE,QAAQ,YAAY;AAC/B;;;ACtHA,IAAM,gBAAwC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE;AAEnF,IAAM,MAAM,OAAO,aAAa,CAAC;AAE1B,SAAS,eAAe,UAAwD;AACrF,QAAM,OAAO,oBAAI,IAAiC;AAClD,aAAW,WAAW,UAAU;AAE9B,UAAM,kBAAkB,QAAQ,WAAW,SAAS,KAAK,sBAAsB,QAAQ,KAAK;AAC5F,QAAI,QAAQ,WAAW,UAAU,CAAC,iBAAiB;AACjD,WAAK,IAAI,aAAa,KAAK,IAAI,IAAI,OAAO;AAC1C;AAAA,IACF;AAEA,UAAM,MAAM,QAAQ,WAAW,SAC3B,OAAO,GAAG,GAAG,QAAQ,UAAU,EAAE,GAAG,GAAG,GAAG,QAAQ,IAAI,GAAG,GAAG,GAAG,QAAQ,cAAc,EAAE,KACvF,MAAM,GAAG,GAAG,QAAQ,IAAI,GAAG,GAAG,GAAG,QAAQ,cAAc,QAAQ,QAAQ,EAAE,GAAG,GAAG,GAAG,eAAe;AACrG,UAAM,WAAW,KAAK,IAAI,GAAG;AAC7B,QAAI,CAAC,UAAU;AACb,WAAK,IAAI,KAAK,OAAO;AACrB;AAAA,IACF;AACA,UAAM,OAAO,cAAc,QAAQ,QAAQ,KAAK;AAChD,UAAM,eAAe,cAAc,SAAS,QAAQ,KAAK;AACzD,UAAM,WACJ,OAAO,gBACN,SAAS,iBAAiB,QAAQ,mBAAmB,MAAM,SAAS,mBAAmB;AAC1F,QAAI,SAAU,MAAK,IAAI,KAAK,OAAO;AAAA,EACrC;AACA,SAAO,MAAM,KAAK,KAAK,OAAO,CAAC;AACjC;;;ACpBO,SAAS,kBAAkB,OAKtB;AACV,MAAI,MAAM,eAAe,EAAG,QAAO;AACnC,MAAI,MAAM,KAAK,aAAa,MAAM,gBAAgB,2BAA4B,QAAO;AACrF,SAAO,MAAM,QAAQ,KAAK,EAAE,SAAS;AACvC;;;ACtBA,SAAS,oCAAiE;AAC1E,SAAS,cAAAC,mBAAkB;AAC3B,OAAkB;AAYlB,SAAS,cAAc,OAA+C;AACpE,aAAW,OAAO,CAAC,sBAAsB,QAAQ,QAAQ,aAAa,UAAU,GAAY;AAC1F,UAAM,QAAQ,MAAM,GAAG;AACvB,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAG,QAAO,MAAM,KAAK;AAAA,EACnE;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAoC;AAC/D,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACjE,MAAI,QAAQ,EAAG,QAAO,KAAK,IAAI,QAAQ,IAAI,CAAC;AAC5C,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO;AACT;AAEA,SAAS,wBAAwB,OAAgB,cAAuC;AACtF,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,QAAO;AACxE,QAAM,IAAI;AACV,QAAM,OAAO,cAAc,CAAC,KAAK;AACjC,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,CAAC,MAAM,QAAQ,EAAE,QAAQ,EAAG,QAAO;AAEvC,SAAO;AAAA,IACL,oBAAoB;AAAA,IACpB,UAAU,EAAE,SAAS,QAAQ,CAAC,YAAY;AACxC,YAAM,aAAa,iBAAiB,OAAO;AAC3C,aAAO,aAAa,CAAC,UAAU,IAAI,CAAC;AAAA,IACtC,CAAC;AAAA,IACD,qBAAqB,OAAO,EAAE,wBAAwB,YAAY,EAAE,sBAAsB,EAAE,sBAAsB;AAAA,IAClH,qBAAqB,OAAO,EAAE,wBAAwB,YAAY,EAAE,sBAAsB,EAAE,sBAAsB;AAAA,IAClH,0BAA0B,oBAAoB,EAAE,wBAAwB;AAAA,EAC1E;AACF;AAEA,SAAS,oBAAoB,YAAuC;AAClE,QAAM,OAAO,cAAc,OAAO,eAAe,WAAY,aAAyC;AACtG,QAAM,QAAQ,MAAM,UAAU,MAAM,QAAQ,UAAU,IAAI,aAAa;AAEvE,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,QAAQ,CAAC,UAAU;AAC9B,YAAM,aAAa,wBAAwB,KAAK;AAChD,aAAO,aAAa,CAAC,UAAU,IAAI,CAAC;AAAA,IACtC,CAAC;AAAA,EACH;AACA,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,WAAO,OAAO,QAAQ,KAAgC,EAAE,QAAQ,CAAC,CAAC,MAAM,KAAK,MAAM;AACjF,YAAM,aAAa,wBAAwB,OAAO,IAAI;AACtD,aAAO,aAAa,CAAC,UAAU,IAAI,CAAC;AAAA,IACtC,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAMO,SAAS,qBAAqB,KAA8B;AACjE,MAAI;AACJ,MAAI;AACF,gBAAY,YAAY,KAAK,OAAO;AACpC,QAAI,CAAC,cAAc,SAAS,GAAG;AAC7B,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACpE;AAAA,EACF,SAAS,GAAG;AACV,WAAO,MAAM,sDAAsD;AAAA,MACjE,WAAW,IAAI;AAAA,MACf,WAAW,IAAI,MAAM,GAAG,GAAG;AAAA,MAC3B,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,IAClD,CAAC;AACD,UAAM,IAAI,MAAM,uDAAuD,EAAE,OAAO,EAAE,CAAC;AAAA,EACrF;AAEA,MAAI;AACJ,MAAI;AACF,mBAAe,eAAe,SAAS;AAAA,EACzC,SAAS,GAAG;AACV,WAAO,KAAK,sDAAsD,EAAE,WAAW,OAAO,EAAE,CAAC;AACzF,mBAAe;AAAA,EACjB;AAEA,MAAI,WAAW;AACf,MAAI;AACF,eAAWC,YAAW,YAAY;AAAA,EACpC,SAAS,GAAG;AACV,WAAO,KAAK,0EAA0E,EAAE,cAAc,mBAAmB,YAAY,GAAG,OAAO,EAAE,CAAC;AAAA,EACpJ;AAEA,MAAI;AACJ,MAAI;AACF,iBAAa,WAAW,KAAK,MAAM,QAAQ,CAAC;AAAA,EAC9C,SAAS,GAAG;AACV,WAAO,MAAM,yDAAyD,EAAE,UAAU,mBAAmB,QAAQ,GAAG,OAAO,EAAE,CAAC;AAC1H,UAAM,IAAI,MAAM,wBAAwB,aAAa,QAAQ,EAAE,UAAU,eAAe,IAAI,EAAE,OAAO,EAAE,CAAC;AAAA,EAC1G;AAEA,QAAM,UAAU,oBAAoB,UAAU;AAC9C,QAAM,OAAO,cAAc,OAAO,eAAe,YAAY,CAAC,MAAM,QAAQ,UAAU,IACjF,aACD,CAAC;AAEL,MAAI,CAAC,SAAS,QAAQ;AACpB,QAAI,MAAM,QAAQ,KAAK,QAAQ,EAAG,QAAO,EAAE,OAAO,QAAQ,MAAM,gBAAgB,GAAG,EAAE;AACrF,WAAO,MAAM,iEAAiE;AAAA,MAC5E,YAAY,mBAAmB,KAAK,UAAU,cAAc,IAAI,CAAC;AAAA,IACnE,CAAC;AACD,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AAEA,MAAI;AACF,WAAO;AAAA,MACL,OAAO;AAAA,MACP,MAAM,6BAA6B,MAAM;AAAA,QACvC,OAAO;AAAA,QACP,0BAA0B,oBAAoB,KAAK,wBAAwB,KAAK;AAAA,MAClF,CAAC;AAAA,IACH;AAAA,EACF,SAAS,GAAG;AACV,WAAO,MAAM,mDAAmD,EAAE,YAAY,OAAO,EAAE,CAAC;AACxF,UAAM,IAAI,MAAM,qCAAqC,aAAa,QAAQ,EAAE,UAAU,YAAY,IAAI,EAAE,OAAO,EAAE,CAAC;AAAA,EACpH;AACF;;;AC9GA,IAAM,WAAW,CAAC,SAAiB,KAAK,MAAM,GAAG,EAAE,IAAI,KAAK;AAG5D,SAAS,iBAAiB,UAAkB,YAAiC,SAAuC;AAClH,QAAM,YAAY,CAAC,YAAiC,QAAQ,KAAK,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,IAAI,CAAC,KAAK;AAEjG,QAAM,QAAQ,WAAW,OAAO,CAAC,MAAM,SAAS,EAAE,MAAM,QAAQ,CAAC;AACjE,QAAM,UAAU,WAAW,OAAO,CAAC,MAAM,EAAE,gBAAgB,SAAS,EAAE,cAAc,QAAQ,CAAC;AAC7F,MAAI,MAAM,SAAS,KAAK,QAAQ,SAAS,EAAG,QAAO,UAAU,KAAK,KAAK,UAAU,OAAO;AAExF,QAAM,WAAW,SAAS,KAAK,EAAE,QAAQ,SAAS,EAAE,EAAE,QAAQ,WAAW,EAAE,EAAE,QAAQ,OAAO,EAAE;AAC9F,QAAM,WAAW,WAAW,OAAO,CAAC,MAAM,EAAE,KAAK,SAAS,IAAI,QAAQ,EAAE,CAAC;AACzE,MAAI,SAAS,WAAW,EAAG,QAAO,UAAU,QAAQ;AAEpD,QAAM,QAAQ,WAAW,OAAO,CAAC,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS,QAAQ,CAAC;AAC9E,SAAO,MAAM,WAAW,IAAI,UAAU,KAAK,IAAI;AACjD;AAEA,SAAS,YACP,MACA,OACA,kBACA,WACA,iBACA,OACoB;AACpB,aAAW,WAAW,MAAM,UAAwE;AAClG,UAAM,UAAU,QAAQ,eAAe,oBAAoB,KAAK;AAChE,QAAI,WAAW,CAAC,SAAS,SAAS,KAAK,IAAI,EAAG,OAAM,wBAAwB;AAAA,EAC9E;AAEA,SAAO;AAAA,IACL,EAAE,GAAG,OAAO,UAAU,MAAM,UAAmB,0BAA0B,gBAAgB;AAAA,IACzF;AAAA,IACA,EAAE,kBAAkB,WAAW,EAAE,OAAO,UAAU,OAAO,UAAU,KAAK,MAAM,OAAO,UAAU,MAAM,EAAE;AAAA,EACzG;AACF;AAEA,SAAS,YAAY,SAA0C,KAAa,OAAwB;AAClG,aAAW,CAAC,MAAM,MAAM,KAAK,SAAS;AACpC,QAAI,OAAO,SAAS,UAAU,IAAK;AAEnC,UAAM,SAAS,OAAO,SAAS,SAAS,CAAC,GAAG,MAAM,eAAe,QAAQ,EAAE,QAAQ,IAAI,eAAe,QAAQ,EAAE,QAAQ,CAAC;AACzH,UAAM,UAAU,OAAO,MAAM,GAAG;AAChC,UAAM,WAAW,QAAQ;AAEzB,UAAM,SAAS;AACf,UAAM,UAAU,QAAQ,IAAI,CAAC,MAAM,kBAAkB,EAAE,KAAK,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,IAAI;AACtF,YAAQ,IAAI,MAAM;AAAA,MAChB,GAAG;AAAA,MACH,UAAU,OAAO,MAAM,GAAG,GAAG;AAAA,MAC7B,aAAa,OAAO,YAAY,SAAS,MAAM,IAC3C,GAAG,OAAO,WAAW;AAAA,EAAK,OAAO,KACjC,GAAG,OAAO,WAAW;AAAA;AAAA,EAAO,MAAM;AAAA,EAAK,OAAO;AAAA,IACpD,CAAC;AAAA,EACH;AACF;AAEO,SAAS,yBACd,KACA,OACA,SACmB;AACnB,QAAM,QAAyB;AAAA,IAC7B,mBAAmB;AAAA,IAAG,sBAAsB;AAAA,IAAG,oBAAoB;AAAA,IACnE,cAAc;AAAA,IAAG,SAAS;AAAA,IAAG,iBAAiB;AAAA,EAChD;AACA,QAAM,UAAU,qBAAqB,GAAG;AACxC,QAAM,UAAU,oBAAI,IAAgC;AACpD,QAAM,YAAuB,EAAE,OAAO,uBAAuB,KAAK,GAAG,OAAO,EAAE,oBAAoB,EAAE,EAAE;AACtG,QAAM,UAAU,oBAAI,IAAY;AAEhC,QAAM,SAAS,CAAC,MAAgB,OAAiB,eAC/C,QAAQ,IAAI,KAAK,MAAM,YAAY,MAAM,OAAO,SAAS,kBAAkB,WAAW,YAAY,KAAK,CAAC;AAE1G,MAAI,QAAQ,UAAU,QAAQ;AAC5B,UAAM,eAAe;AACrB,UAAM,kBAAkB;AAExB,UAAM,SAAS,oBAAI,IAAwE;AAC3F,eAAW,WAAW,QAAQ,KAAK,UAAU;AAC3C,YAAM,WAAW,QAAQ,cAAc,oBAAoB,KAAK;AAChE,YAAM,SAAS,CAAC,YAAY,MAAM,WAAW,IAAI,MAAM,CAAC,IAAI,iBAAiB,YAAY,IAAI,OAAO,OAAO;AAC3G,UAAI,CAAC,QAAQ;AACX,cAAM,qBAAqB;AAC3B;AAAA,MACF;AACA,YAAM,SAAS,OAAO,IAAI,OAAO,IAAI;AACrC,UAAI,OAAQ,QAAO,SAAS,KAAK,OAAO;AAAA,UACnC,QAAO,IAAI,OAAO,MAAM,EAAE,MAAM,QAAQ,UAAU,CAAC,OAAO,EAAE,CAAC;AAAA,IACpE;AAEA,eAAW,EAAE,MAAM,SAAS,KAAK,OAAO,OAAO,GAAG;AAChD,aAAO,MAAM;AAAA,QACX;AAAA,QACA,qBAAqB,QAAQ,KAAK;AAAA,QAClC,qBAAqB,QAAQ,KAAK;AAAA,MACpC,GAAG,QAAQ,KAAK,wBAAwB;AAAA,IAC1C;AAAA,EACF,OAAO;AACL,UAAM,kBAAkB,QAAQ,KAAK,MAAM;AAC3C,eAAW,SAAS,QAAQ,KAAK,OAAO;AACtC,YAAM,OAAO,iBAAiB,MAAM,oBAAoB,OAAO,OAAO;AACtE,UAAI,CAAC,MAAM;AACT,cAAM,qBAAqB;AAC3B,eAAO,KAAK,wDAAwD,EAAE,UAAU,MAAM,mBAAmB,CAAC;AAC1G;AAAA,MACF;AACA,cAAQ,IAAI,KAAK,IAAI;AACrB,aAAO,MAAM,OAAO,MAAM,4BAA4B,QAAQ,KAAK,wBAAwB;AAAA,IAC7F;AAAA,EACF;AAEA,QAAM,qBAAqB,UAAU,MAAM;AAC3C,MAAI,SAAS,mBAAoB,aAAY,SAAS,oBAAoB,QAAQ,kBAAkB,GAAG,KAAK;AAE5G,SAAO,EAAE,SAAS,SAAS,MAAM,QAAQ,CAAC,MAAO,QAAQ,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAE,GAAG,MAAM;AAChG;;;AN7GO,SAAS,SAAS,GAAW,GAAoB;AACtD,QAAM,QAAQ,CAAC,MAAc,EAAE,KAAK,EAAE,QAAQ,SAAS,EAAE,EAAE,QAAQ,WAAW,EAAE,EAAE,QAAQ,OAAO,EAAE;AACnG,SAAO,MAAM,CAAC,MAAM,MAAM,CAAC;AAC7B;AAQA,SAAS,eAAe,MAAc,gBAAyB;AAC7D,MAAI,CAAC,eAAgB,QAAO;AAE5B,QAAM,kBAAkB,eAAe,QAAQ,yBAAyB,EAAE,EAAE,KAAK;AAEjF,QAAM,YAAY,KAAK,MAAM,eAAe,EAAE,CAAC,EAAE,KAAK;AAEtD,SAAO,GAAG,SAAS;AAAA;AAAA;AAAA,EAAyB,eAAe;AAAA;AAC7D;AAEA,IAAM,qBAA+E;AAAA,EACnF,EAAE,SAAS,kDAAkD,WAAW,0BAA0B;AAAA,EAClG,EAAE,SAAS,uDAAuD,WAAW,cAAc;AAC7F;AAEA,SAAS,gBAAgB,WAAsB,OAAe,MAAc,UAAiC;AAC3G,MAAI,cAAc,QAAS,QAAO;AAClC,QAAM,OAAO,GAAG,KAAK;AAAA,EAAK,IAAI;AAE9B,MAAI,8BAA8B,OAAO,IAAI,GAAG;AAC9C,aAAS;AACT,WAAO;AAAA,EACT;AAEA,aAAW,EAAE,SAAS,WAAW,SAAS,KAAK,oBAAoB;AACjE,QAAI,QAAQ,KAAK,IAAI,GAAG;AACtB,eAAS;AACT,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,eAAe,GAAqB;AAC3C,SAAO,EAAE,MAAM,QAAQ,EAAE,GAAG,KAAK,EAAE,KAAK,OAAO,EAAE,IAAI,KAAK,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI;AACvF;AAEA,SAAS,wBACP,SACA,eACA,eACA,WACiD;AACjD,QAAM,eAAe,QAAQ,cAAc,QAAQ,QAAQ,cAAc,YAAY;AAErF,gBAAc,SAAS;AACvB,QAAM,WAAW,gBAAgB,QAAQ,UAAU,eAAe,YAAY;AAC9E,MAAI,SAAS,WAAW,UAAW,eAAc,WAAW;AAAA,WACnD,SAAS,WAAW,YAAa,eAAc,aAAa;AAAA,WAC5D,SAAS,WAAW,OAAQ,eAAc,QAAQ;AAAA,WAClD,SAAS,WAAW,SAAU,eAAc,UAAU;AAE/D,MAAI,SAAS,WAAW,WAAW;AACjC,WAAO,EAAE,UAAU,EAAE,OAAO,QAAQ,OAAO,MAAM,QAAQ,MAAM,KAAK,cAAc,SAAS,MAAM,GAAG,EAAE;AAAA,EACxG;AAMA,MAAI,CAAC,SAAS,SAAS;AACrB,kBAAc,eAAe;AAC7B,WAAO,EAAE,UAAU,EAAE,OAAO,QAAQ,OAAO,MAAM,QAAQ,MAAM,KAAK,0BAA0B,EAAE;AAAA,EAClG;AAEA,MAAI,WAAW;AACb,UAAM,YAAY,sBAAsB,QAAQ,QAAQ;AACxD,UAAM,cAAc,QAAQ,cAAc,oBAAoB,KAAK;AACnE,UAAM,qBAAqB,aAAa,UAAU,MAAM,IAAI,SAAS,KAAK,KAAK,IAAI;AACnF,QAAI,sBAAsB,eAAe,CAAC,SAAS,aAAa,UAAU,QAAQ,GAAG;AACnF,gBAAU,MAAM,sBAAsB;AACtC,aAAO;AAAA,QACL,UAAU;AAAA,UACR,OAAO,QAAQ;AAAA,UACf,MAAM,QAAQ;AAAA,UACd,KAAK;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,SAAS,KAAK;AACnC;AAEA,SAAS,qBACP,MACA,UACA,gBACA,SAC6D;AAC7D,QAAM,OAAO,SAAS;AACtB,QAAM,WAAW,oBAAoB,MAAM,IAAI;AAE/C,MAAI,aAAa,UAAa,CAAC,eAAe,IAAI,QAAQ,GAAG;AAC3D,WAAO,EAAE,UAAU,EAAE,OAAO,QAAQ,OAAO,MAAM,QAAQ,KAAK,EAAE;AAAA,EAClE;AAEA,SAAO,EAAE,MAAM,SAAS;AAC1B;AAEA,SAAS,qBAAqB,SAAiG;AAC7H,QAAM,cAA+D;AAAA,IACnE,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACA,QAAM,WAAW,QAAQ,aAAa,SAClC,YAAY,QAAQ,QAAQ,KAAK,OACjC;AAEJ,QAAM,YAAY,CAAC,SAAiB;AAClC,QAAI,UAAU,KAAK,KAAK;AACxB,QAAI,OAAO;AACX,WAAO,YAAY,MAAM;AACvB,aAAO;AACP,gBAAU,QACP,QAAQ,mFAAmF,EAAE,EAC7F,QAAQ,UAAU,GAAG,EACrB,KAAK;AAAA,IACV;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,UAAU,QAAQ,KAAK;AACrC,MAAI,OAAO,UAAU,QAAQ,IAAI;AAEjC,QAAM,aAAa,UAAU,KAAK,MAAM,IAAI,EAAE,CAAC,CAAC;AAChD,MAAI,WAAW,YAAY,EAAE,WAAW,MAAM,YAAY,CAAC,KAAK,MAAM,YAAY,EAAE,WAAW,WAAW,YAAY,CAAC,GAAG;AACxH,WAAO,UAAU,KAAK,MAAM,KAAK,MAAM,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC;AAAA,EACzD;AAEA,SAAO,EAAE,UAAU,OAAO,KAAK;AACjC;AAEA,SAAS,eACP,SACA,OACA,MACA,eACA,kBACA,iBACA,mBACmD;AACnD,QAAM,YAAY,gBAAgB,YAAY,QAAQ,UAAU,GAAG,OAAO,MAAM,MAAM;AACpF,oBAAgB,cAAc,gBAAgB,cAAc,KAAK;AAAA,EACnE,CAAC;AAED,kBAAgB,SAAS,KAAK,gBAAgB,SAAS,KAAK,KAAK;AAEjE,MAAI,iBAAiB,IAAI,SAAS,GAAG;AACnC,sBAAkB,SAAS,KAAK,kBAAkB,SAAS,KAAK,KAAK;AACrE,WAAO,EAAE,UAAU,EAAE,OAAO,MAAM,KAAK,gBAAgB,SAAS,GAAG,EAAE;AAAA,EACvE;AAEA,MAAI,2BAA2B,EAAE,OAAO,MAAM,cAAc,CAAC,GAAG;AAC9D,sBAAkB,+BAA+B,kBAAkB,+BAA+B,KAAK;AACvG,WAAO,EAAE,UAAU,EAAE,OAAO,MAAM,KAAK,qBAAqB,EAAE;AAAA,EAChE;AAEA,QAAM,cAAc,uBAAuB,EAAE,OAAO,KAAK,CAAC;AAC1D,MAAI,aAAa;AACf,UAAM,MAAM,eAAe,YAAY,QAAQ,KAAK,GAAG,CAAC;AACxD,sBAAkB,GAAG,KAAK,kBAAkB,GAAG,KAAK,KAAK;AACzD,WAAO,EAAE,UAAU,EAAE,OAAO,MAAM,KAAK,WAAW,WAAW,GAAG,EAAE;AAAA,EACpE;AAEA,SAAO,EAAE,UAAU;AACrB;AAEA,SAAS,mBAAmB,QAUJ;AACtB,QAAM,EAAE,MAAM,MAAM,UAAU,UAAU,OAAO,MAAM,WAAW,eAAe,QAAQ,IAAI;AAE3F,QAAM,kBAAkB,OAAO,QAAQ,qBAAqB,WACxD,QAAQ,mBACR;AAEJ,QAAM,iBAAiB,OAAO,QAAQ,oBAAoB,YAAY,QAAQ,gBAAgB,KAAK,IAC/F,QAAQ,kBACR;AAEJ,SAAO,0BAA0B,MAAM;AAAA,IACrC,MAAM,KAAK;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,oBAAoB,SAAS;AAAA,IACvC;AAAA,IACA,gBAAgB,kBAAkB,MAAM,IAAI,KAAK;AAAA,IACjD;AAAA,IACA,MAAM,eAAe,MAAM,cAAc;AAAA,IACzC;AAAA,IACA;AAAA,IACA,UAAU,OAAO,QAAQ,aAAa,YAAY,QAAQ,SAAS,KAAK,IAAI,QAAQ,SAAS,KAAK,IAAI;AAAA,IACtG,aAAa,wBAAwB,KAAK,MAAM,KAAK;AAAA,IACrD,YAAY,gBAAgB,gBAAgB,aAAa,IAAI;AAAA,IAC7D,eAAe;AAAA,MACb,KAAK;AAAA,MACL;AAAA,MACA,gBAAgB,gBAAgB,aAAa,IAAI;AAAA,IACnD,KAAK;AAAA,EACP,CAAC;AACH;AAyBO,SAAS,qBACd,QACA,MACA,SACoB;AACpB,QAAM,iBAAiB,kBAAkB,IAAI;AAC7C,QAAM,gBAAgB,mBAAmB,IAAI;AAC7C,QAAM,gBAAgB,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,aAAa,EAAE;AAC/F,QAAM,kBAA0C,CAAC;AACjD,QAAM,oBAA4C,CAAC;AACnD,QAAM,mBAAmB,IAAI,IAAe,SAAS,oBAAoB,CAAC,CAAC;AAC3E,QAAM,gBAAgB,mBAAmB,MAAM,SAAS,WAAW;AACnE,QAAM,oBAAoB,EAAE,eAAe,GAAG,qBAAqB,GAAG,SAAS,EAAE;AACjF,QAAM,mBAA6B,CAAC;AAEpC,QAAM,YAAY,OAAO,YAAY,CAAC,GACnC,IAAI,CAAC,YAAwC;AAC5C,UAAM,WAAW,wBAAwB,SAAS,eAAe,eAAe,SAAS,SAAS;AAClG,QAAI,cAAc,UAAU;AAC1B,uBAAiB,KAAK,eAAe,SAAS,QAAQ,CAAC;AACvD,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,qBAAqB,MAAM,SAAS,UAAU,gBAAgB,OAAO;AACtF,QAAI,cAAc,UAAU;AAC1B,uBAAiB,KAAK,eAAe,SAAS,QAAQ,CAAC;AACvD,aAAO;AAAA,IACT;AAEA,UAAM,EAAE,UAAU,OAAO,KAAK,IAAI,qBAAqB,OAAO;AAE9D,UAAM,gBAAgB,SAAS,SAAS,WACnC,KAAK,MAAM,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,kBAAkB,SAAS,IAAI,GAAG,WACnF;AAEL,UAAM,QAAQ,eAAe,SAAS,OAAO,MAAM,eAAe,kBAAkB,iBAAiB,iBAAiB;AACtH,QAAI,cAAc,OAAO;AACvB,uBAAiB,KAAK,eAAe,MAAM,QAAQ,CAAC;AACpD,aAAO;AAAA,IACT;AAKA,UAAM,UAAU,kBAAkB,EAAE,OAAO,MAAM,YAAY,SAAS,MAAM,OAAO,cAAc,CAAC;AAClG,QAAI,QAAQ,WAAW,WAAW;AAChC,wBAAkB,iBAAiB;AACnC,wBAAkB,uBAAuB;AACzC,wBAAkB,WAAW;AAK7B,UAAI,aAAa,MAAM;AACrB,eAAO,KAAK,+BAA+B,KAAK,IAAI,IAAI;AAAA,UACtD,YAAY,QAAQ;AAAA,UACpB,aAAa,QAAQ;AAAA,UACrB;AAAA,QACF,CAAC;AACD,yBAAiB,KAAK,eAAe;AAAA,UACnC;AAAA,UACA;AAAA,UACA,KAAK,mBAAmB,QAAQ,UAAU;AAAA,QAC5C,CAAC,CAAC;AACF,eAAO;AAAA,MACT;AAAA,IACF,WAAW,QAAQ,WAAW,sBAAsB;AAClD,wBAAkB,iBAAiB;AACnC,UAAI,QAAQ,WAAW,mBAAmB,QAAQ,WAAW,wBAAwB;AACnF,0BAAkB,uBAAuB;AAAA,MAC3C;AAAA,IACF;AAGA,QAAI;AACF,aAAO,mBAAmB;AAAA,QACxB;AAAA,QACA,MAAM,SAAS;AAAA,QACf,UAAU,SAAS;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,EAAE,iBAAiBC,GAAE,UAAW,OAAM;AAE1C,uBAAiB,KAAK,eAAe;AAAA,QACnC,OAAO,QAAQ;AAAA,QACf,MAAM,QAAQ;AAAA,QACd,KAAK;AAAA,MACP,CAAC,CAAC;AACF,aAAO,KAAK,iDAAiD;AAAA,QAC3D,MAAM,KAAK;AAAA,QACX,OAAO,QAAQ;AAAA,QACf,OAAO,MAAM;AAAA,MACf,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF,CAAC,EACA,OAAO,CAAC,YAA4C,QAAQ,OAAO,CAAC;AAEvE,QAAM,UAAU,OAAO,oBAAoB,YAAY,EAAE,SAAS,kBAAkB,IAAI,YAAY;AACpG,MAAI,cAAc,OAAO;AAEzB,MAAI,iBAAiB,SAAS,GAAG;AAC/B,mBAAe;AAAA;AAAA;AAAA,EAA2C,iBAAiB,KAAK,IAAI,CAAC;AAAA,EACvF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS,SAAS,SAAS,IAAI,YAAY;AAAA,IAC3C;AAAA,IACA,oBAAoB,OAAO;AAAA,IAC3B,iBAAiB,OAAO;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,wBACd,KACA,MACA,SACoB;AACpB,SAAO,qBAAqB,gBAAgB,GAAG,GAAG,MAAM,OAAO;AACjE;","names":["z","jsonrepair","jsonrepair","z"]}
@@ -1,19 +1,19 @@
1
- import {
2
- MAX_RULE_SCAN_ADDED_LINES
3
- } from "./chunk-FGXA7JNR.js";
4
1
  import {
5
2
  RULES
6
- } from "./chunk-R2HRWFRH.js";
3
+ } from "./chunk-JAF7JIZQ.js";
4
+ import {
5
+ MAX_RULE_SCAN_ADDED_LINES
6
+ } from "./chunk-W3RY755V.js";
7
7
  import {
8
8
  commentSyntaxFor,
9
9
  stripCommentsAndStrings
10
- } from "./chunk-U3VKVDZ7.js";
10
+ } from "./chunk-IKVUW4WC.js";
11
11
  import {
12
12
  buildAnchorHash,
13
13
  buildFindingFingerprint,
14
14
  buildFindingFingerprintV2,
15
15
  normalizeDiffText
16
- } from "./chunk-NSTV4TRP.js";
16
+ } from "./chunk-XPYVLHSX.js";
17
17
 
18
18
  // src/rules/detect.ts
19
19
  import { CLAIM_TYPE_CATEGORY } from "@codraoss/schema";
@@ -114,4 +114,4 @@ export {
114
114
  scanFileForRuleHits,
115
115
  ruleHitsToComments
116
116
  };
117
- //# sourceMappingURL=chunk-ILZUCIZH.js.map
117
+ //# sourceMappingURL=chunk-EQL5OOEF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/rules/detect.ts"],"sourcesContent":["import type { ClaimType, ParsedReviewComment } from '@codraoss/schema';\nimport type { DiffLine, FileDiff } from '../diff';\nimport { commentSyntaxFor, stripCommentsAndStrings } from '../claim-checks';\nimport { buildAnchorHash, buildFindingFingerprint, buildFindingFingerprintV2, normalizeDiffText } from '../fingerprint';\nimport { CLAIM_TYPE_CATEGORY } from '@codraoss/schema';\nimport { RULES, type Rule } from './table';\n\nimport { MAX_RULE_SCAN_ADDED_LINES } from '../constants';\n\nexport type RuleHit = {\n rule: Rule;\n line: DiffLine;\n shadow: boolean;\n};\n\nexport type RuleScanStats = {\n addedLinesScanned: number;\n sievePassed: number;\n hits: number;\n shadowHits: number;\n suppressedAsMoved: number;\n unstrippable: number;\n truncated: boolean;\n byRule: Record<string, number>;\n};\n\nexport type RuleScanResult = { hits: RuleHit[]; stats: RuleScanStats };\n\nexport type RuleScanOptions = {\n disabledRuleIds?: readonly string[];\n shadowRuleIds?: readonly string[];\n deniedClaimTypes?: readonly ClaimType[];\n};\n\nfunction extensionOf(path: string) {\n return path.toLowerCase().split('.').pop() ?? '';\n}\n\nfunction ruleApplies(rule: Rule, ext: string) {\n return !rule.extensions || rule.extensions.includes(ext);\n}\n\nexport function scanFileForRuleHits(file: FileDiff, options: RuleScanOptions = {}): RuleScanResult {\n const stats: RuleScanStats = {\n addedLinesScanned: 0,\n sievePassed: 0,\n hits: 0,\n shadowHits: 0,\n suppressedAsMoved: 0,\n unstrippable: 0,\n truncated: false,\n byRule: {},\n };\n const hits: RuleHit[] = [];\n\n if (file.isDeleted || file.isBinary || !file.path) return { hits, stats };\n\n const ext = extensionOf(file.path);\n const denied = new Set(options.deniedClaimTypes ?? []);\n const disabled = new Set(options.disabledRuleIds ?? []);\n const shadowIds = new Set(options.shadowRuleIds ?? []);\n\n const active = RULES.filter((rule) =>\n rule.enabled\n && !disabled.has(rule.id)\n && !denied.has(rule.claimType)\n && ruleApplies(rule, ext));\n if (active.length === 0) return { hits, stats };\n\n const triggers = [...new Set(active.flatMap((rule) => rule.triggers))];\n const syntax = commentSyntaxFor(file.path);\n\n for (const hunk of file.hunks) {\n const removed = new Set<string>();\n for (const l of hunk.lines) {\n if (l.kind === 'del') removed.add(normalizeDiffText(l.content));\n }\n\n for (const line of hunk.lines) {\n if (line.kind !== 'add') continue;\n if (stats.addedLinesScanned >= MAX_RULE_SCAN_ADDED_LINES) {\n stats.truncated = true;\n break;\n }\n stats.addedLinesScanned += 1;\n\n const raw = line.content;\n if (!triggers.some((trigger) => raw.includes(trigger))) continue;\n stats.sievePassed += 1;\n\n const stripped = stripCommentsAndStrings(raw, syntax);\n if (stripped === null) {\n stats.unstrippable += 1;\n continue;\n }\n\n for (const rule of active) {\n if (!rule.triggers.some((trigger) => raw.includes(trigger))) continue;\n if (!rule.pattern.test(stripped)) continue;\n if (rule.rejectRaw?.test(raw)) continue;\n\n if (removed.has(normalizeDiffText(raw))) {\n stats.suppressedAsMoved += 1;\n continue;\n }\n\n const shadow = shadowIds.has(rule.id);\n hits.push({ rule, line, shadow });\n stats.byRule[rule.id] = (stats.byRule[rule.id] ?? 0) + 1;\n if (shadow) stats.shadowHits += 1;\n else stats.hits += 1;\n break;\n }\n }\n if (stats.truncated) break;\n }\n\n return { hits, stats };\n}\n\nexport function ruleHitsToComments(file: FileDiff, result: RuleScanResult): ParsedReviewComment[] {\n const comments: ParsedReviewComment[] = [];\n for (const hit of result.hits) {\n if (hit.shadow) continue;\n\n const { rule, line } = hit;\n const anchorHash = buildAnchorHash(line.content);\n comments.push({\n path: file.path,\n line: line.newLineNumber ?? null,\n position: line.position ?? null,\n severity: rule.severity,\n category: CLAIM_TYPE_CATEGORY[rule.claimType] ?? 'quality',\n title: rule.title,\n body: rule.body,\n evidence: line.content,\n anchorHash,\n claimType: rule.claimType,\n fingerprint: buildFindingFingerprint(file.path, `${rule.title} @${anchorHash}`),\n fingerprintV2: buildFindingFingerprintV2(file.path, rule.claimType, anchorHash),\n source: 'rule' as const,\n ruleId: rule.id,\n } satisfies ParsedReviewComment);\n }\n\n return comments;\n}\n"],"mappings":"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","names":[]}
@@ -75,8 +75,14 @@ function getLanguageForFile(path) {
75
75
  }
76
76
  return matches[0];
77
77
  }
78
+ function isChangelogPath(path) {
79
+ const name = path.split("/").pop()?.toLowerCase() ?? "";
80
+ const stem = name.replace(/\.(md|mdx|markdown|rst|txt)$/, "");
81
+ return stem === "changelog" || stem === "changes" || stem === "history" || stem === "news" || stem === "release-notes" || stem === "release_notes" || stem === "releasenotes";
82
+ }
78
83
 
79
84
  export {
80
- getLanguageForFile
85
+ getLanguageForFile,
86
+ isChangelogPath
81
87
  };
82
- //# sourceMappingURL=chunk-RQ5S67XB.js.map
88
+ //# sourceMappingURL=chunk-FKRCBUWZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/prompts/languages.ts"],"sourcesContent":["export type LanguageGuideline = {\n language: string;\n extensions: string[];\n guidelines: string[];\n persona?: string;\n};\n\nconst LANGUAGE_GUIDELINES: LanguageGuideline[] = [\n {\n language: 'TypeScript/JavaScript',\n persona: 'an expert TypeScript engineer focused on correctness and safe async code',\n extensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],\n guidelines: [\n 'Flag unhandled promise rejections, missing await, or async errors that can crash or silently drop work.',\n 'Flag resource leaks that cause real bugs (uncleared timers/intervals/listeners on a path that runs repeatedly).',\n 'Flag security pitfalls such as eval() on untrusted input or ReDoS-prone regexes.',\n 'Flag runtime-breaking null/undefined access introduced by the diff.',\n ],\n },\n {\n language: 'Python',\n persona: 'a Python engineer focused on correctness',\n extensions: ['py'],\n guidelines: [\n 'Flag mutable default arguments that cause shared-state bugs.',\n 'Flag bare \"except:\" that swallows errors and hides failures.',\n 'Flag incorrect exception handling or resource handling (files/sockets not closed).',\n ],\n },\n {\n language: 'CSS/SCSS/Less',\n persona: 'a frontend engineer',\n extensions: ['css', 'scss', 'sass', 'less'],\n guidelines: [\n 'Flag only rules that break layout or rendering; do not report stylistic preferences.',\n ],\n },\n {\n language: 'SQL',\n persona: 'a database engineer focused on query safety and correctness',\n extensions: ['sql'],\n guidelines: [\n 'Flag SQL injection risks (unparameterized/interpolated user input).',\n 'Flag destructive or non-atomic migrations that risk data loss.',\n ],\n },\n {\n language: 'Markdown',\n persona: 'a technical writer',\n extensions: ['md', 'mdx'],\n guidelines: [\n 'Flag only broken links/images or factually incorrect content; do not report style or grammar nits.',\n ],\n },\n {\n language: 'HTML',\n persona: 'a web engineer',\n extensions: ['html', 'htm'],\n guidelines: [\n 'Flag only markup that is broken or functionally inaccessible; do not report SEO or style preferences.',\n ],\n },\n {\n language: 'JSON/Config',\n persona: 'a DevOps engineer',\n extensions: ['json', 'jsonc', 'yaml', 'yml', 'toml'],\n guidelines: [\n 'Flag invalid syntax/schema or hardcoded secrets; do not report naming-convention preferences.',\n ],\n },\n];\n\nexport function getLanguageForFile(path: string): LanguageGuideline | undefined {\n const ext = path.split('.').pop()?.toLowerCase();\n if (!ext) return undefined;\n\n const matches = LANGUAGE_GUIDELINES.filter((g) => g.extensions.includes(ext));\n \n if (matches.length === 0) return undefined;\n\n if (matches.length > 1) {\n return matches.reduce((best, candidate) =>\n candidate.extensions.length < best.extensions.length ? candidate : best,\n );\n }\n\n return matches[0];\n}\n\n// Matched by filename convention, not extension; kept narrow to avoid false positives.\nexport function isChangelogPath(path: string): boolean {\n const name = path.split('/').pop()?.toLowerCase() ?? '';\n const stem = name.replace(/\\.(md|mdx|markdown|rst|txt)$/, '');\n return stem === 'changelog' || stem === 'changes' || stem === 'history' || stem === 'news'\n || stem === 'release-notes' || stem === 'release_notes' || stem === 'releasenotes';\n}\n"],"mappings":";AAOA,IAAM,sBAA2C;AAAA,EAC/C;AAAA,IACE,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY,CAAC,MAAM,OAAO,MAAM,OAAO,OAAO,KAAK;AAAA,IACnD,YAAY;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY,CAAC,IAAI;AAAA,IACjB,YAAY;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY,CAAC,OAAO,QAAQ,QAAQ,MAAM;AAAA,IAC1C,YAAY;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY,CAAC,KAAK;AAAA,IAClB,YAAY;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY,CAAC,MAAM,KAAK;AAAA,IACxB,YAAY;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY,CAAC,QAAQ,KAAK;AAAA,IAC1B,YAAY;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY,CAAC,QAAQ,SAAS,QAAQ,OAAO,MAAM;AAAA,IACnD,YAAY;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,mBAAmB,MAA6C;AAC9E,QAAM,MAAM,KAAK,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY;AAC/C,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,UAAU,oBAAoB,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,GAAG,CAAC;AAE5E,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,MAAI,QAAQ,SAAS,GAAG;AACtB,WAAO,QAAQ;AAAA,MAAO,CAAC,MAAM,cAC3B,UAAU,WAAW,SAAS,KAAK,WAAW,SAAS,YAAY;AAAA,IACrE;AAAA,EACF;AAEA,SAAO,QAAQ,CAAC;AAClB;AAGO,SAAS,gBAAgB,MAAuB;AACrD,QAAM,OAAO,KAAK,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY,KAAK;AACrD,QAAM,OAAO,KAAK,QAAQ,gCAAgC,EAAE;AAC5D,SAAO,SAAS,eAAe,SAAS,aAAa,SAAS,aAAa,SAAS,UAC/E,SAAS,mBAAmB,SAAS,mBAAmB,SAAS;AACxE;","names":[]}
@@ -64,4 +64,4 @@ export {
64
64
  InMemoryOrchestrator,
65
65
  InMemorySessionStore
66
66
  };
67
- //# sourceMappingURL=chunk-JC74XH7Q.js.map
67
+ //# sourceMappingURL=chunk-GDX2ZEBA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ports/in-memory.ts"],"sourcesContent":["import type { KeyValueStore } from './kv';\nimport type { QueueProducer } from './queue';\nimport type { JobOrchestrator } from './orchestrator';\nimport type { SessionStore, DashboardSessionUser } from './session-store';\nimport type { ReviewJobMessage } from '@codraoss/schema';\n\nexport class InMemoryKV implements KeyValueStore {\n private store = new Map<string, { value: string; expiresAt?: number }>();\n\n async put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void> {\n const expiresAt = options?.expirationTtl ? Date.now() + options.expirationTtl * 1000 : undefined;\n this.store.set(key, { value, expiresAt });\n }\n\n async get(key: string, type?: 'json' | 'text'): Promise<any> {\n const entry = this.store.get(key);\n if (!entry) return null;\n if (entry.expiresAt && Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return null;\n }\n if (type === 'json') {\n try { return JSON.parse(entry.value); } catch { return null; }\n }\n return entry.value;\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n}\n\nexport class InMemoryQueue<T> implements QueueProducer<T> {\n public messages: Array<{ message: T; delaySeconds?: number }> = [];\n\n async send(message: T, options?: { delaySeconds?: number }): Promise<void> {\n this.messages.push({ message, delaySeconds: options?.delaySeconds });\n }\n}\n\nexport class InMemoryOrchestrator implements JobOrchestrator {\n public jobs: Map<string, ReviewJobMessage> = new Map();\n\n async startReviewJob(id: string, params: ReviewJobMessage): Promise<void> {\n this.jobs.set(id, params);\n }\n}\n\nexport class InMemorySessionStore implements SessionStore {\n private kv = new InMemoryKV();\n\n async createSession(session: DashboardSessionUser): Promise<string> {\n const token = Math.random().toString(36).substring(2);\n await this.kv.put(`session:${token}`, JSON.stringify(session), { expirationTtl: 60 * 60 * 24 * 7 });\n return token;\n }\n\n async readSession(token: string): Promise<DashboardSessionUser | null> {\n return this.kv.get(`session:${token}`, 'json');\n }\n\n async destroySession(token: string): Promise<void> {\n await this.kv.delete(`session:${token}`);\n }\n\n async renewSession(token: string): Promise<void> {\n const session = await this.readSession(token);\n if (session) {\n await this.kv.put(`session:${token}`, JSON.stringify(session), { expirationTtl: 60 * 60 * 24 * 7 });\n }\n }\n}\n"],"mappings":";AAMO,IAAM,aAAN,MAA0C;AAAA,EACvC,QAAQ,oBAAI,IAAmD;AAAA,EAEvE,MAAM,IAAI,KAAa,OAAe,SAAqD;AACzF,UAAM,YAAY,SAAS,gBAAgB,KAAK,IAAI,IAAI,QAAQ,gBAAgB,MAAO;AACvF,SAAK,MAAM,IAAI,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,EAC1C;AAAA,EAEA,MAAM,IAAI,KAAa,MAAsC;AAC3D,UAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;AAChC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,MAAM,aAAa,KAAK,IAAI,IAAI,MAAM,WAAW;AACnD,WAAK,MAAM,OAAO,GAAG;AACrB,aAAO;AAAA,IACT;AACA,QAAI,SAAS,QAAQ;AACnB,UAAI;AAAE,eAAO,KAAK,MAAM,MAAM,KAAK;AAAA,MAAG,QAAQ;AAAE,eAAO;AAAA,MAAM;AAAA,IAC/D;AACA,WAAO,MAAM;AAAA,EACf;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AACF;AAEO,IAAM,gBAAN,MAAmD;AAAA,EACjD,WAAyD,CAAC;AAAA,EAEjE,MAAM,KAAK,SAAY,SAAoD;AACzE,SAAK,SAAS,KAAK,EAAE,SAAS,cAAc,SAAS,aAAa,CAAC;AAAA,EACrE;AACF;AAEO,IAAM,uBAAN,MAAsD;AAAA,EACpD,OAAsC,oBAAI,IAAI;AAAA,EAErD,MAAM,eAAe,IAAY,QAAyC;AACxE,SAAK,KAAK,IAAI,IAAI,MAAM;AAAA,EAC1B;AACF;AAEO,IAAM,uBAAN,MAAmD;AAAA,EAChD,KAAK,IAAI,WAAW;AAAA,EAE5B,MAAM,cAAc,SAAgD;AAClE,UAAM,QAAQ,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,CAAC;AACpD,UAAM,KAAK,GAAG,IAAI,WAAW,KAAK,IAAI,KAAK,UAAU,OAAO,GAAG,EAAE,eAAe,KAAK,KAAK,KAAK,EAAE,CAAC;AAClG,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,OAAqD;AACrE,WAAO,KAAK,GAAG,IAAI,WAAW,KAAK,IAAI,MAAM;AAAA,EAC/C;AAAA,EAEA,MAAM,eAAe,OAA8B;AACjD,UAAM,KAAK,GAAG,OAAO,WAAW,KAAK,EAAE;AAAA,EACzC;AAAA,EAEA,MAAM,aAAa,OAA8B;AAC/C,UAAM,UAAU,MAAM,KAAK,YAAY,KAAK;AAC5C,QAAI,SAAS;AACX,YAAM,KAAK,GAAG,IAAI,WAAW,KAAK,IAAI,KAAK,UAAU,OAAO,GAAG,EAAE,eAAe,KAAK,KAAK,KAAK,EAAE,CAAC;AAAA,IACpG;AAAA,EACF;AACF;","names":[]}
@@ -1,3 +1,7 @@
1
+ import {
2
+ MAX_TOTAL_DIFF_CHARS
3
+ } from "./chunk-W3RY755V.js";
4
+
1
5
  // src/diff/index.ts
2
6
  import picomatch from "picomatch";
3
7
 
@@ -306,7 +310,7 @@ function buildUnifiedDiffFromFiles(files) {
306
310
  return out.length > 0 ? `${out.join("\n")}
307
311
  ` : "";
308
312
  }
309
- function filterReviewableFiles(files, config, maxFiles) {
313
+ function filterReviewableFiles(files, config, maxFiles, maxTotalDiffChars = MAX_TOTAL_DIFF_CHARS) {
310
314
  const customMatchers = config.skip_files.map((pattern) => picomatch(pattern, { dot: true }));
311
315
  const reviewable = [];
312
316
  for (const file of files) {
@@ -316,9 +320,21 @@ function filterReviewableFiles(files, config, maxFiles) {
316
320
  reviewable.push(file);
317
321
  }
318
322
  reviewable.sort((left, right) => Number(left.isNew) - Number(right.isNew) || left.path.localeCompare(right.path));
323
+ const withinFileLimit = reviewable.slice(0, maxFiles);
324
+ const kept = [];
325
+ let totalChars = 0;
326
+ for (const file of withinFileLimit) {
327
+ const fileChars = file.hunks.reduce(
328
+ (sum, hunk) => sum + hunk.lines.reduce((lineSum, line) => lineSum + line.content.length + 1, 0),
329
+ 0
330
+ );
331
+ if (kept.length > 0 && totalChars + fileChars > maxTotalDiffChars) break;
332
+ kept.push(file);
333
+ totalChars += fileChars;
334
+ }
319
335
  return {
320
- files: reviewable.slice(0, maxFiles),
321
- skipped: Math.max(0, reviewable.length - maxFiles)
336
+ files: kept,
337
+ skipped: Math.max(0, reviewable.length - kept.length)
322
338
  };
323
339
  }
324
340
 
@@ -334,4 +350,4 @@ export {
334
350
  buildUnifiedDiffFromFiles,
335
351
  filterReviewableFiles
336
352
  };
337
- //# sourceMappingURL=chunk-OIQRTDYR.js.map
353
+ //# sourceMappingURL=chunk-I5S3NBFL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/diff/index.ts","../src/diff/position.ts"],"sourcesContent":["import picomatch from 'picomatch';\nimport { MAX_TOTAL_DIFF_CHARS } from '../constants';\nimport type { RepoConfig } from '@codraoss/schema';\nimport {\n type DiffLineKind,\n type DiffLine,\n type DiffHunk,\n type FileDiff,\n getValidNewLines,\n getValidPositions,\n findPositionForLine,\n truncateFileDiff,\n chunkFileDiff,\n} from './position';\n\nexport {\n type DiffLineKind,\n type DiffLine,\n type DiffHunk,\n type FileDiff,\n getValidNewLines,\n getValidPositions,\n findPositionForLine,\n truncateFileDiff,\n chunkFileDiff,\n};\n\nconst defaultSkipMatchers = ['**/*.lock', '**/package-lock.json', '**/pnpm-lock.yaml', '**/yarn.lock', '**/*.min.js'].map((pattern) =>\n picomatch(pattern, { dot: true }),\n);\n\nexport function isReviewableFile(path: string, customMatchers: ReturnType<typeof picomatch>[]) {\n if (defaultSkipMatchers.some((matcher) => matcher(path))) return false;\n if (customMatchers.some((matcher) => matcher(path))) return false;\n return true;\n}\n\nexport function parseDiffHeaderPath(line: string) {\n const rest = line.slice('diff --git '.length);\n\n if (rest.startsWith('a/')) {\n const n = (rest.length - 5) / 2;\n if (Number.isInteger(n) && n > 0 && rest[2 + n] === ' ' && rest.startsWith('b/', 3 + n)) {\n const a = rest.slice(2, 2 + n);\n if (a === rest.slice(5 + n)) return a;\n }\n }\n\n const bStart = rest.indexOf(' b/', rest.startsWith('a/') ? 2 : 0);\n const bPath = bStart === -1 ? rest.slice(rest.lastIndexOf(' ') + 1) : rest.slice(bStart + 3);\n return bPath.startsWith('b/') ? bPath.slice(2) : bPath;\n}\n\nfunction parseHunkHeader(line: string): { oldLine: number; newLine: number } | null {\n const match = /^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/.exec(line);\n if (!match) {\n return null;\n }\n\n return {\n oldLine: Number.parseInt(match[1], 10),\n newLine: Number.parseInt(match[2], 10),\n };\n}\n\nfunction classifyDiffLine(prefix: ' ' | '+' | '-', content: string, oldLine: number, newLine: number, position: number): DiffLine {\n if (prefix === ' ') {\n return { kind: 'context', content, oldLineNumber: oldLine, newLineNumber: newLine, position };\n }\n\n if (prefix === '+') {\n return { kind: 'add', content, newLineNumber: newLine, position };\n }\n\n return { kind: 'del', content, oldLineNumber: oldLine, position };\n}\n\nfunction finishFile(files: FileDiff[], currentFile: FileDiff | null) {\n if (currentFile) {\n files.push(currentFile);\n }\n}\n\nexport function parseUnifiedDiff(rawDiff: string, reviewConfig?: RepoConfig['review']): FileDiff[] {\n const files: FileDiff[] = [];\n const customMatchers = reviewConfig?.skip_files?.map((pattern) => picomatch(pattern, { dot: true })) ?? [];\n\n let currentFile: FileDiff | null = null;\n let currentHunk: DiffHunk | null = null;\n let oldLine = 0;\n let newLine = 0;\n let position = 0;\n let isIgnored = false;\n\n const pushCurrentFile = () => {\n finishFile(files, currentFile);\n currentFile = null;\n currentHunk = null;\n oldLine = 0;\n newLine = 0;\n position = 0;\n isIgnored = false;\n };\n\n let startIndex = 0;\n const length = rawDiff.length;\n\n while (startIndex < length) {\n let endIndex = rawDiff.indexOf('\\n', startIndex);\n if (endIndex === -1) {\n endIndex = length;\n }\n\n let line = rawDiff.substring(startIndex, endIndex);\n if (line.charCodeAt(line.length - 1) === 13) {\n line = line.slice(0, -1);\n }\n\n startIndex = endIndex + 1;\n\n if (line.startsWith('diff --git ')) {\n pushCurrentFile();\n const path = parseDiffHeaderPath(line);\n\n currentFile = {\n path,\n previousPath: null,\n isNew: false,\n isDeleted: false,\n isBinary: false,\n lineCount: 0,\n hunks: [],\n };\n\n if (reviewConfig) {\n isIgnored = !isReviewableFile(path, customMatchers);\n }\n continue;\n }\n\n if (!currentFile) {\n continue;\n }\n\n if (line.startsWith('rename from ')) {\n currentFile.previousPath = line.slice(12);\n continue;\n }\n\n if (line.startsWith('rename to ')) {\n const nextPath = line.slice(10);\n currentFile.path = nextPath.startsWith('b/') ? nextPath.slice(2) : nextPath;\n if (reviewConfig) {\n isIgnored = !isReviewableFile(currentFile.path, customMatchers);\n }\n continue;\n }\n\n if (line.startsWith('new file mode ')) {\n currentFile.isNew = true;\n continue;\n }\n\n if (line.startsWith('deleted file mode ')) {\n currentFile.isDeleted = true;\n isIgnored = true;\n continue;\n }\n\n if (line.startsWith('Binary files ') || line.startsWith('GIT binary patch')) {\n currentFile.isBinary = true;\n isIgnored = true;\n continue;\n }\n\n if (line.startsWith('+++ ')) {\n const nextPath = line.slice(4);\n currentFile.path = nextPath.startsWith('b/') ? nextPath.slice(2) : nextPath;\n if (reviewConfig) {\n isIgnored = !isReviewableFile(currentFile.path, customMatchers);\n }\n continue;\n }\n\n if (isIgnored) {\n continue;\n }\n\n if (line.startsWith('--- ')) {\n continue;\n }\n\n if (line.startsWith('@@ ')) {\n const header = parseHunkHeader(line);\n if (!header) {\n continue;\n }\n\n oldLine = header.oldLine;\n newLine = header.newLine;\n currentHunk = { header: line, lines: [] };\n currentFile.hunks.push(currentHunk);\n continue;\n }\n\n if (!currentHunk) {\n continue;\n }\n\n const prefix = line[0];\n if (prefix !== ' ' && prefix !== '+' && prefix !== '-') {\n continue;\n }\n\n position += 1;\n const diffLine = classifyDiffLine(prefix, line.slice(1), oldLine, newLine, position);\n currentHunk.lines.push(diffLine);\n currentFile.lineCount += 1;\n\n if (diffLine.kind !== 'del') newLine += 1;\n if (diffLine.kind !== 'add') oldLine += 1;\n }\n\n pushCurrentFile();\n\n return files.filter((file) => file.path);\n}\n\n/** Shape returned by forge compare/files endpoints. Maps to GitHub's pulls/files JSON. */\nexport type DiffFileEntry = {\n filename: string;\n previous_filename?: string | null;\n status?: string;\n patch?: string | null;\n};\n\nexport function buildUnifiedDiffFromFiles(files: DiffFileEntry[]): string {\n const out: string[] = [];\n\n for (const file of files) {\n const newPath = file.filename;\n const oldPath = file.previous_filename || file.filename;\n const isAdded = file.status === 'added';\n const isRemoved = file.status === 'removed';\n\n out.push(`diff --git a/${oldPath} b/${newPath}`);\n if (isAdded) out.push('new file mode 100644');\n if (isRemoved) out.push('deleted file mode 100644');\n if (file.previous_filename && file.previous_filename !== newPath) {\n out.push(`rename from ${file.previous_filename}`);\n out.push(`rename to ${newPath}`);\n }\n\n if (!file.patch) {\n out.push(`Binary files a/${oldPath} and b/${newPath} differ`);\n continue;\n }\n\n out.push(isAdded ? '--- /dev/null' : `--- a/${oldPath}`);\n out.push(isRemoved ? '+++ /dev/null' : `+++ b/${newPath}`);\n out.push(file.patch);\n }\n\n return out.length > 0 ? `${out.join('\\n')}\\n` : '';\n}\n\nexport function filterReviewableFiles(\n files: FileDiff[],\n config: RepoConfig['review'],\n maxFiles: number,\n // Overridable for tests; production always uses the constant.\n maxTotalDiffChars: number = MAX_TOTAL_DIFF_CHARS,\n): { files: FileDiff[]; skipped: number } {\n const customMatchers = config.skip_files.map((pattern) => picomatch(pattern, { dot: true }));\n\n const reviewable: FileDiff[] = [];\n for (const file of files) {\n if (file.isDeleted || file.isBinary) continue;\n if (defaultSkipMatchers.some((matcher) => matcher(file.path))) continue;\n if (customMatchers.some((matcher) => matcher(file.path))) continue;\n reviewable.push(file);\n }\n reviewable.sort((left, right) => Number(left.isNew) - Number(right.isNew) || left.path.localeCompare(right.path));\n\n const withinFileLimit = reviewable.slice(0, maxFiles);\n\n // Job-level input ceiling. Files are kept or dropped whole; half a file's hunks would lie.\n const kept: FileDiff[] = [];\n let totalChars = 0;\n for (const file of withinFileLimit) {\n const fileChars = file.hunks.reduce(\n (sum, hunk) => sum + hunk.lines.reduce((lineSum, line) => lineSum + line.content.length + 1, 0),\n 0,\n );\n if (kept.length > 0 && totalChars + fileChars > maxTotalDiffChars) break;\n kept.push(file);\n totalChars += fileChars;\n }\n\n return {\n files: kept,\n skipped: Math.max(0, reviewable.length - kept.length),\n };\n}\n","export type DiffLineKind = 'context' | 'add' | 'del';\n\nexport type DiffLine = {\n kind: DiffLineKind;\n content: string;\n oldLineNumber?: number;\n newLineNumber?: number;\n position: number;\n};\n\nexport type DiffHunk = {\n header: string;\n lines: DiffLine[];\n};\n\nexport type FileDiff = {\n path: string;\n previousPath: string | null;\n isNew: boolean;\n isDeleted: boolean;\n isBinary: boolean;\n lineCount: number;\n hunks: DiffHunk[];\n isTruncated?: boolean;\n originalLineCount?: number;\n};\n\nexport function getValidNewLines(file: FileDiff) {\n const newLines = new Set<number>();\n for (const hunk of file.hunks) {\n for (const line of hunk.lines) {\n if (line.kind !== 'del' && line.newLineNumber !== undefined) {\n newLines.add(line.newLineNumber);\n }\n }\n }\n\n return newLines;\n}\n\nexport function getValidPositions(file: FileDiff) {\n const positions = new Set<number>();\n for (const hunk of file.hunks) {\n for (const line of hunk.lines) {\n if (line.kind !== 'del') {\n positions.add(line.position);\n }\n }\n }\n\n return positions;\n}\n\nexport function findPositionForLine(file: FileDiff, lineNumber: number) {\n for (const hunk of file.hunks) {\n for (const line of hunk.lines) {\n if (line.newLineNumber === lineNumber && line.kind !== 'del') {\n return line.position;\n }\n }\n }\n\n return undefined;\n}\n\n\nexport function truncateFileDiff(file: FileDiff, maxLines: number): FileDiff {\n if (file.lineCount <= maxLines) {\n return file;\n }\n\n let currentLines = 0;\n const keptHunks: DiffHunk[] = [];\n\n for (const hunk of file.hunks) {\n const remainingLines = maxLines - currentLines;\n if (remainingLines <= 0) {\n break;\n }\n\n if (hunk.lines.length <= remainingLines) {\n keptHunks.push(hunk);\n currentLines += hunk.lines.length;\n continue;\n }\n\n keptHunks.push({\n ...hunk,\n lines: hunk.lines.slice(0, remainingLines),\n });\n currentLines += remainingLines;\n break;\n }\n\n return {\n ...file,\n hunks: keptHunks,\n lineCount: currentLines,\n isTruncated: true,\n originalLineCount: file.lineCount,\n };\n}\n\nexport function chunkFileDiff(file: FileDiff, maxLinesPerChunk: number): FileDiff[] {\n if (file.lineCount <= maxLinesPerChunk) {\n return [file];\n }\n\n const chunks: FileDiff[] = [];\n let currentHunks: DiffHunk[] = [];\n let currentLines = 0;\n\n for (const hunk of file.hunks) {\n let linesRemainingInHunk = hunk.lines;\n\n while (linesRemainingInHunk.length > 0) {\n const roomInChunk = maxLinesPerChunk - currentLines;\n\n if (roomInChunk <= 0) {\n chunks.push({\n ...file,\n hunks: currentHunks,\n lineCount: currentLines,\n isTruncated: true,\n originalLineCount: file.lineCount,\n });\n currentHunks = [];\n currentLines = 0;\n continue;\n }\n\n if (linesRemainingInHunk.length <= roomInChunk) {\n currentHunks.push({\n ...hunk,\n lines: linesRemainingInHunk,\n });\n currentLines += linesRemainingInHunk.length;\n linesRemainingInHunk = [];\n } else {\n currentHunks.push({\n ...hunk,\n lines: linesRemainingInHunk.slice(0, roomInChunk),\n });\n currentLines += roomInChunk;\n linesRemainingInHunk = linesRemainingInHunk.slice(roomInChunk);\n }\n }\n }\n\n if (currentHunks.length > 0) {\n chunks.push({\n ...file,\n hunks: currentHunks,\n lineCount: currentLines,\n isTruncated: true,\n originalLineCount: file.lineCount,\n });\n }\n\n return chunks;\n}\n"],"mappings":"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","names":[]}