@mrkaran/hodor 0.6.1 → 0.6.2-rc.3

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.
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  HODOR_REVIEW_MARKER,
3
3
  bulkPublishGitlabDraftNotes,
4
- cleanupHodorComments,
5
4
  createGitlabDraftNote,
6
5
  exec,
7
6
  execJson,
@@ -488,6 +487,7 @@ var REVIEW_FINDING_SCHEMA = Type.Object(
488
487
  body: Type.String({ minLength: 1 }),
489
488
  priority: Type.Integer({ minimum: 0, maximum: 3 }),
490
489
  code_location: REVIEW_LOCATION_SCHEMA,
490
+ existing_code: Type.Optional(Type.String({ minLength: 1 })),
491
491
  suggestion: Type.Optional(Type.String({ minLength: 1 }))
492
492
  },
493
493
  { additionalProperties: false }
@@ -1146,6 +1146,191 @@ async function cleanupWorkspace(workspace) {
1146
1146
  }
1147
1147
  }
1148
1148
 
1149
+ // src/resolve-location.ts
1150
+ import { readFileSync as readFileSync2 } from "fs";
1151
+ import { resolve as resolve2, sep } from "path";
1152
+ var MAX_RESOLVE_BYTES = 2 * 1024 * 1024;
1153
+ function normalizeLine(line) {
1154
+ let s = line.replace(/\r$/, "").trim();
1155
+ if (s.startsWith("+") || s.startsWith("-")) {
1156
+ s = s.slice(1).trim();
1157
+ }
1158
+ return s;
1159
+ }
1160
+ function normalizeSnippet(code) {
1161
+ return code.split("\n").map(normalizeLine).filter((l) => l.length > 0);
1162
+ }
1163
+ function indexFile(content) {
1164
+ const result = [];
1165
+ const lines = content.split("\n");
1166
+ for (let i = 0; i < lines.length; i++) {
1167
+ const normalized = normalizeLine(lines[i]);
1168
+ if (normalized.length > 0) {
1169
+ result.push({ lineNum: i + 1, content: normalized });
1170
+ }
1171
+ }
1172
+ return result;
1173
+ }
1174
+ function isWithinWorkspace(root, filePath) {
1175
+ const target = resolve2(filePath);
1176
+ return target === root || target.startsWith(root + sep);
1177
+ }
1178
+ function findMatches(fileLines, target) {
1179
+ const matches = [];
1180
+ if (target.length === 0 || fileLines.length < target.length) return matches;
1181
+ for (let i = 0; i <= fileLines.length - target.length; i++) {
1182
+ let ok = true;
1183
+ for (let j = 0; j < target.length; j++) {
1184
+ if (fileLines[i + j].content !== target[j]) {
1185
+ ok = false;
1186
+ break;
1187
+ }
1188
+ }
1189
+ if (ok) {
1190
+ matches.push({ start: fileLines[i].lineNum, end: fileLines[i + target.length - 1].lineNum });
1191
+ }
1192
+ }
1193
+ return matches;
1194
+ }
1195
+ function resolveLineRange(input) {
1196
+ const { existingCode, fileContent, modelRange, changedLines } = input;
1197
+ const keep = (status2) => ({
1198
+ start: modelRange.start,
1199
+ end: modelRange.end,
1200
+ status: status2
1201
+ });
1202
+ const target = existingCode ? normalizeSnippet(existingCode) : [];
1203
+ if (target.length === 0) return keep("no-snippet");
1204
+ const matches = findMatches(indexFile(fileContent), target);
1205
+ if (matches.length === 0) return keep("unmatched");
1206
+ let chosen;
1207
+ if (matches.length === 1) {
1208
+ chosen = matches[0];
1209
+ } else {
1210
+ const overlapping = changedLines && changedLines.size > 0 ? matches.filter((m) => {
1211
+ for (let l = m.start; l <= m.end; l++) {
1212
+ if (changedLines.has(l)) return true;
1213
+ }
1214
+ return false;
1215
+ }) : [];
1216
+ const pool = overlapping.length > 0 ? overlapping : matches;
1217
+ chosen = pool.reduce(
1218
+ (best, m) => Math.abs(m.start - modelRange.start) < Math.abs(best.start - modelRange.start) ? m : best
1219
+ );
1220
+ }
1221
+ const status = chosen.start === modelRange.start && chosen.end === modelRange.end ? "confirmed" : "corrected";
1222
+ return { start: chosen.start, end: chosen.end, status };
1223
+ }
1224
+ function parseChangedLines(diffText) {
1225
+ const byFile = /* @__PURE__ */ new Map();
1226
+ if (!diffText) return byFile;
1227
+ let current = null;
1228
+ let newLine = 0;
1229
+ const hunkRe = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
1230
+ for (const line of diffText.split("\n")) {
1231
+ if (line.startsWith("+++ ")) {
1232
+ let path = line.slice(4).trim();
1233
+ if (path.startsWith("b/")) path = path.slice(2);
1234
+ if (path === "/dev/null") {
1235
+ current = null;
1236
+ } else {
1237
+ current = byFile.get(path) ?? /* @__PURE__ */ new Set();
1238
+ byFile.set(path, current);
1239
+ }
1240
+ continue;
1241
+ }
1242
+ const m = line.match(hunkRe);
1243
+ if (m) {
1244
+ newLine = parseInt(m[1], 10);
1245
+ continue;
1246
+ }
1247
+ if (!current) continue;
1248
+ if (line.startsWith("diff --git ") || line.startsWith("--- ")) continue;
1249
+ if (line.startsWith("+")) {
1250
+ current.add(newLine);
1251
+ newLine++;
1252
+ } else if (line.startsWith("-")) {
1253
+ } else if (line.startsWith("\\")) {
1254
+ } else {
1255
+ current.add(newLine);
1256
+ newLine++;
1257
+ }
1258
+ }
1259
+ return byFile;
1260
+ }
1261
+ function resolveReviewLocations(review, opts) {
1262
+ const stats = { total: 0, noSnippet: 0, confirmed: 0, corrected: 0, unmatched: 0 };
1263
+ if (review.findings.length === 0) return { review, stats };
1264
+ const changedByFile = opts.diffText ? parseChangedLines(opts.diffText) : /* @__PURE__ */ new Map();
1265
+ const fileCache = /* @__PURE__ */ new Map();
1266
+ const workspaceRoot = opts.workspacePath ? resolve2(opts.workspacePath) : null;
1267
+ const readFile = (path) => {
1268
+ if (fileCache.has(path)) return fileCache.get(path) ?? null;
1269
+ let content = null;
1270
+ try {
1271
+ const buf = readFileSync2(path);
1272
+ if (buf.byteLength <= MAX_RESOLVE_BYTES) content = buf.toString("utf-8");
1273
+ } catch {
1274
+ content = null;
1275
+ }
1276
+ fileCache.set(path, content);
1277
+ return content;
1278
+ };
1279
+ const findings = review.findings.map((finding) => {
1280
+ stats.total++;
1281
+ const { existing_code: existingCode, code_location: loc } = finding;
1282
+ if (!existingCode) {
1283
+ stats.noSnippet++;
1284
+ return finding;
1285
+ }
1286
+ if (workspaceRoot && !isWithinWorkspace(workspaceRoot, loc.absolute_file_path)) {
1287
+ stats.unmatched++;
1288
+ logger.warn(
1289
+ `Location resolution: ${loc.absolute_file_path} is outside the workspace for "${finding.title}"; keeping model range`
1290
+ );
1291
+ return finding;
1292
+ }
1293
+ const fileContent = readFile(loc.absolute_file_path);
1294
+ if (fileContent === null) {
1295
+ stats.unmatched++;
1296
+ logger.warn(`Location resolution: could not read ${loc.absolute_file_path} for "${finding.title}"`);
1297
+ return finding;
1298
+ }
1299
+ let changedLines;
1300
+ for (const [relPath, lines] of changedByFile) {
1301
+ if (loc.absolute_file_path.endsWith(`/${relPath}`) || loc.absolute_file_path === relPath) {
1302
+ changedLines = lines;
1303
+ break;
1304
+ }
1305
+ }
1306
+ const result = resolveLineRange({
1307
+ existingCode,
1308
+ fileContent,
1309
+ modelRange: loc.line_range,
1310
+ changedLines
1311
+ });
1312
+ if (result.status === "confirmed") stats.confirmed++;
1313
+ else if (result.status === "corrected") stats.corrected++;
1314
+ else stats.unmatched++;
1315
+ if (result.status === "corrected") {
1316
+ logger.info(
1317
+ `Location resolution: corrected "${finding.title}" ${loc.line_range.start}-${loc.line_range.end} -> ${result.start}-${result.end}`
1318
+ );
1319
+ return {
1320
+ ...finding,
1321
+ code_location: { ...loc, line_range: { start: result.start, end: result.end } }
1322
+ };
1323
+ }
1324
+ if (result.status === "unmatched") {
1325
+ logger.warn(
1326
+ `Location resolution: snippet not found for "${finding.title}" in ${loc.absolute_file_path}; keeping model range ${loc.line_range.start}-${loc.line_range.end}`
1327
+ );
1328
+ }
1329
+ return finding;
1330
+ });
1331
+ return { review: { ...review, findings }, stats };
1332
+ }
1333
+
1149
1334
  // src/system-prompt.ts
1150
1335
  var REVIEW_SYSTEM_PROMPT = `You are a code review agent. You analyze pull request diffs to find production bugs.
1151
1336
 
@@ -1429,17 +1614,6 @@ async function postReviewStructured(opts) {
1429
1614
  } catch (err) {
1430
1615
  logger.warn(`Failed to resolve old discussions: ${err instanceof Error ? err.message : err}`);
1431
1616
  }
1432
- try {
1433
- const deleted = await cleanupHodorComments(
1434
- parsed.owner,
1435
- parsed.repo,
1436
- parsed.prNumber,
1437
- parsed.host
1438
- );
1439
- if (deleted > 0) logger.info(`Cleaned up ${deleted} old Hodor comment(s)`);
1440
- } catch (err) {
1441
- logger.warn(`Failed to cleanup old comments: ${err instanceof Error ? err.message : err}`);
1442
- }
1443
1617
  let diffRefs = null;
1444
1618
  try {
1445
1619
  diffRefs = await getGitlabMrDiffRefs(
@@ -2089,10 +2263,19 @@ async function reviewPr(opts) {
2089
2263
  `Agent did not call submit_review after ${SUBMIT_REVIEW_RECOVERY_ATTEMPTS} recovery attempt(s): ${diagnostic}`
2090
2264
  );
2091
2265
  }
2092
- const review = submittedReview;
2266
+ const rawReview = submittedReview;
2093
2267
  if (submitReviewCalls > 1) {
2094
2268
  logger.warn(`Agent called submit_review ${submitReviewCalls} times; using the first valid submission`);
2095
2269
  }
2270
+ const { review, stats: locationStats } = resolveReviewLocations(rawReview, {
2271
+ workspacePath,
2272
+ diffText: embeddedDiff
2273
+ });
2274
+ if (locationStats.corrected > 0 || locationStats.unmatched > 0) {
2275
+ logger.info(
2276
+ `Location resolution: ${locationStats.corrected} corrected, ${locationStats.confirmed} confirmed, ${locationStats.unmatched} unmatched, ${locationStats.noSnippet} without snippet`
2277
+ );
2278
+ }
2096
2279
  logger.info(
2097
2280
  `Captured ${review.findings.length} finding(s), verdict: ${review.overall_correctness}`
2098
2281
  );
@@ -2164,4 +2347,4 @@ export {
2164
2347
  postReviewStructured,
2165
2348
  reviewPr
2166
2349
  };
2167
- //# sourceMappingURL=chunk-JSLG7EOX.js.map
2350
+ //# sourceMappingURL=chunk-MRMLGIXO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/prompt.ts","../src/model.ts","../src/metrics.ts","../src/review.ts","../src/agent.ts","../src/github.ts","../src/gitea.ts","../src/workspace.ts","../src/resolve-location.ts","../src/system-prompt.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { logger } from \"./utils/logger.js\";\nimport { summarizeGitlabNotes } from \"./gitlab.js\";\nimport type { MrMetadata, Platform } from \"./types.js\";\n\n// Resolve templates directory relative to this file (works in both src/ and dist/)\nfunction getTemplatesDir(): string {\n const currentDir = dirname(fileURLToPath(import.meta.url));\n return resolve(currentDir, \"..\", \"templates\");\n}\n\nexport function buildPrReviewPrompt(opts: {\n prUrl: string;\n platform: Platform;\n targetBranch?: string;\n diffBaseSha?: string | null;\n mrMetadata?: MrMetadata | null;\n customInstructions?: string | null;\n customPromptFile?: string | null;\n embeddedDiff?: string | null;\n previousReviewSha?: string | null;\n localMode?: boolean;\n}): string {\n const {\n prUrl,\n platform,\n targetBranch = \"main\",\n diffBaseSha,\n mrMetadata,\n customInstructions,\n customPromptFile,\n embeddedDiff,\n previousReviewSha,\n localMode = false,\n } = opts;\n\n // Step 1: Determine template (always tool submission; rendered to markdown post-hoc)\n let templateFile: string;\n if (customPromptFile) {\n templateFile = customPromptFile;\n logger.info(`Using custom prompt file: ${templateFile}`);\n } else {\n templateFile = resolve(getTemplatesDir(), \"tool-review.md\");\n logger.info(\"Using tool-based review template\");\n }\n\n // Step 2: Load template\n let templateText: string;\n try {\n templateText = readFileSync(templateFile, \"utf-8\");\n } catch (err) {\n throw new Error(`Failed to load prompt template from ${templateFile}: ${err}`);\n }\n\n // Validate ref inputs to prevent shell injection via branch/SHA names.\n // Block shell metacharacters while allowing valid git ref chars (@, +, ~, ^, etc.)\n const dangerousChars = /[;\\|`$&<>(){}\\n\\r\\0\\\\!]/;\n if (dangerousChars.test(targetBranch)) {\n throw new Error(`Invalid target branch name: ${targetBranch}`);\n }\n if (diffBaseSha && dangerousChars.test(diffBaseSha)) {\n throw new Error(`Invalid diff base SHA: ${diffBaseSha}`);\n }\n if (previousReviewSha && !/^[a-f0-9]{40}$/.test(previousReviewSha)) {\n throw new Error(`Invalid previous review SHA: ${previousReviewSha}`);\n }\n\n // Prepare platform-specific commands\n let prDiffCmd: string;\n let gitDiffCmd: string;\n\n // Incremental mode: three-dot diff from previous review SHA (excludes upstream changes)\n if (previousReviewSha) {\n prDiffCmd = `git --no-pager diff ${previousReviewSha}...HEAD --name-only`;\n gitDiffCmd = `git --no-pager diff ${previousReviewSha}...HEAD`;\n logger.info(`Incremental review: diffing from ${previousReviewSha.slice(0, 8)} to HEAD`);\n } else if (localMode) {\n // Plain two-arg diff includes uncommitted (staged + unstaged) changes\n prDiffCmd = `git --no-pager diff ${targetBranch} --name-only`;\n gitDiffCmd = `git --no-pager diff ${targetBranch}`;\n } else if (platform === \"github\" || platform === \"gitea\") {\n prDiffCmd = `git --no-pager diff origin/${targetBranch}...HEAD --name-only`;\n gitDiffCmd = `git --no-pager diff origin/${targetBranch}...HEAD`;\n } else {\n // gitlab\n if (diffBaseSha) {\n prDiffCmd = `git --no-pager diff ${diffBaseSha} HEAD --name-only`;\n gitDiffCmd = `git --no-pager diff ${diffBaseSha} HEAD`;\n logger.info(`Using GitLab CI_MERGE_REQUEST_DIFF_BASE_SHA: ${diffBaseSha.slice(0, 8)}`);\n } else {\n prDiffCmd = `git --no-pager diff origin/${targetBranch}...HEAD --name-only`;\n gitDiffCmd = `git --no-pager diff origin/${targetBranch}...HEAD`;\n }\n }\n\n // Diff explanation\n let diffExplanation: string;\n if (previousReviewSha) {\n diffExplanation =\n `**Incremental mode**: Showing only changes since the last hodor review ` +\n `(commit \\`${previousReviewSha.slice(0, 8)}\\`).`;\n } else if (diffBaseSha) {\n diffExplanation =\n `**GitLab CI Advantage**: This uses GitLab's pre-calculated merge base SHA ` +\n `(\\`CI_MERGE_REQUEST_DIFF_BASE_SHA\\`), which matches exactly what the GitLab UI shows. ` +\n `This is more reliable than three-dot syntax because it handles force pushes, rebases, ` +\n `and messy histories correctly.`;\n } else {\n diffExplanation =\n `**Three-dot syntax** shows ONLY changes introduced on the source branch, ` +\n `excluding changes already on \\`${targetBranch}\\`.`;\n }\n\n // Step 3: Build MR sections\n const { contextSection, notesSection, reminderSection } = buildMrSections(mrMetadata);\n\n // Step 3b: Build incremental review section\n let incrementalSection = \"\";\n if (previousReviewSha) {\n incrementalSection =\n \"## Incremental Review Mode\\n\\n\" +\n `This is a follow-up review. A previous hodor review was done at commit \\`${previousReviewSha.slice(0, 8)}\\`. ` +\n \"The diff below shows ONLY changes since that review. Your job is to review that delta, not the whole MR again.\\n\\n\" +\n \"Rules for incremental reviews:\\n\" +\n \"1. Only report bugs introduced or still affected by the new delta.\\n\" +\n \"2. Do not re-report issues that are already mentioned in existing notes unless the new delta changes the same code and the issue remains newly relevant.\\n\" +\n \"3. If the delta is small and self-contained, decide from the embedded diff and submit the review without broad repository exploration.\\n\" +\n \"4. For mechanical changes like route/path/string renames, verify the direct call sites or tests only when the diff itself leaves a concrete compatibility question.\\n\" +\n \"5. If the delta does not introduce a production bug, submit no findings.\\n\\n\";\n }\n\n // Step 3c: Build conditional sections based on whether diff is embedded\n let embeddedDiffSection: string;\n let diffFetchInstructions: string;\n let reviewProcessSection: string;\n let startInstruction: string;\n\n if (embeddedDiff) {\n embeddedDiffSection =\n \"## Full Diff (Pre-fetched)\\n\\n\" +\n \"The complete diff for this PR is provided below. Analyze it directly. \" +\n \"Use `read` or `grep` only if you need additional file context beyond what the diff shows.\\n\\n\" +\n \"````diff\\n\" + embeddedDiff + \"\\n````\\n\";\n\n diffFetchInstructions =\n \"## Review the Diff Above\\n\\n\" +\n \"### Critical Rules\\n\" +\n \"- ONLY review files that appear in the diff above\\n\" +\n \"- ONLY analyze actual code changes (+ and - lines in the diff)\\n\" +\n \"- NEVER review files not in the diff\\n\" +\n \"- NEVER flag \\\"files will be deleted when merging\\\" (outdated branch)\\n\" +\n \"- NEVER flag \\\"dependency version downgrade\\\" (branch not rebased)\\n\" +\n `- NEVER compare entire codebase to ${targetBranch} - DIFF ONLY\\n`;\n\n reviewProcessSection =\n \"## Review Process\\n\\n\" +\n \"1. Analyze the embedded diff above thoroughly\\n\" +\n \"2. Use `grep` to search for patterns when needed\\n\" +\n \"3. Use `read` only when surrounding context is essential\\n\" +\n \"4. Submit your review using `submit_review`\\n\";\n\n startInstruction = previousReviewSha\n ? \"Analyze only the incremental diff provided above. If it is self-contained, submit your review without extra tool calls.\"\n : \"Analyze the diff provided above, then submit your review using `submit_review`.\";\n } else {\n embeddedDiffSection = \"\";\n\n diffFetchInstructions =\n \"## Step 1: List Changed Files (MANDATORY FIRST STEP)\\n\\n\" +\n \"**Run this command FIRST to get the list of changed files:**\\n\" +\n \"```bash\\n\" + prDiffCmd + \"\\n```\\n\\n\" +\n \"This lists ONLY the filenames changed in this PR. **Do NOT dump the entire diff here** - \" +\n \"you'll inspect each file individually in Step 2. Only review files that appear in this output.\\n\\n\" +\n \"## Step 2: Review Changed Files Only\\n\\n\" +\n \"### Critical Rules\\n\" +\n \"- ONLY review files that appear in the diff from Step 1\\n\" +\n \"- ONLY analyze actual code changes (+ and - lines in the diff)\\n\" +\n \"- Use the most reliable diff command: `\" + gitDiffCmd + \"`\\n\" +\n \"- NEVER review files not in the diff\\n\" +\n \"- NEVER flag \\\"files will be deleted when merging\\\" (outdated branch)\\n\" +\n \"- NEVER flag \\\"dependency version downgrade\\\" (branch not rebased)\\n\" +\n `- NEVER compare entire codebase to ${targetBranch} - DIFF ONLY\\n\\n` +\n \"### Git Diff Command\\n\\n\" +\n \"**Most reliable command to see changes:**\\n\" +\n \"```bash\\n\" + gitDiffCmd + \"\\n```\\n\\n\" +\n diffExplanation;\n\n reviewProcessSection =\n \"## Review Process\\n\\n\" +\n \"**Efficient Sequential Workflow:**\\n\\n\" +\n `1. **List files first**: Run \\`${prDiffCmd}\\` to get the list of changed files (NOT full diff)\\n` +\n `2. **Per-file analysis**: For each file, run \\`${gitDiffCmd} -- path/to/file\\` to see its specific changes\\n` +\n \"3. **Batch pattern search**: Use `grep` across multiple files to find common bug patterns (null, undefined, TODO, FIXME, etc.)\\n\" +\n \"4. **Selective deep dive**: Only use `read` to read full file context when the diff alone is insufficient\\n\" +\n \"5. **Group related files**: Analyze related files together (e.g., implementation + tests, interfaces + implementations)\\n\" +\n \"6. **Avoid redundancy**: Don't re-read files unnecessarily; make decisions based on diff context\\n\";\n\n startInstruction =\n `Start by running \\`${prDiffCmd}\\` to list the changed files, then analyze each file individually using \\`${gitDiffCmd} -- path/to/file\\`.`;\n }\n\n // Step 4: Interpolate\n let prompt = templateText\n .replace(/\\{pr_url\\}/g, prUrl)\n .replace(/\\{pr_diff_cmd\\}/g, prDiffCmd)\n .replace(/\\{git_diff_cmd\\}/g, gitDiffCmd)\n .replace(/\\{target_branch\\}/g, targetBranch)\n .replace(/\\{diff_explanation\\}/g, diffExplanation)\n .replace(/\\{mr_context_section\\}/g, contextSection)\n .replace(/\\{mr_notes_section\\}/g, notesSection)\n .replace(/\\{mr_reminder_section\\}/g, reminderSection)\n .replace(/\\{incremental_section\\}/g, incrementalSection)\n .replace(/\\{embedded_diff_section\\}/g, embeddedDiffSection)\n .replace(/\\{diff_fetch_instructions\\}/g, diffFetchInstructions)\n .replace(/\\{review_process_section\\}/g, reviewProcessSection)\n .replace(/\\{start_instruction\\}/g, startInstruction);\n\n // Step 5: Append custom instructions\n if (customInstructions) {\n prompt += `\\n\\n## Additional Instructions\\n\\n${customInstructions}\\n`;\n logger.info(\"Appended custom instructions to prompt\");\n }\n\n return prompt;\n}\n\nexport function buildMrSections(mrMetadata?: MrMetadata | null): {\n contextSection: string;\n notesSection: string;\n reminderSection: string;\n} {\n if (!mrMetadata) {\n return { contextSection: \"\", notesSection: \"\", reminderSection: \"\" };\n }\n\n const contextLines: string[] = [];\n\n if (mrMetadata.title) {\n contextLines.push(`- Title: ${mrMetadata.title}`);\n }\n\n const author =\n mrMetadata.author?.username ?? mrMetadata.author?.name;\n if (author) {\n contextLines.push(`- Author: @${author}`);\n }\n\n if (mrMetadata.source_branch && mrMetadata.target_branch) {\n contextLines.push(\n `- Branches: ${mrMetadata.source_branch} → ${mrMetadata.target_branch}`,\n );\n }\n\n if (mrMetadata.changes_count) {\n contextLines.push(`- Files changed: ${mrMetadata.changes_count}`);\n }\n\n const pipelineStatus = mrMetadata.pipeline?.status;\n const pipelineUrl = mrMetadata.pipeline?.web_url;\n if (pipelineStatus) {\n const statusText = pipelineStatus.replace(/_/g, \" \");\n contextLines.push(\n pipelineUrl\n ? `- Pipeline: ${statusText} (${pipelineUrl})`\n : `- Pipeline: ${statusText}`,\n );\n }\n\n let labelNames = normalizeLabelNames(mrMetadata.label_details);\n if (labelNames.length === 0) {\n labelNames = normalizeLabelNames(mrMetadata.labels);\n }\n if (labelNames.length > 0) {\n contextLines.push(`- Labels: ${labelNames.join(\", \")}`);\n }\n\n const description = (mrMetadata.description ?? \"\").trim();\n let descriptionSection = \"\";\n if (description) {\n descriptionSection =\n \"**Author Description:**\\n\" + truncateBlock(description, 800);\n }\n\n let contextSection = \"\";\n if (contextLines.length > 0 || descriptionSection) {\n contextSection = \"## MR Context\\n\" + contextLines.join(\"\\n\");\n if (descriptionSection) {\n contextSection += \"\\n\\n\" + descriptionSection;\n }\n contextSection += \"\\n\";\n }\n\n let notesSection = \"\";\n const notesSummary = summarizeGitlabNotes(mrMetadata.Notes);\n if (notesSummary) {\n notesSection = `## Existing MR Notes\\n${notesSummary}\\n`;\n }\n\n let reminderSection = \"\";\n if (notesSummary) {\n reminderSection =\n \"## Review Note Deduplication\\n\\n\" +\n \"The discussions above may already cover some issues. Before reporting a finding:\\n\" +\n \"1. Check if it's already mentioned in existing notes\\n\" +\n \"2. Only report if your finding is materially different or more specific\\n\" +\n \"3. If an existing note is incorrect/outdated, explain why in your finding\\n\\n\" +\n \"Focus on discovering NEW issues not yet discussed.\\n\";\n }\n\n return { contextSection, notesSection, reminderSection };\n}\n\nfunction truncateBlock(text: string, limit: number): string {\n const trimmed = text.trim();\n if (trimmed.length <= limit) return trimmed;\n return trimmed.slice(0, limit - 1).trimEnd() + \"…\";\n}\n\nexport function normalizeLabelNames(\n rawLabels: unknown,\n): string[] {\n if (!rawLabels) return [];\n\n const names: string[] = [];\n\n function addLabel(value: unknown): void {\n let name = \"\";\n if (typeof value === \"string\") {\n name = value.trim();\n } else if (typeof value === \"object\" && value !== null) {\n const labelValue = (value as Record<string, unknown>).name;\n if (typeof labelValue === \"string\") {\n name = labelValue.trim();\n }\n } else if (value != null) {\n name = String(value).trim();\n }\n if (name) names.push(name);\n }\n\n if (Array.isArray(rawLabels)) {\n for (const label of rawLabels) addLabel(label);\n } else {\n addLabel(rawLabels);\n }\n\n return names;\n}\n","import { getEnvApiKey, getProviders } from \"@earendil-works/pi-ai\";\nimport type { ThinkingLevel } from \"@earendil-works/pi-ai\";\n\nexport interface ParsedModel {\n provider: string;\n modelId: string;\n}\n\nconst PROVIDER_ALIASES: Record<string, string> = {\n bedrock: \"amazon-bedrock\",\n};\n\n/**\n * Parse a model string like \"anthropic/claude-sonnet-4-5\" into { provider, modelId }.\n * Handles bare names like \"claude-sonnet-4-5\" or \"gpt-5\" via auto-detection.\n */\nexport function parseModelString(model: string): ParsedModel {\n const trimmed = model.trim();\n if (!trimmed) throw new Error(\"Model name must be provided\");\n\n const parts = trimmed.split(\"/\");\n\n // Explicit provider prefix. Hodor delegates provider/model support to pi-ai's\n // registry instead of maintaining its own curated allow-list. Keep `bedrock`\n // as a friendlier alias for pi-ai's `amazon-bedrock` provider name.\n if (parts.length >= 2) {\n const first = parts[0].toLowerCase();\n const provider = PROVIDER_ALIASES[first] ?? first;\n const knownProviders = new Set<string>(getProviders());\n\n if (provider === \"amazon-bedrock\") {\n // Strip optional \"converse/\" prefix from model ID for backwards compatibility.\n let modelId = parts.slice(1).join(\"/\");\n if (modelId.startsWith(\"converse/\")) {\n modelId = modelId.slice(\"converse/\".length);\n }\n return { provider, modelId };\n }\n\n if (knownProviders.has(provider)) {\n return { provider, modelId: parts.slice(1).join(\"/\") };\n }\n\n // OpenRouter adds new model slugs frequently. Allow it even if the installed\n // pi-ai registry ever lags; agent.ts has a conservative OpenRouter fallback.\n if (provider === \"openrouter\") {\n return { provider, modelId: parts.slice(1).join(\"/\") };\n }\n\n throw new Error(\n `Unsupported provider \"${first}\". Use a pi-ai provider prefix such as anthropic/, openai/, openrouter/, google/, mistral/, xai/, or bedrock/.`,\n );\n }\n\n // Auto-detect provider from bare model name\n const lower = trimmed.toLowerCase();\n if (lower.includes(\"claude\") || lower.includes(\"anthropic\")) {\n return { provider: \"anthropic\", modelId: trimmed };\n }\n if (\n lower.startsWith(\"gpt\") ||\n lower.startsWith(\"o1\") ||\n lower.startsWith(\"o3\") ||\n lower.startsWith(\"o4\") ||\n lower.includes(\"openai\")\n ) {\n return { provider: \"openai\", modelId: trimmed };\n }\n\n // Default to anthropic for unknown models\n return { provider: \"anthropic\", modelId: trimmed };\n}\n\n/**\n * Map reasoning effort strings to pi-ai thinking levels.\n * Returns undefined for no reasoning.\n */\nexport function mapReasoningEffort(\n effort: string | undefined,\n): ThinkingLevel | undefined {\n if (!effort) return undefined;\n switch (effort.toLowerCase()) {\n case \"minimal\":\n return \"minimal\";\n case \"low\":\n return \"low\";\n case \"medium\":\n return \"medium\";\n case \"high\":\n return \"high\";\n case \"xhigh\":\n return \"xhigh\";\n default:\n return undefined;\n }\n}\n\nfunction normalizeModelMatchValue(value: string): string {\n return value.toLowerCase().replace(/[\\s_.:/]+/g, \"-\");\n}\n\nexport function getDefaultReasoningEffortForModel(model: {\n id?: string;\n name?: string;\n}): ThinkingLevel | undefined {\n const values = [model.id, model.name].filter((value): value is string => Boolean(value));\n const isOpus47 = values\n .map(normalizeModelMatchValue)\n .some((value) => value.includes(\"opus-4-7\"));\n\n return isOpus47 ? \"xhigh\" : undefined;\n}\n\n/**\n * Get API key with provider-aware selection.\n *\n * Priority:\n * 1. LLM_API_KEY (universal override)\n * 2. Provider-specific key known by pi-ai (ANTHROPIC_API_KEY, OPENAI_API_KEY,\n * OPENROUTER_API_KEY, etc.)\n *\n * Returns null for bedrock (uses AWS credentials).\n */\nexport function getApiKey(model?: string): string | null {\n // Priority 1: Universal override\n const llmKey = process.env.LLM_API_KEY;\n if (llmKey) return llmKey;\n\n // Priority 2: Provider-specific\n if (model) {\n const { provider } = parseModelString(model);\n if (provider === \"amazon-bedrock\") return null;\n const key = getEnvApiKey(provider);\n if (key) return key;\n }\n\n throw new Error(\n model\n ? `No API key found for provider \"${parseModelString(model).provider}\". Set the provider-specific environment variable or LLM_API_KEY.`\n : \"No LLM API key found. Please set LLM_API_KEY or a provider-specific environment variable.\",\n );\n}\n","import chalk from \"chalk\";\nimport { logger } from \"./utils/logger.js\";\nimport type { ReviewMetrics } from \"./types.js\";\n\ntype FindingPriority = { priority: number };\n\nfunction tok(value: number): string {\n if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(2)}M`;\n if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`;\n return String(value);\n}\n\nfunction formatDuration(seconds: number): string {\n if (seconds >= 60) {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return s > 0 ? `${m}m ${s}s` : `${m}m`;\n }\n return `${seconds}s`;\n}\n\nexport function formatMetricsMarkdown(metrics: ReviewMetrics): string {\n const totalInput = metrics.inputTokens + metrics.cacheReadTokens;\n const parts = [`in \\`${tok(totalInput)}\\``];\n if (metrics.cacheReadTokens > 0) {\n parts.push(`cached \\`${tok(metrics.cacheReadTokens)}\\``);\n }\n parts.push(`out \\`${tok(metrics.outputTokens)}\\``);\n\n const lines = [\n `**Review Metrics** — ${metrics.turns} turns, ${metrics.toolCalls} tool calls, ${formatDuration(metrics.durationSeconds)}`,\n `- Tokens: ${parts.join(\" | \")} (total \\`${tok(metrics.totalTokens)}\\`)`,\n ];\n if (metrics.cost > 0) {\n lines.push(`- Cost: \\`$${metrics.cost.toFixed(4)}\\``);\n }\n return lines.join(\"\\n\");\n}\n\nexport function printMetrics(metrics: ReviewMetrics, stream: NodeJS.WritableStream = process.stderr): void {\n const dim = chalk.dim;\n const bold = chalk.bold;\n const cyan = chalk.cyan;\n\n const write = (line: string) => stream.write(line + \"\\n\");\n\n write(\"\");\n write(dim(\"─\".repeat(50)));\n\n // Tokens — inputTokens may be only fresh tokens (SDK reports cache hits separately)\n const totalInput = metrics.inputTokens + metrics.cacheReadTokens;\n let tokenLine = `${dim(\"Tokens:\")} ${bold(tok(totalInput))} in`;\n if (metrics.cacheReadTokens > 0) {\n const hitPct = ((metrics.cacheReadTokens / totalInput) * 100).toFixed(0);\n tokenLine += dim(` (${tok(metrics.cacheReadTokens)} cached ${hitPct}% · ${tok(metrics.inputTokens)} fresh)`);\n }\n tokenLine += ` ${bold(tok(metrics.outputTokens))} out`;\n tokenLine += dim(` (${tok(metrics.totalTokens)} total)`);\n write(tokenLine);\n\n // Agent work\n write(\n `${dim(\"Agent:\")} ${bold(String(metrics.turns))} turns ${bold(String(metrics.toolCalls))} tool calls ${cyan(formatDuration(metrics.durationSeconds))}`,\n );\n\n // Cost\n if (metrics.cost > 0) {\n write(`${dim(\"Cost:\")} ${bold(\"$\" + metrics.cost.toFixed(4))}`);\n }\n\n write(dim(\"─\".repeat(50)));\n}\n\n/**\n * Push review metrics to a Prometheus Pushgateway.\n * Failures are logged as warnings and never thrown.\n */\nexport async function pushMetrics(opts: {\n pushgatewayUrl: string;\n metrics: ReviewMetrics;\n findings?: FindingPriority[];\n labels?: Record<string, string>;\n}): Promise<void> {\n const { pushgatewayUrl, metrics, findings = [], labels = {} } = opts;\n\n // Build label string for all metrics\n const formatLabels = (extraLabels: Record<string, string> = {}) => {\n const labelPairs = Object.entries({ ...labels, ...extraLabels })\n .map(([k, v]) => `${k}=\"${v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\\n/g, \"\\\\n\").replace(/\"/g, '\\\\\"')}\"`)\n .join(\",\");\n return labelPairs ? `{${labelPairs}}` : \"\";\n };\n const labelSuffix = formatLabels();\n\n const totalInput = metrics.inputTokens + metrics.cacheReadTokens;\n const cacheHitRatio = totalInput > 0 ? metrics.cacheReadTokens / totalInput : 0;\n const priorityCounts = [0, 1, 2, 3].map((priority) => ({\n priority,\n count: findings.filter((finding) => finding.priority === priority).length,\n }));\n const lines = [\n `# HELP hodor_review_input_tokens_total Total input tokens (fresh + cached)`,\n `# TYPE hodor_review_input_tokens_total gauge`,\n `hodor_review_input_tokens_total${labelSuffix} ${totalInput}`,\n `# HELP hodor_review_output_tokens_total Total output tokens`,\n `# TYPE hodor_review_output_tokens_total gauge`,\n `hodor_review_output_tokens_total${labelSuffix} ${metrics.outputTokens}`,\n `# HELP hodor_review_cache_read_tokens_total Tokens served from prompt cache`,\n `# TYPE hodor_review_cache_read_tokens_total gauge`,\n `hodor_review_cache_read_tokens_total${labelSuffix} ${metrics.cacheReadTokens}`,\n `# HELP hodor_review_cache_write_tokens_total Tokens written to prompt cache`,\n `# TYPE hodor_review_cache_write_tokens_total gauge`,\n `hodor_review_cache_write_tokens_total${labelSuffix} ${metrics.cacheWriteTokens}`,\n `# HELP hodor_review_cache_hit_ratio Fraction of input tokens served from cache (0-1)`,\n `# TYPE hodor_review_cache_hit_ratio gauge`,\n `hodor_review_cache_hit_ratio${labelSuffix} ${cacheHitRatio}`,\n `# HELP hodor_review_findings_total Number of findings at each priority level`,\n `# TYPE hodor_review_findings_total gauge`,\n ...priorityCounts.map(({ priority, count }) =>\n `hodor_review_findings_total${formatLabels({ priority: `P${priority}` })} ${count}`,\n ),\n `# HELP hodor_review_cost_dollars Cost of the review in USD`,\n `# TYPE hodor_review_cost_dollars gauge`,\n `hodor_review_cost_dollars${labelSuffix} ${metrics.cost}`,\n `# HELP hodor_review_turns_total Number of agent turns`,\n `# TYPE hodor_review_turns_total gauge`,\n `hodor_review_turns_total${labelSuffix} ${metrics.turns}`,\n `# HELP hodor_review_tool_calls_total Number of tool calls`,\n `# TYPE hodor_review_tool_calls_total gauge`,\n `hodor_review_tool_calls_total${labelSuffix} ${metrics.toolCalls}`,\n `# HELP hodor_review_duration_seconds Review duration in seconds`,\n `# TYPE hodor_review_duration_seconds gauge`,\n `hodor_review_duration_seconds${labelSuffix} ${metrics.durationSeconds}`,\n \"\",\n ];\n const body = lines.join(\"\\n\");\n\n // POST to either a Prometheus Pushgateway base URL or a direct Prometheus text import endpoint.\n const baseUrl = pushgatewayUrl.replace(/\\/+$/, \"\");\n const url = baseUrl.endsWith(\"/api/v1/import/prometheus\")\n ? baseUrl\n : `${baseUrl}/metrics/job/hodor`;\n\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"text/plain\" },\n body,\n signal: AbortSignal.timeout(10_000),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n logger.warn(`Metrics endpoint returned ${res.status}: ${text.slice(0, 200)}`);\n } else {\n logger.info(\"Metrics pushed successfully\");\n }\n } catch (err) {\n logger.warn(`Failed to push metrics: ${err instanceof Error ? err.message : err}`);\n }\n}\n","import { isAbsolute } from \"node:path\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { ReviewOutput, ReviewPriority } from \"./types.js\";\n\nconst REVIEW_PRIORITY_TAGS = new Map<string, ReviewPriority>([\n [\"[P0]\", 0],\n [\"[P1]\", 1],\n [\"[P2]\", 2],\n [\"[P3]\", 3],\n]);\n\nexport const REVIEW_LOCATION_SCHEMA = Type.Object(\n {\n absolute_file_path: Type.String({ minLength: 1 }),\n line_range: Type.Object(\n {\n start: Type.Integer({ minimum: 1 }),\n end: Type.Integer({ minimum: 1 }),\n },\n { additionalProperties: false },\n ),\n },\n { additionalProperties: false },\n);\n\nexport const REVIEW_FINDING_SCHEMA = Type.Object(\n {\n title: Type.String({ minLength: 1 }),\n body: Type.String({ minLength: 1 }),\n priority: Type.Integer({ minimum: 0, maximum: 3 }),\n code_location: REVIEW_LOCATION_SCHEMA,\n existing_code: Type.Optional(Type.String({ minLength: 1 })),\n suggestion: Type.Optional(Type.String({ minLength: 1 })),\n },\n { additionalProperties: false },\n);\n\nexport const SUBMIT_REVIEW_SCHEMA = Type.Object(\n {\n findings: Type.Array(REVIEW_FINDING_SCHEMA),\n overall_correctness: Type.Union([\n Type.Literal(\"patch is correct\"),\n Type.Literal(\"patch is incorrect\"),\n ]),\n overall_explanation: Type.String({ minLength: 1 }),\n },\n { additionalProperties: false },\n);\n\nexport function validateReviewOutput(review: ReviewOutput): ReviewOutput {\n if (review.overall_explanation.trim().length === 0) {\n throw new Error(\"submit_review overall_explanation must be non-empty\");\n }\n\n // Auto-correct verdict when it contradicts findings. Findings are the ground truth;\n // the verdict field is mechanically derivable. Throwing here would prevent `terminate: true`\n // from being returned, leaving pi's error-handling behavior undefined.\n if (review.findings.length > 0 && review.overall_correctness !== \"patch is incorrect\") {\n review = { ...review, overall_correctness: \"patch is incorrect\" };\n } else if (review.findings.length === 0 && review.overall_correctness !== \"patch is correct\") {\n review = { ...review, overall_correctness: \"patch is correct\" };\n }\n\n for (const [index, finding] of review.findings.entries()) {\n const label = `submit_review finding ${index + 1}`;\n if (finding.title.trim().length === 0) {\n throw new Error(`${label} title must be non-empty`);\n }\n if (finding.body.trim().length === 0) {\n throw new Error(`${label} body must be non-empty`);\n }\n\n const taggedPriority = getPriorityFromTitle(finding.title);\n if (taggedPriority == null) {\n throw new Error(`${label} title must start with [P0], [P1], [P2], or [P3]`);\n }\n if (finding.priority !== taggedPriority) {\n throw new Error(\n `${label} priority ${finding.priority} does not match title tag ${taggedPriority}`,\n );\n }\n\n const { absolute_file_path: filePath, line_range: lineRange } = finding.code_location;\n if (!isAbsolute(filePath)) {\n throw new Error(`${label} code_location.absolute_file_path must be absolute`);\n }\n if (lineRange.start > lineRange.end) {\n throw new Error(`${label} code_location line_range start must be <= end`);\n }\n }\n\n return review;\n}\n\nfunction getPriorityFromTitle(title: string): ReviewPriority | null {\n const match = title.match(/^\\[(P[0-3])\\]/);\n if (!match) return null;\n return REVIEW_PRIORITY_TAGS.get(`[${match[1]}]`) ?? null;\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { Value } from \"@sinclair/typebox/value\";\nimport type { AgentSession, ToolDefinition } from \"@earendil-works/pi-coding-agent\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { logger } from \"./utils/logger.js\";\nimport { exec } from \"./utils/exec.js\";\nimport {\n fetchGithubPrInfo,\n normalizeGithubMetadata,\n} from \"./github.js\";\nimport {\n fetchGitlabMrInfo,\n postGitlabMrComment,\n getGitlabMrDiffRefs,\n createGitlabDraftNote,\n bulkPublishGitlabDraftNotes,\n postGitlabCommitStatus,\n listHodorDiscussions,\n resolveGitlabDiscussions,\n HODOR_REVIEW_MARKER,\n} from \"./gitlab.js\";\nimport {\n fetchGiteaPrInfo,\n postGiteaPrComment,\n} from \"./gitea.js\";\nimport type { DiffRefs } from \"./gitlab.js\";\nimport { setupWorkspace, cleanupWorkspace } from \"./workspace.js\";\nimport { buildPrReviewPrompt } from \"./prompt.js\";\nimport {\n getDefaultReasoningEffortForModel,\n mapReasoningEffort,\n parseModelString,\n} from \"./model.js\";\nimport { formatMetricsMarkdown, printMetrics } from \"./metrics.js\";\nimport { SUBMIT_REVIEW_SCHEMA, validateReviewOutput } from \"./review.js\";\nimport { resolveReviewLocations } from \"./resolve-location.js\";\nimport { REVIEW_SYSTEM_PROMPT } from \"./system-prompt.js\";\nimport { renderMarkdown, renderSummaryMarkdown } from \"./render.js\";\nimport { relativizeWorkspacePath } from \"./utils/path.js\";\nimport type {\n Platform,\n ParsedPrUrl,\n ReviewMetrics,\n PostCommentResult,\n MrMetadata,\n ReviewOutput,\n} from \"./types.js\";\n\nexport interface AgentProgressEvent {\n type: \"tool_start\" | \"tool_end\" | \"thinking\" | \"turn_start\" | \"turn_end\" | \"agent_start\" | \"agent_end\" | \"text_delta\" | \"thinking_delta\" | \"tool_result\";\n toolName?: string;\n toolArgs?: string;\n isError?: boolean;\n turnIndex?: number;\n delta?: string;\n result?: string;\n}\n\nexport function detectPlatform(prUrl: string): Platform {\n const url = new URL(prUrl);\n const hostname = url.hostname;\n if (prUrl.includes(\"/-/merge_requests/\") || hostname.includes(\"gitlab\")) {\n return \"gitlab\";\n }\n // Gitea/Forgejo: /pulls/ (plural) — must check before GitHub since /pulls/ contains /pull/\n if (prUrl.includes(\"/pulls/\") || hostname.includes(\"gitea\") || hostname.includes(\"forgejo\") || hostname.includes(\"codeberg\")) {\n return \"gitea\";\n }\n if (prUrl.includes(\"/pull/\") || hostname.includes(\"github\")) {\n return \"github\";\n }\n throw new Error(\n `Cannot detect platform for URL: ${prUrl}. Expected a GitHub (/pull/), GitLab (/-/merge_requests/), or Gitea/Forgejo (/pulls/) URL.`,\n );\n}\n\nexport function parsePrUrl(prUrl: string): ParsedPrUrl {\n const url = new URL(prUrl);\n const pathParts = url.pathname.split(\"/\").filter(Boolean);\n const host = url.host;\n\n // GitHub format: /owner/repo/pull/123\n if (pathParts.length >= 4 && pathParts[2] === \"pull\") {\n const prNumber = parseInt(pathParts[3], 10);\n if (!Number.isSafeInteger(prNumber) || prNumber <= 0) {\n throw new Error(`Invalid PR number in URL: ${prUrl}. Expected a positive integer after /pull/.`);\n }\n return {\n owner: pathParts[0],\n repo: pathParts[1],\n prNumber,\n host,\n };\n }\n\n // Gitea/Forgejo format: /owner/repo/pulls/123\n if (pathParts.length >= 4 && pathParts[2] === \"pulls\") {\n const prNumber = parseInt(pathParts[3], 10);\n if (!Number.isSafeInteger(prNumber) || prNumber <= 0) {\n throw new Error(`Invalid PR number in URL: ${prUrl}. Expected a positive integer after /pulls/.`);\n }\n return {\n owner: pathParts[0],\n repo: pathParts[1],\n prNumber,\n host,\n };\n }\n\n // GitLab format: /group/subgroup/repo/-/merge_requests/123\n const mrIndex = pathParts.indexOf(\"merge_requests\");\n if (mrIndex >= 0) {\n if (mrIndex < 2 || mrIndex + 1 >= pathParts.length) {\n throw new Error(\n `Invalid GitLab MR URL format: ${prUrl}. Expected .../-/merge_requests/<number>`,\n );\n }\n if (pathParts[mrIndex - 1] !== \"-\") {\n throw new Error(\n `Invalid GitLab MR URL format: ${prUrl}. Missing '/-/' segment before merge_requests.`,\n );\n }\n\n const repo = pathParts[mrIndex - 2];\n const ownerParts = pathParts.slice(0, mrIndex - 2);\n const owner =\n ownerParts.length > 0 ? ownerParts.join(\"/\") : pathParts[0];\n const prNumber = parseInt(pathParts[mrIndex + 1], 10);\n if (!Number.isSafeInteger(prNumber) || prNumber <= 0) {\n throw new Error(`Invalid MR number in URL: ${prUrl}. Expected a positive integer after /merge_requests/.`);\n }\n return { owner, repo, prNumber, host };\n }\n\n throw new Error(\n `Invalid PR/MR URL format: ${prUrl}. Expected GitHub (/pull/), GitLab (/-/merge_requests/), or Gitea/Forgejo (/pulls/) URL.`,\n );\n}\n\nfunction formatLocationRelative(\n loc: { absolute_file_path: string },\n workspacePath?: string | null,\n): string {\n return relativizeWorkspacePath(loc.absolute_file_path, workspacePath ?? undefined);\n}\n\n/**\n * Post a pass/fail commit status to a GitLab MR head SHA based on review priorities.\n * Findings with priority <= 1 (P0/P1) are treated as blocking.\n */\nexport async function postGitlabReviewCommitStatus(\n parsed: ParsedPrUrl,\n review: ReviewOutput,\n diffRefs: DiffRefs,\n): Promise<void> {\n const blocking = review.findings.filter((f) => f.priority <= 1).length;\n const state = blocking > 0 ? \"failed\" : \"success\";\n const description =\n blocking > 0\n ? `${blocking} blocking issue(s) found`\n : review.findings.length > 0\n ? `${review.findings.length} non-blocking issue(s)`\n : \"No issues found\";\n\n await postGitlabCommitStatus(\n parsed.owner,\n parsed.repo,\n diffRefs.head_sha,\n state,\n parsed.host,\n { description },\n );\n}\n\nexport async function postReviewComment(opts: {\n prUrl: string;\n reviewText: string;\n model?: string | null;\n metricsFooter?: string | null;\n headSha?: string | null;\n}): Promise<PostCommentResult> {\n const { prUrl, reviewText, model, metricsFooter, headSha } = opts;\n const platform = detectPlatform(prUrl);\n logger.info(`Posting comment to ${platform} PR/MR: ${prUrl}`);\n\n let parsed: ParsedPrUrl;\n try {\n parsed = parsePrUrl(prUrl);\n } catch (err) {\n return { success: false, error: String(err) };\n }\n\n let body = reviewText;\n if (headSha) {\n body = `<!-- hodor:sha:${headSha} -->\\n${body}`;\n }\n if (model) {\n body = `${body}\\n\\n---\\n\\nReview generated by Hodor (model: \\`${model}\\`)`;\n }\n if (metricsFooter) {\n body = `${body}\\n\\n${metricsFooter}`;\n }\n\n try {\n if (platform === \"github\") {\n await exec(\"gh\", [\n \"pr\",\n \"review\",\n String(parsed.prNumber),\n \"--repo\",\n `${parsed.owner}/${parsed.repo}`,\n \"--comment\",\n \"--body\",\n body,\n ]);\n logger.info(`Successfully posted review to GitHub PR #${parsed.prNumber}`);\n return { success: true, platform: \"github\", prNumber: parsed.prNumber };\n } else if (platform === \"gitea\") {\n await postGiteaPrComment(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n body,\n parsed.host,\n );\n logger.info(`Successfully posted review to Gitea PR #${parsed.prNumber}`);\n return { success: true, platform: \"gitea\", prNumber: parsed.prNumber };\n } else {\n await postGitlabMrComment(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n body,\n parsed.host,\n );\n logger.info(\n `Successfully posted review to GitLab MR !${parsed.prNumber}`,\n );\n return {\n success: true,\n platform: \"gitlab\",\n mrNumber: parsed.prNumber,\n };\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.error(`Failed to post comment: ${msg}`);\n return { success: false, error: msg };\n }\n}\n\nconst HODOR_REVIEW_SHA_RE = /^\\s*<!--\\s*hodor:sha:([a-f0-9]{40})\\s*-->/i;\n\nexport function getHodorReviewShaCandidates(notes: MrMetadata[\"Notes\"] | undefined | null): string[] {\n if (!notes || notes.length === 0) return [];\n\n const candidates: Array<{ sha: string; createdAtMs: number | null; index: number }> = [];\n for (const [index, note] of notes.entries()) {\n const match = note.body?.match(HODOR_REVIEW_SHA_RE);\n if (!match) continue;\n\n const createdAtMs = Date.parse(note.created_at ?? \"\");\n candidates.push({\n sha: match[1],\n createdAtMs: Number.isFinite(createdAtMs) ? createdAtMs : null,\n index,\n });\n }\n\n candidates.sort((a, b) => {\n if (a.createdAtMs != null && b.createdAtMs != null && a.createdAtMs !== b.createdAtMs) {\n return b.createdAtMs - a.createdAtMs;\n }\n if (a.createdAtMs != null && b.createdAtMs == null) return -1;\n if (a.createdAtMs == null && b.createdAtMs != null) return 1;\n return a.index - b.index;\n });\n\n const seen = new Set<string>();\n const shas: string[] = [];\n for (const { sha } of candidates) {\n if (seen.has(sha)) continue;\n seen.add(sha);\n shas.push(sha);\n }\n return shas;\n}\n\nasync function findLatestValidReviewSha(\n notes: MrMetadata[\"Notes\"] | undefined | null,\n workspacePath: string,\n): Promise<string | null> {\n const candidates = getHodorReviewShaCandidates(notes);\n if (candidates.length === 0) return null;\n\n logger.info(`Found ${candidates.length} previous Hodor review marker(s)`);\n for (const sha of candidates) {\n try {\n const { stdout: objType } = await exec(\"git\", [\"cat-file\", \"-t\", sha], { cwd: workspacePath });\n if (objType.trim() !== \"commit\") throw new Error(\"not a commit\");\n await exec(\"git\", [\"merge-base\", \"--is-ancestor\", sha, \"HEAD\"], { cwd: workspacePath });\n return sha;\n } catch {\n logger.info(`Skipping previous review SHA ${sha.slice(0, 8)}; not a valid ancestor of HEAD`);\n }\n }\n\n return null;\n}\n\nexport async function postReviewStructured(opts: {\n prUrl: string;\n review: ReviewOutput;\n model?: string | null;\n metricsFooter?: string | null;\n reviewStyle?: \"summary\" | \"inline\" | \"hybrid\";\n commitStatus?: boolean;\n codeQualityPath?: string | null;\n headSha?: string | null;\n workspacePath?: string | null;\n}): Promise<PostCommentResult> {\n const {\n prUrl,\n review,\n model,\n metricsFooter,\n reviewStyle,\n commitStatus,\n codeQualityPath,\n headSha,\n workspacePath,\n } = opts;\n\n const platform = detectPlatform(prUrl);\n if (platform === \"github\") {\n return postReviewComment({\n prUrl,\n reviewText: renderMarkdown(review),\n model,\n metricsFooter,\n headSha,\n });\n }\n\n if (reviewStyle === \"summary\") {\n return postReviewComment({\n prUrl,\n reviewText: renderMarkdown(review),\n model,\n metricsFooter,\n headSha,\n });\n }\n\n const parsed = parsePrUrl(prUrl);\n\n try {\n const discussions = await listHodorDiscussions(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n parsed.host,\n );\n const unresolvedIds = [...new Set(\n discussions.filter((d) => !d.resolved).map((d) => d.discussionId),\n )];\n if (unresolvedIds.length > 0) {\n const resolved = await resolveGitlabDiscussions(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n unresolvedIds,\n parsed.host,\n );\n if (resolved > 0) logger.info(`Resolved ${resolved} old Hodor discussion(s)`);\n }\n } catch (err) {\n logger.warn(`Failed to resolve old discussions: ${err instanceof Error ? err.message : err}`);\n }\n\n let diffRefs: DiffRefs | null = null;\n try {\n diffRefs = await getGitlabMrDiffRefs(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n parsed.host,\n );\n } catch (err) {\n logger.warn(`Failed to get diff_refs, falling back to summary mode: ${err instanceof Error ? err.message : err}`);\n }\n\n if (!diffRefs) {\n return postReviewComment({\n prUrl,\n reviewText: renderMarkdown(review),\n model,\n metricsFooter,\n headSha,\n });\n }\n\n let inlineCount = 0;\n let failedCount = 0;\n let summaryPosted = false;\n let draftsPublished = false;\n let statusPosted = false;\n const postingErrors: string[] = [];\n\n for (const finding of review.findings) {\n const relPath = formatLocationRelative(finding.code_location, workspacePath);\n const priorityTag = `[P${finding.priority}]`;\n const title = /^\\[P[0-3]\\]/.test(finding.title)\n ? finding.title\n : `${priorityTag} ${finding.title}`;\n let body = `${HODOR_REVIEW_MARKER}\\n**${title}**\\n\\n${finding.body}`;\n\n if (finding.suggestion) {\n // GitLab suggestion blocks anchor to the comment's line and extend `+N` lines below.\n // Single-line: `suggestion:-0+0` (replace one line). Range: `suggestion:-0+N`\n // where N is the number of additional lines beyond the anchor.\n const { start, end } = finding.code_location.line_range;\n const span = Math.max(0, end - start);\n body += `\\n\\n\\`\\`\\`suggestion:-0+${span}\\n${finding.suggestion}\\n\\`\\`\\``;\n }\n\n try {\n await createGitlabDraftNote(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n body,\n parsed.host,\n {\n filePath: relPath,\n line: finding.code_location.line_range.start,\n diffRefs,\n },\n );\n inlineCount++;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn(`Failed to create inline note for \"${finding.title}\": ${msg}`);\n postingErrors.push(`inline note: ${msg}`);\n failedCount++;\n }\n }\n\n logger.info(`Created ${inlineCount} inline draft note(s)${failedCount > 0 ? ` (${failedCount} failed)` : \"\"}`);\n\n if (reviewStyle === \"hybrid\" || reviewStyle === undefined) {\n let summaryBody = renderSummaryMarkdown(review);\n if (headSha) summaryBody = `<!-- hodor:sha:${headSha} -->\\n${summaryBody}`;\n if (model) summaryBody += `\\n---\\n\\nReview generated by Hodor (model: \\`${model}\\`)`;\n if (metricsFooter) summaryBody += `\\n\\n${metricsFooter}`;\n try {\n await postGitlabMrComment(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n summaryBody,\n parsed.host,\n );\n summaryPosted = true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn(`Failed to post summary comment: ${msg}`);\n postingErrors.push(`summary comment: ${msg}`);\n }\n }\n\n if (inlineCount > 0) {\n try {\n await bulkPublishGitlabDraftNotes(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n parsed.host,\n );\n logger.info(\"Published all draft notes\");\n draftsPublished = true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn(`Failed to bulk publish draft notes: ${msg}`);\n postingErrors.push(`draft publish: ${msg}`);\n }\n }\n\n if (commitStatus && diffRefs) {\n try {\n await postGitlabReviewCommitStatus(parsed, review, diffRefs);\n logger.info(\"Posted commit status\");\n statusPosted = true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn(`Failed to post commit status: ${msg}`);\n postingErrors.push(`commit status: ${msg}`);\n }\n }\n\n if (codeQualityPath) {\n try {\n const { formatCodeQualityReport } = await import(\"./codequality.js\");\n const report = formatCodeQualityReport(review, workspacePath ?? undefined);\n const { writeFileSync } = await import(\"node:fs\");\n writeFileSync(codeQualityPath, report, \"utf-8\");\n logger.info(`Wrote code quality report to ${codeQualityPath}`);\n } catch (err) {\n logger.warn(`Failed to write code quality report: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n const visibleResult = summaryPosted || (inlineCount > 0 && draftsPublished) || statusPosted;\n const expectedInlineComments = reviewStyle === \"inline\" && review.findings.length > 0;\n if ((postingErrors.length > 0 && !visibleResult) || (expectedInlineComments && inlineCount === 0)) {\n return {\n success: false,\n platform: \"gitlab\",\n mrNumber: parsed.prNumber,\n error: postingErrors[0] ?? \"No GitLab inline comments were created\",\n };\n }\n\n return {\n success: true,\n platform: \"gitlab\",\n mrNumber: parsed.prNumber,\n };\n}\n\n// Paths that waste context without contributing reviewable logic.\nconst DIFF_SKIP_PATTERNS: RegExp[] = [\n /(?:^|\\/)testdata\\//, // test fixture directories\n /(?:^|\\/)(?:package-lock\\.json|yarn\\.lock|pnpm-lock\\.yaml|go\\.sum|Cargo\\.lock|poetry\\.lock|Gemfile\\.lock|composer\\.lock)$/,\n /\\.mdx?$/, // markdown docs\n];\n\nexport function filterEmbeddedDiff(rawDiff: string): { filtered: string; skippedFiles: string[] } {\n const skippedFiles: string[] = [];\n // Each file section starts with \"diff --git a/\". Split while preserving the delimiter.\n const sections = rawDiff.split(/(?=^diff --git )/m);\n const kept: string[] = [];\n for (const section of sections) {\n const match = section.match(/^diff --git a\\/(.*?) b\\//);\n if (!match) {\n kept.push(section);\n continue;\n }\n const filePath = match[1];\n if (DIFF_SKIP_PATTERNS.some((re) => re.test(filePath))) {\n skippedFiles.push(filePath);\n } else {\n kept.push(section);\n }\n }\n return { filtered: kept.join(\"\"), skippedFiles };\n}\n\nconst SUBMIT_REVIEW_RECOVERY_ATTEMPTS = 2;\n\nexport function buildSubmitReviewRecoveryPrompt(attempt: number, maxAttempts: number): string {\n const finalAttempt =\n attempt >= maxAttempts\n ? \"\\nThis is the final automatic recovery attempt; do not end the turn without calling `submit_review`.\"\n : \"\";\n\n return [\n \"Your previous assistant turn ended without a valid `submit_review` tool call, so Hodor cannot capture the review.\",\n \"Continue from the existing review context. Use only read-only tools and only the changed files/diff already identified.\",\n \"If more evidence is needed, inspect the relevant diff or file context now.\",\n \"When analysis is complete, call `submit_review` exactly once. Do not write the review as normal text.\",\n \"If there are no findings, call `submit_review` with `\\\"findings\\\": []` and `\\\"overall_correctness\\\": \\\"patch is correct\\\"`.\",\n finalAttempt,\n ].filter(Boolean).join(\"\\n\");\n}\n\nexport function parseReviewFromAssistantText(text: string): ReviewOutput | null {\n const candidates = getJsonCandidates(text);\n for (const candidate of candidates) {\n try {\n const parsed = JSON.parse(candidate) as unknown;\n if (!Value.Check(SUBMIT_REVIEW_SCHEMA, parsed)) {\n continue;\n }\n return validateReviewOutput(parsed as ReviewOutput);\n } catch {\n // Keep scanning; assistant text often contains prose around the payload.\n }\n }\n return null;\n}\n\nfunction getJsonCandidates(text: string): string[] {\n const candidates: string[] = [];\n const seen = new Set<string>();\n\n const addCandidate = (value: string): void => {\n const trimmed = value.trim();\n if (!trimmed || seen.has(trimmed)) return;\n seen.add(trimmed);\n candidates.push(trimmed);\n };\n\n addCandidate(text);\n\n const fencedJson = /```(?:json)?\\s*([\\s\\S]*?)```/gi;\n for (const match of text.matchAll(fencedJson)) {\n addCandidate(match[1] ?? \"\");\n }\n\n const firstBrace = text.indexOf(\"{\");\n const lastBrace = text.lastIndexOf(\"}\");\n if (firstBrace >= 0 && lastBrace > firstBrace) {\n addCandidate(text.slice(firstBrace, lastBrace + 1));\n }\n\n return candidates;\n}\n\nfunction summarizeLastAssistantMessage(session: AgentSession): string {\n const messages = session.messages as unknown as Array<Record<string, unknown>>;\n const lastAssistant = [...messages].reverse().find((msg) => msg.role === \"assistant\");\n if (!lastAssistant) {\n return \"no assistant message\";\n }\n\n const stopReason =\n typeof lastAssistant.stopReason === \"string\" ? lastAssistant.stopReason : \"unknown\";\n const errorMessage =\n typeof lastAssistant.errorMessage === \"string\" ? `, error=${JSON.stringify(truncateForLog(lastAssistant.errorMessage, 300))}` : \"\";\n const content = Array.isArray(lastAssistant.content)\n ? lastAssistant.content\n .map((item) => {\n const block = item as Record<string, unknown>;\n const type = typeof block.type === \"string\" ? block.type : \"unknown\";\n if (type === \"toolCall\" && typeof block.name === \"string\") {\n return `toolCall:${block.name}`;\n }\n return type;\n })\n .join(\",\")\n : \"unknown\";\n const rawText = session.getLastAssistantText()?.trim();\n const textSummary = rawText\n ? `, text=${JSON.stringify(truncateForLog(rawText.replace(/\\s+/g, \" \"), 500))}`\n : \"\";\n\n return `stopReason=${stopReason}, content=[${content || \"none\"}]${errorMessage}${textSummary}`;\n}\n\nfunction truncateForLog(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return `${text.slice(0, maxLength - 1)}…`;\n}\n\nexport async function reviewPr(opts: {\n prUrl?: string;\n model?: string;\n reasoningEffort?: string;\n customPrompt?: string | null;\n promptFile?: string | null;\n cleanup?: boolean;\n workspaceDir?: string | null;\n includeMetricsFooter?: boolean;\n onEvent?: (event: AgentProgressEvent) => void;\n bedrockTags?: Record<string, string> | null;\n localMode?: boolean;\n diffAgainst?: string;\n}): Promise<{ review: ReviewOutput; metricsFooter: string | null; headSha: string | null; metrics: ReviewMetrics; workspacePath: string }> {\n const {\n prUrl,\n model = \"anthropic/claude-sonnet-4-5-20250929\",\n reasoningEffort,\n customPrompt,\n promptFile,\n cleanup = true,\n workspaceDir,\n includeMetricsFooter = false,\n onEvent,\n bedrockTags,\n localMode = false,\n diffAgainst,\n } = opts;\n\n logger.info(`Starting PR review for: ${localMode ? \"local diff\" : prUrl}`);\n\n let owner = \"\", repo = \"\", host = \"\";\n let prNumber = 0;\n let platform: Platform = \"github\";\n\n if (!localMode && prUrl) {\n const urlParsed = parsePrUrl(prUrl);\n owner = urlParsed.owner;\n repo = urlParsed.repo;\n prNumber = urlParsed.prNumber;\n host = urlParsed.host;\n platform = detectPlatform(prUrl);\n logger.info(`Platform: ${platform}, Repo: ${owner}/${repo}, PR: ${prNumber}, Host: ${host}`);\n }\n\n // --- Preflight: validate model + credentials before any expensive I/O ---\n const parsed = parseModelString(model);\n\n // Snapshot env vars we may mutate, restore in finally block.\n const envSnapshot: Record<string, string | undefined> = {\n AWS_REGION: process.env.AWS_REGION,\n };\n\n // Import pi SDK\n const {\n AuthStorage,\n createAgentSession,\n DefaultResourceLoader,\n ModelRegistry,\n SessionManager,\n SettingsManager,\n getAgentDir,\n } = await import(\"@earendil-works/pi-coding-agent\");\n\n // In-memory auth storage avoids loading ~/.pi/auth.json — env vars only.\n const authStorage = AuthStorage.inMemory();\n if (process.env.LLM_API_KEY) {\n authStorage.setRuntimeApiKey(parsed.provider, process.env.LLM_API_KEY);\n }\n const modelRegistry = ModelRegistry.inMemory(authStorage);\n\n // Resolve model — use registry for known models, construct manually for custom ARNs\n let piModel: Model<Api>;\n if (parsed.modelId.startsWith(\"arn:\")) {\n // Custom bedrock ARN (inference profile, cross-region, etc.)\n // Extract region from ARN: arn:aws:bedrock:<region>:<account>:...\n const arnParts = parsed.modelId.split(\":\");\n const region = arnParts.length >= 4 ? arnParts[3] : \"us-east-1\";\n // Set AWS_REGION so the BedrockRuntimeClient uses the correct endpoint\n if (!process.env.AWS_REGION && !process.env.AWS_DEFAULT_REGION) {\n process.env.AWS_REGION = region;\n }\n piModel = {\n id: parsed.modelId,\n name: parsed.modelId,\n api: \"bedrock-converse-stream\",\n provider: \"amazon-bedrock\",\n baseUrl: `https://bedrock-runtime.${region}.amazonaws.com`,\n reasoning: false,\n input: [\"text\"] as (\"text\" | \"image\")[],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 200000,\n maxTokens: 16384,\n } as Model<Api>;\n logger.info(`Custom bedrock ARN model — region: ${region}`);\n } else {\n const registryModel = modelRegistry.find(parsed.provider, parsed.modelId);\n if (registryModel) {\n piModel = registryModel;\n } else if (parsed.provider === \"openrouter\") {\n piModel = {\n id: parsed.modelId,\n name: parsed.modelId,\n api: \"openai-completions\",\n provider: \"openrouter\",\n baseUrl: \"https://openrouter.ai/api/v1\",\n reasoning: true,\n input: [\"text\", \"image\"] as (\"text\" | \"image\")[],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 256000,\n maxTokens: 65536,\n } as Model<Api>;\n logger.warn(`Using best-effort unregistered OpenRouter model — ${parsed.modelId}`);\n } else {\n throw new Error(\n `Unsupported model \"${model}\". Provider \"${parsed.provider}\" is recognized by pi-ai, but model \"${parsed.modelId}\" was not found in the installed registry.`,\n );\n }\n }\n const thinkingLevel =\n mapReasoningEffort(reasoningEffort) ?? getDefaultReasoningEffortForModel(piModel);\n if (!reasoningEffort && thinkingLevel) {\n logger.info(`Default reasoning effort for ${piModel.name}: ${thinkingLevel}`);\n }\n\n // Note: For bedrock, don't preflight-check AWS credentials because the SDK\n // resolves them from many sources (env vars, IMDS, ECS task role, IRSA,\n // ~/.aws/credentials, etc.) and we can't reliably detect all of them.\n if (parsed.provider !== \"amazon-bedrock\") {\n const resolvedKey = await modelRegistry.getApiKeyForProvider(parsed.provider);\n if (!resolvedKey) {\n throw new Error(\n `No API key found for provider \"${parsed.provider}\". Set the provider-specific environment variable, configure pi auth, or set LLM_API_KEY.`,\n );\n }\n }\n logger.info(\"Preflight OK — model and credentials validated\");\n\n // --- End preflight ---\n\n // Setup workspace\n let workspacePath: string;\n let targetBranch: string;\n let diffBaseSha: string | null = null;\n let isTemporary = false;\n\n if (localMode) {\n // Resolve to git repo root so paths from git diff match tool expectations\n const cwd = workspaceDir ?? process.cwd();\n try {\n const { stdout: toplevel } = await exec(\"git\", [\"rev-parse\", \"--show-toplevel\"], { cwd });\n workspacePath = toplevel.trim();\n } catch {\n workspacePath = cwd; // fallback if not in a git repo\n }\n targetBranch = diffAgainst ?? \"origin/main\";\n logger.info(`Local mode: workspace=${workspacePath}, diffAgainst=${targetBranch}`);\n } else {\n const wsResult = await setupWorkspace({\n platform,\n owner,\n repo,\n prNumber: String(prNumber),\n host,\n workingDir: workspaceDir ?? undefined,\n reuse: workspaceDir != null,\n });\n workspacePath = wsResult.workspace;\n targetBranch = wsResult.targetBranch;\n diffBaseSha = wsResult.diffBaseSha;\n isTemporary = wsResult.isTemporary;\n }\n\n let activeSession: AgentSession | undefined;\n\n try {\n let mrMetadata: MrMetadata | null = null;\n if (!localMode && platform === \"gitlab\") {\n try {\n mrMetadata = await fetchGitlabMrInfo(owner, repo, prNumber, host, {\n includeComments: true,\n });\n } catch (err) {\n logger.warn(`Failed to fetch GitLab metadata: ${err}`);\n }\n } else if (!localMode && platform === \"github\") {\n try {\n const githubRaw = await fetchGithubPrInfo(owner, repo, prNumber);\n mrMetadata = normalizeGithubMetadata(githubRaw);\n } catch (err) {\n logger.warn(`Failed to fetch GitHub metadata: ${err}`);\n }\n } else if (!localMode && platform === \"gitea\") {\n try {\n mrMetadata = await fetchGiteaPrInfo(owner, repo, prNumber, host, {\n includeComments: true,\n });\n } catch (err) {\n logger.warn(`Failed to fetch Gitea metadata: ${err}`);\n }\n }\n\n // Detect previous Hodor review SHA for incremental mode. GitLab returns MR\n // notes newest-first by default, so select by timestamp instead of taking\n // the last match from API order.\n const previousReviewSha = await findLatestValidReviewSha(mrMetadata?.Notes, workspacePath);\n if (previousReviewSha) {\n logger.info(`Incremental mode: previous review at ${previousReviewSha.slice(0, 8)}`);\n }\n\n // Get HEAD SHA for embedding in posted comments (skip in local mode — no posting)\n let headSha: string | null = null;\n if (!localMode) {\n const { stdout: headShaRaw } = await exec(\"git\", [\"rev-parse\", \"HEAD\"], { cwd: workspacePath });\n headSha = headShaRaw.trim();\n }\n\n // Pre-fetch diff for embedding in prompt (avoids per-file tool calls)\n const MAX_EMBED_BYTES = 200 * 1024; // 200KB\n let embeddedDiff: string | null = null;\n try {\n const diffArgs = previousReviewSha\n ? [\"--no-pager\", \"diff\", `${previousReviewSha}...HEAD`]\n : diffBaseSha\n ? [\"--no-pager\", \"diff\", diffBaseSha, \"HEAD\"]\n : localMode\n ? [\"--no-pager\", \"diff\", targetBranch] // includes uncommitted changes\n : [\"--no-pager\", \"diff\", `origin/${targetBranch}...HEAD`];\n const { stdout: rawDiff } = await exec(\"git\", diffArgs, { cwd: workspacePath });\n const { filtered: filteredDiff, skippedFiles } = filterEmbeddedDiff(rawDiff);\n if (skippedFiles.length > 0) {\n logger.info(`Filtered ${skippedFiles.length} file(s) from embedded diff: ${skippedFiles.join(\", \")}`);\n }\n if (Buffer.byteLength(filteredDiff, \"utf-8\") <= MAX_EMBED_BYTES) {\n embeddedDiff = filteredDiff;\n logger.info(`Embedding diff in prompt (${Buffer.byteLength(filteredDiff, \"utf-8\")} bytes, raw: ${Buffer.byteLength(rawDiff, \"utf-8\")} bytes)`);\n } else {\n logger.info(`Diff too large to embed (${Buffer.byteLength(filteredDiff, \"utf-8\")} bytes filtered, ${Buffer.byteLength(rawDiff, \"utf-8\")} bytes raw), using command mode`);\n }\n } catch (err) {\n logger.warn(`Failed to pre-fetch diff, falling back to command mode: ${err}`);\n }\n\n // Build prompt (always uses JSON template; rendered to markdown post-hoc)\n const prompt = buildPrReviewPrompt({\n prUrl: prUrl ?? `local diff (against ${targetBranch})`,\n platform,\n targetBranch,\n diffBaseSha,\n mrMetadata,\n customInstructions: customPrompt,\n customPromptFile: promptFile,\n embeddedDiff,\n previousReviewSha,\n localMode,\n });\n\n const startTime = Date.now();\n const settingsManager = SettingsManager.inMemory({\n compaction: { enabled: true },\n });\n const skillPaths = [join(workspacePath, \".agents\", \"skills\")]\n .filter((p) => existsSync(p));\n const resourceLoader = new DefaultResourceLoader({\n cwd: workspacePath,\n agentDir: getAgentDir(),\n settingsManager,\n systemPrompt: REVIEW_SYSTEM_PROMPT,\n appendSystemPrompt: [],\n noExtensions: true,\n noSkills: true,\n noPromptTemplates: true,\n noThemes: true,\n additionalSkillPaths: skillPaths,\n agentsFilesOverride: () => ({ agentsFiles: [] }),\n });\n await resourceLoader.reload();\n const { skills, diagnostics: skillDiagnostics } = resourceLoader.getSkills();\n if (skills.length > 0) {\n logger.info(`Discovered ${skills.length} repository skill(s)`);\n for (const skill of skills) {\n logger.info(`Found skill: ${skill.name} (${skill.filePath})`);\n }\n }\n for (const diagnostic of skillDiagnostics) {\n const path = diagnostic.path ? ` (${diagnostic.path})` : \"\";\n logger.warn(`Skill diagnostic: ${diagnostic.message}${path}`);\n }\n\n let submittedReview: ReviewOutput | null = null;\n let submitReviewCalls = 0;\n const submitReviewTool: ToolDefinition = {\n name: \"submit_review\",\n label: \"Submit Review\",\n description: \"Submit the final structured review after the analysis is complete.\",\n promptSnippet: \"Submit the final structured review (call exactly once when done)\",\n parameters: SUBMIT_REVIEW_SCHEMA,\n execute: async (_toolCallId, params, _signal, _onUpdate, _ctx) => {\n submitReviewCalls++;\n if (submittedReview) {\n logger.warn(\"Agent called submit_review more than once; ignoring duplicate submission\");\n return {\n content: [{\n type: \"text\",\n text: \"Review already submitted. Do not call submit_review again.\",\n }],\n details: { ignoredDuplicate: true },\n };\n }\n\n try {\n submittedReview = validateReviewOutput(params as ReviewOutput);\n } catch (err) {\n logger.warn(`Invalid submit_review payload: ${err instanceof Error ? err.message : err}`);\n throw err;\n }\n logger.info(\n `Received structured review via submit_review (${submittedReview.findings.length} finding(s))`,\n );\n return {\n content: [{\n type: \"text\",\n text: \"Review received. Do not output the review as normal text.\",\n }],\n details: {},\n terminate: true,\n };\n },\n };\n\n const { session } = await createAgentSession({\n cwd: workspacePath,\n model: piModel,\n thinkingLevel,\n // pi v0.74 filters customTools through the same allowlist as built-ins\n // (see _refreshToolRegistry in @earendil-works/pi-coding-agent's\n // agent-session.ts). The submit_review custom tool must be named here\n // or the LLM never sees it and the agent loop exits without calling it.\n tools: [\"read\", \"bash\", \"grep\", \"find\", \"ls\", \"submit_review\"],\n customTools: [submitReviewTool],\n authStorage,\n modelRegistry,\n sessionManager: SessionManager.inMemory(),\n settingsManager,\n resourceLoader,\n });\n activeSession = session;\n\n // Inject Bedrock cost allocation tags into stream requests\n if (bedrockTags && parsed.provider === \"amazon-bedrock\") {\n type AgentWithStream = { agent: { streamFn: (...args: unknown[]) => unknown } };\n const agent = (session as unknown as AgentWithStream).agent;\n const originalStreamFn = agent.streamFn;\n agent.streamFn = (...args: unknown[]) => {\n const options = (args[2] ?? {}) as Record<string, unknown>;\n return originalStreamFn(args[0], args[1], { ...options, requestMetadata: bedrockTags });\n };\n logger.info(`Bedrock cost allocation tags: ${JSON.stringify(bedrockTags)}`);\n }\n\n // Subscribe to agent events for progress + metrics tracking\n let turnCount = 0;\n let toolCallCount = 0;\n\n /** Extract human-readable summary from tool args */\n function formatToolArgs(_toolName: string, args: unknown): string {\n if (typeof args === \"string\") return args.slice(0, 200);\n const obj = args as Record<string, unknown> | undefined;\n if (!obj) return \"\";\n // bash tool: show the command, strip workspace prefix\n if (obj.command) {\n return String(obj.command)\n .replace(new RegExp(`cd ${workspacePath.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")} && `), \"\")\n .slice(0, 200);\n }\n // grep/find: show pattern + path\n if (obj.pattern) {\n const path = obj.path ? ` in ${obj.path}` : \"\";\n return `${obj.pattern}${path}`;\n }\n // read/ls: show the path\n if (obj.path || obj.file_path) return String(obj.path ?? obj.file_path);\n return JSON.stringify(obj).slice(0, 200);\n }\n\n /** Extract text content from tool result */\n function formatToolResult(result: unknown): string {\n if (typeof result === \"string\") return result;\n const obj = result as Record<string, unknown> | undefined;\n if (!obj) return \"\";\n // pi-sdk wraps results as {content: [{type: \"text\", text: \"...\"}]}\n const content = obj.content as Array<{ type?: string; text?: string }> | undefined;\n if (Array.isArray(content)) {\n return content\n .filter((c) => c.type === \"text\" && c.text)\n .map((c) => c.text)\n .join(\"\\n\");\n }\n return JSON.stringify(result)?.slice(0, 500) ?? \"\";\n }\n\n session.subscribe((event) => {\n switch (event.type) {\n case \"agent_start\":\n onEvent?.({ type: \"agent_start\" });\n break;\n case \"agent_end\":\n onEvent?.({ type: \"agent_end\" });\n break;\n case \"turn_start\":\n turnCount++;\n onEvent?.({ type: \"turn_start\", turnIndex: turnCount });\n break;\n case \"turn_end\":\n onEvent?.({ type: \"turn_end\", turnIndex: turnCount });\n break;\n case \"tool_execution_start\":\n toolCallCount++;\n onEvent?.({\n type: \"tool_start\",\n toolName: event.toolName,\n toolArgs: formatToolArgs(event.toolName, event.args),\n });\n break;\n case \"tool_execution_end\":\n onEvent?.({\n type: \"tool_end\",\n toolName: event.toolName,\n isError: event.isError,\n result: formatToolResult(event.result),\n });\n break;\n case \"message_start\":\n onEvent?.({ type: \"thinking\" });\n break;\n case \"message_update\": {\n const msgEvent = (event as Record<string, unknown>).assistantMessageEvent as\n { type: string; delta?: string } | undefined;\n if (!msgEvent?.delta) break;\n if (msgEvent.type === \"text_delta\") {\n onEvent?.({ type: \"text_delta\", delta: msgEvent.delta });\n } else if (msgEvent.type === \"thinking_delta\") {\n onEvent?.({ type: \"thinking_delta\", delta: msgEvent.delta });\n }\n break;\n }\n }\n });\n\n const throwIfAgentErrored = (): void => {\n // pi-agent-core stores failed/aborted assistant turns in state.errorMessage.\n const agentError = session.state.errorMessage;\n if (agentError) {\n throw new Error(`LLM request failed: ${agentError}`);\n }\n };\n\n const recoverReviewFromAssistantText = (source: string): boolean => {\n const rawText = session.getLastAssistantText() ?? \"\";\n if (!rawText.trim()) return false;\n\n const parsedReview = parseReviewFromAssistantText(rawText);\n if (!parsedReview) return false;\n\n submittedReview = parsedReview;\n logger.warn(\n `Recovered structured review from assistant text after ${source}; model did not call submit_review`,\n );\n return true;\n };\n\n logger.info(\"Sending prompt to agent...\");\n await session.prompt(prompt);\n throwIfAgentErrored();\n\n if (!submittedReview) {\n recoverReviewFromAssistantText(\"initial agent run\");\n }\n\n for (\n let attempt = 1;\n !submittedReview && attempt <= SUBMIT_REVIEW_RECOVERY_ATTEMPTS;\n attempt++\n ) {\n logger.warn(\n `Agent ended without a valid submit_review (${summarizeLastAssistantMessage(session)}); ` +\n `requesting recovery ${attempt}/${SUBMIT_REVIEW_RECOVERY_ATTEMPTS}`,\n );\n await session.prompt(buildSubmitReviewRecoveryPrompt(attempt, SUBMIT_REVIEW_RECOVERY_ATTEMPTS));\n throwIfAgentErrored();\n recoverReviewFromAssistantText(`recovery attempt ${attempt}`);\n }\n\n if (!submittedReview) {\n const diagnostic = summarizeLastAssistantMessage(session);\n if (submitReviewCalls > 0) {\n throw new Error(\n `Agent called submit_review but did not provide a valid review payload after ` +\n `${SUBMIT_REVIEW_RECOVERY_ATTEMPTS} recovery attempt(s): ${diagnostic}`,\n );\n }\n throw new Error(\n `Agent did not call submit_review after ${SUBMIT_REVIEW_RECOVERY_ATTEMPTS} recovery attempt(s): ${diagnostic}`,\n );\n }\n\n const rawReview = submittedReview as ReviewOutput;\n if (submitReviewCalls > 1) {\n logger.warn(`Agent called submit_review ${submitReviewCalls} times; using the first valid submission`);\n }\n\n // Resolve each finding's line_range from its quoted snippet against the\n // checked-out file, correcting model line-number errors before posting.\n const { review, stats: locationStats } = resolveReviewLocations(rawReview, {\n workspacePath,\n diffText: embeddedDiff,\n });\n if (locationStats.corrected > 0 || locationStats.unmatched > 0) {\n logger.info(\n `Location resolution: ${locationStats.corrected} corrected, ${locationStats.confirmed} confirmed, ` +\n `${locationStats.unmatched} unmatched, ${locationStats.noSnippet} without snippet`,\n );\n }\n\n logger.info(\n `Captured ${review.findings.length} finding(s), verdict: ${review.overall_correctness}`,\n );\n\n const durationSeconds = (Date.now() - startTime) / 1000;\n logger.info(`Review complete (${review.findings.length} finding(s))`);\n\n // Aggregate usage from all assistant messages\n interface MsgUsage {\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n totalTokens: number;\n cost: { total: number };\n }\n interface AssistantMsg {\n role: string;\n usage?: MsgUsage;\n }\n\n const allMessages = session.messages as AssistantMsg[];\n\n let inputTokens = 0;\n let outputTokens = 0;\n let cacheReadTokens = 0;\n let cacheWriteTokens = 0;\n let totalTokens = 0;\n let cost = 0;\n\n for (const msg of allMessages) {\n if (msg.role === \"assistant\" && msg.usage) {\n inputTokens += msg.usage.input ?? 0;\n outputTokens += msg.usage.output ?? 0;\n cacheReadTokens += msg.usage.cacheRead ?? 0;\n cacheWriteTokens += msg.usage.cacheWrite ?? 0;\n totalTokens += msg.usage.totalTokens ?? 0;\n cost += msg.usage.cost?.total ?? 0;\n }\n }\n\n const metrics: ReviewMetrics = {\n inputTokens,\n outputTokens,\n cacheReadTokens,\n cacheWriteTokens,\n totalTokens,\n cost,\n turns: turnCount,\n toolCalls: toolCallCount,\n durationSeconds: Math.round(durationSeconds),\n };\n printMetrics(metrics);\n\n let metricsFooter: string | null = null;\n if (includeMetricsFooter) {\n metricsFooter = formatMetricsMarkdown(metrics);\n }\n\n return { review, metricsFooter, headSha, metrics, workspacePath };\n } finally {\n activeSession?.dispose();\n\n // Restore mutated env vars\n for (const [key, val] of Object.entries(envSnapshot)) {\n if (val === undefined) {\n delete process.env[key];\n } else {\n process.env[key] = val;\n }\n }\n\n if (cleanup && isTemporary) {\n logger.info(\"Cleaning up workspace...\");\n await cleanupWorkspace(workspacePath);\n }\n }\n}\n","import { execJson } from \"./utils/exec.js\";\nimport type { MrMetadata, NoteEntry } from \"./types.js\";\n\nexport class GitHubAPIError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"GitHubAPIError\";\n }\n}\n\nexport async function fetchGithubPrInfo(\n owner: string,\n repo: string,\n prNumber: number | string,\n): Promise<Record<string, unknown>> {\n const fields = [\n \"number\",\n \"title\",\n \"body\",\n \"author\",\n \"baseRefName\",\n \"headRefName\",\n \"baseRefOid\",\n \"headRefOid\",\n \"changedFiles\",\n \"labels\",\n \"comments\",\n \"state\",\n \"isDraft\",\n \"createdAt\",\n \"updatedAt\",\n \"mergeable\",\n \"url\",\n ];\n\n const repoFullPath = `${owner}/${repo}`;\n try {\n return await execJson<Record<string, unknown>>(\"gh\", [\n \"pr\",\n \"view\",\n String(prNumber),\n \"-R\",\n repoFullPath,\n \"--json\",\n fields.join(\",\"),\n ]);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitHubAPIError(msg);\n }\n}\n\nexport function normalizeGithubMetadata(\n raw: Record<string, unknown>,\n): MrMetadata {\n const author = (raw.author as Record<string, string>) ?? {};\n const labels = (raw.labels as Array<Record<string, string>>) ?? [];\n const comments = raw.comments as\n | Record<string, unknown>\n | Array<Record<string, unknown>>\n | undefined;\n\n return {\n title: raw.title as string | undefined,\n description: (raw.body as string) ?? \"\",\n source_branch: raw.headRefName as string | undefined,\n target_branch: raw.baseRefName as string | undefined,\n changes_count: raw.changedFiles as number | undefined,\n labels: labels.map((lbl) => ({ name: lbl.name ?? lbl.id })),\n author: {\n username: author.login ?? author.name,\n name: author.name,\n },\n Notes: githubCommentsToNotes(comments),\n };\n}\n\nfunction githubCommentsToNotes(\n comments:\n | Record<string, unknown>\n | Array<Record<string, unknown>>\n | undefined\n | null,\n): NoteEntry[] {\n if (!comments) return [];\n\n let nodes: Array<Record<string, unknown>>;\n if (Array.isArray(comments)) {\n nodes = comments;\n } else if (typeof comments === \"object\") {\n nodes =\n (comments.nodes as Array<Record<string, unknown>>) ??\n (comments.edges as Array<Record<string, unknown>>) ??\n [];\n // Handle GraphQL edge format\n if (\n nodes.length > 0 &&\n typeof nodes[0] === \"object\" &&\n \"node\" in nodes[0]\n ) {\n nodes = nodes.map(\n (edge) =>\n (edge.node as Record<string, unknown>) ?? {},\n );\n }\n } else {\n nodes = [];\n }\n\n return nodes.map((node) => {\n const author = (node.author as Record<string, string>) ?? {};\n return {\n body: (node.body as string) ?? \"\",\n author: {\n username: author.login ?? author.name,\n name: author.name,\n },\n created_at: node.createdAt as string | undefined,\n };\n });\n}\n","import { logger } from \"./utils/logger.js\";\nimport type { MrMetadata, NoteEntry } from \"./types.js\";\n\nexport interface GiteaPrCheckoutInfo {\n sourceBranch: string;\n targetBranch: string;\n sourceCloneUrl?: string;\n}\n\nexport class GiteaAPIError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"GiteaAPIError\";\n }\n}\n\nfunction normalizeBaseUrl(host?: string | null): string {\n const candidate =\n host ||\n process.env.GITEA_HOST ||\n process.env.FORGEJO_HOST;\n if (!candidate) {\n throw new GiteaAPIError(\n \"No Gitea/Forgejo host configured. Set GITEA_HOST or FORGEJO_HOST, \" +\n \"or provide a full PR URL that includes the hostname.\",\n );\n }\n const trimmed = candidate.trim();\n if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n return trimmed.replace(/\\/+$/, \"\");\n }\n return `https://${trimmed}`.replace(/\\/+$/, \"\");\n}\n\nfunction giteaToken(): string | null {\n return process.env.GITEA_TOKEN ?? process.env.FORGEJO_TOKEN ?? null;\n}\n\nfunction requireGiteaToken(): string {\n const token = giteaToken();\n if (!token) {\n throw new GiteaAPIError(\n \"No Gitea/Forgejo token found. Set GITEA_TOKEN or FORGEJO_TOKEN environment variable.\",\n );\n }\n return token;\n}\n\nasync function giteaFetch<T>(\n host: string | null | undefined,\n path: string,\n options?: { method?: string; body?: unknown },\n): Promise<T> {\n const baseUrl = normalizeBaseUrl(host);\n const url = `${baseUrl}/api/v1/${path}`;\n const token = giteaToken();\n\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n };\n if (token) {\n headers.Authorization = `token ${token}`;\n }\n\n const init: RequestInit = {\n method: options?.method ?? \"GET\",\n headers,\n };\n\n if (options?.body) {\n init.body = JSON.stringify(options.body);\n }\n\n const response = await fetch(url, init);\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n if (response.status === 401 || response.status === 403) {\n throw new GiteaAPIError(\n `Authentication failed (${response.status}): check GITEA_TOKEN/FORGEJO_TOKEN. ${text}`,\n );\n }\n if (response.status === 429) {\n throw new GiteaAPIError(`Rate limited by Gitea API (429). ${text}`);\n }\n throw new GiteaAPIError(\n `Gitea API error ${response.status} for ${options?.method ?? \"GET\"} ${path}: ${text}`,\n );\n }\n\n return (await response.json()) as T;\n}\n\n/**\n * Fetch pull request metadata from Gitea/Forgejo.\n */\nexport async function fetchGiteaPrInfo(\n owner: string,\n repo: string,\n prNumber: number | string,\n host?: string | null,\n options?: { includeComments?: boolean },\n): Promise<MrMetadata> {\n let prData: Record<string, unknown>;\n try {\n prData = await giteaFetch<Record<string, unknown>>(\n host,\n `repos/${owner}/${repo}/pulls/${prNumber}`,\n );\n } catch (err) {\n if (err instanceof GiteaAPIError) throw err;\n const msg = err instanceof Error ? err.message : String(err);\n throw new GiteaAPIError(`Failed to fetch PR #${prNumber}: ${msg}`);\n }\n\n const user = (prData.user as Record<string, string>) ?? {};\n const head = (prData.head as Record<string, unknown>) ?? {};\n const base = (prData.base as Record<string, unknown>) ?? {};\n const labels = (prData.labels as Array<Record<string, string>>) ?? [];\n\n const metadata: MrMetadata = {\n title: prData.title as string | undefined,\n description: (prData.body as string) ?? \"\",\n source_branch: head.ref as string | undefined,\n target_branch: base.ref as string | undefined,\n changes_count: prData.changed_files as number | undefined,\n labels: labels.map((lbl) => ({ name: lbl.name })),\n author: {\n username: user.login,\n name: user.full_name || user.login,\n },\n state: prData.state as string | undefined,\n };\n\n if (options?.includeComments) {\n try {\n metadata.Notes = await fetchGiteaPrComments(owner, repo, prNumber, host);\n } catch (err) {\n logger.warn(`Failed to fetch PR comments: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n return metadata;\n}\n\n/**\n * Fetch the PR branch metadata needed to checkout same-repo and fork PRs.\n */\nexport async function fetchGiteaPrCheckoutInfo(\n owner: string,\n repo: string,\n prNumber: number | string,\n host?: string | null,\n): Promise<GiteaPrCheckoutInfo> {\n const prData = await giteaFetch<Record<string, unknown>>(\n host,\n `repos/${owner}/${repo}/pulls/${prNumber}`,\n );\n const head = (prData.head as Record<string, unknown>) ?? {};\n const base = (prData.base as Record<string, unknown>) ?? {};\n const headRepo = (head.repo as Record<string, unknown> | null) ?? null;\n const sourceBranch = head.ref as string | undefined;\n\n if (!sourceBranch) {\n throw new GiteaAPIError(`Could not determine source branch for PR #${prNumber}`);\n }\n\n return {\n sourceBranch,\n targetBranch: (base.ref as string | undefined) ?? \"main\",\n sourceCloneUrl: headRepo?.clone_url as string | undefined,\n };\n}\n\n/**\n * Fetch comments on a Gitea/Forgejo pull request.\n * PRs are a superset of issues in Gitea, so comments use the issues endpoint.\n */\nexport async function fetchGiteaPrComments(\n owner: string,\n repo: string,\n prNumber: number | string,\n host?: string | null,\n): Promise<NoteEntry[]> {\n const comments: Array<Record<string, unknown>> = [];\n const pageSize = 100;\n\n for (let page = 1; ; page++) {\n const batch = await giteaFetch<Array<Record<string, unknown>>>(\n host,\n `repos/${owner}/${repo}/issues/${prNumber}/comments?page=${page}&limit=${pageSize}`,\n );\n comments.push(...batch);\n if (batch.length < pageSize) break;\n }\n\n return comments.map((c) => {\n const user = (c.user as Record<string, string>) ?? {};\n return {\n body: (c.body as string) ?? \"\",\n author: {\n username: user.login,\n name: user.full_name || user.login,\n },\n created_at: c.created_at as string | undefined,\n system: false,\n };\n });\n}\n\n/**\n * Post a comment on a Gitea/Forgejo pull request.\n */\nexport async function postGiteaPrComment(\n owner: string,\n repo: string,\n prNumber: number | string,\n body: string,\n host?: string | null,\n): Promise<void> {\n // Posting requires authentication\n requireGiteaToken();\n\n try {\n await giteaFetch<unknown>(\n host,\n `repos/${owner}/${repo}/issues/${prNumber}/comments`,\n { method: \"POST\", body: { body } },\n );\n } catch (err) {\n if (err instanceof GiteaAPIError) throw err;\n const msg = err instanceof Error ? err.message : String(err);\n throw new GiteaAPIError(`Failed to post comment to PR #${prNumber}: ${msg}`);\n }\n}\n","import { mkdtemp, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { exec, execJson } from \"./utils/exec.js\";\nimport { logger } from \"./utils/logger.js\";\nimport { fetchGitlabMrInfo } from \"./gitlab.js\";\nimport { fetchGiteaPrCheckoutInfo } from \"./gitea.js\";\nimport type { GiteaPrCheckoutInfo } from \"./gitea.js\";\nimport type { MrMetadata, Platform } from \"./types.js\";\n\nexport class WorkspaceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WorkspaceError\";\n }\n}\n\nexport interface WorkspaceResult {\n workspace: string;\n targetBranch: string;\n diffBaseSha: string | null;\n isTemporary: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// CI detection\n// ---------------------------------------------------------------------------\n\ninterface CiWorkspace {\n path: string | null;\n targetBranch: string | null;\n diffBaseSha: string | null;\n}\n\nfunction envOrNull(name: string): string | null {\n const value = process.env[name]?.trim();\n return value ? value : null;\n}\n\nasync function detectCiWorkspace(owner: string, repo: string): Promise<CiWorkspace> {\n const expected = `${owner}/${repo}`;\n\n // GitLab CI\n if (process.env.GITLAB_CI === \"true\") {\n const projectDir = envOrNull(\"CI_PROJECT_DIR\");\n const projectPath = envOrNull(\"CI_PROJECT_PATH\");\n const targetBranch = envOrNull(\"CI_MERGE_REQUEST_TARGET_BRANCH_NAME\");\n const diffBaseSha = envOrNull(\"CI_MERGE_REQUEST_DIFF_BASE_SHA\");\n\n if (projectDir && projectPath && (projectPath === expected || projectPath.endsWith(`/${expected}`))) {\n if (await isSameRepo(projectDir, owner, repo)) {\n logger.info(`Detected GitLab CI environment (target: ${targetBranch ?? \"unknown\"})`);\n return { path: projectDir, targetBranch, diffBaseSha };\n }\n logger.warn(\n `Detected GitLab CI for ${projectPath}, but ${projectDir} is not a git checkout of ${expected}; falling back to clone`,\n );\n }\n }\n\n // Gitea Actions / Forgejo Actions\n // Must check BEFORE GITHUB_ACTIONS since Gitea/Forgejo Actions sets GITHUB_ACTIONS=true for compat\n if (process.env.GITEA_ACTIONS === \"true\" || process.env.FORGEJO_ACTIONS === \"true\") {\n const workspaceDir = envOrNull(\"GITHUB_WORKSPACE\");\n const repository = envOrNull(\"GITHUB_REPOSITORY\");\n const baseRef = envOrNull(\"GITHUB_BASE_REF\");\n\n if (workspaceDir && repository === expected) {\n const ciType = process.env.FORGEJO_ACTIONS ? \"Forgejo\" : \"Gitea\";\n if (await isSameRepo(workspaceDir, owner, repo)) {\n logger.info(`Detected ${ciType} Actions environment (base: ${baseRef ?? \"unknown\"})`);\n return { path: workspaceDir, targetBranch: baseRef, diffBaseSha: null };\n }\n logger.warn(\n `Detected ${ciType} Actions for ${repository}, but ${workspaceDir} is not a git checkout of ${expected}; falling back to clone`,\n );\n }\n }\n\n // GitHub Actions\n if (process.env.GITHUB_ACTIONS === \"true\") {\n const workspaceDir = envOrNull(\"GITHUB_WORKSPACE\");\n const repository = envOrNull(\"GITHUB_REPOSITORY\");\n const baseRef = envOrNull(\"GITHUB_BASE_REF\");\n\n if (workspaceDir && repository === expected) {\n if (await isSameRepo(workspaceDir, owner, repo)) {\n logger.info(`Detected GitHub Actions environment (base: ${baseRef ?? \"unknown\"})`);\n return { path: workspaceDir, targetBranch: baseRef, diffBaseSha: null };\n }\n logger.warn(\n `Detected GitHub Actions for ${repository}, but ${workspaceDir} is not a git checkout of ${expected}; falling back to clone`,\n );\n }\n }\n\n return { path: null, targetBranch: null, diffBaseSha: null };\n}\n\n// ---------------------------------------------------------------------------\n// Repo identity check\n// ---------------------------------------------------------------------------\n\nfunction normalizeGitRemotePath(remoteUrl: string): string {\n const trimmed = remoteUrl.trim().replace(/\\.git$/, \"\");\n\n try {\n const url = new URL(trimmed);\n return url.pathname.replace(/^\\/+/, \"\").replace(/\\.git$/, \"\");\n } catch {\n // Not a URL; try common SSH/scp-like forms below.\n }\n\n const scpLikeMatch = trimmed.match(/^[^@\\s]+@[^:\\s]+:(.+)$/);\n if (scpLikeMatch) {\n return scpLikeMatch[1].replace(/^\\/+/, \"\").replace(/\\.git$/, \"\");\n }\n\n return trimmed.replace(/^\\/+/, \"\").replace(/\\.git$/, \"\");\n}\n\n/**\n * Check if workspace is already cloned from the expected owner/repo.\n * Parses the remote URL to compare exact owner/repo, avoiding substring false positives.\n */\nasync function isSameRepo(\n workspace: string,\n owner: string,\n repo: string,\n): Promise<boolean> {\n try {\n const { stdout } = await exec(\"git\", [\"remote\", \"get-url\", \"origin\"], { cwd: workspace });\n const remoteUrl = stdout.trim();\n const expectedPath = `${owner}/${repo}`.replace(/\\.git$/, \"\");\n const remotePath = normalizeGitRemotePath(remoteUrl);\n return remotePath === expectedPath || remotePath.toLowerCase() === expectedPath.toLowerCase();\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// GitHub helpers\n// ---------------------------------------------------------------------------\n\nasync function getGithubBaseBranch(workspace: string, prNumber: string): Promise<string> {\n try {\n const prInfo = await execJson<Record<string, string>>(\n \"gh\",\n [\"pr\", \"view\", prNumber, \"--json\", \"headRefName,baseRefName\"],\n { cwd: workspace },\n );\n const baseBranch = prInfo.baseRefName ?? \"main\";\n logger.info(`Base branch: ${baseBranch}`);\n return baseBranch;\n } catch {\n logger.warn(\"Could not fetch PR metadata for base branch detection\");\n return \"main\";\n }\n}\n\nasync function fetchAndCheckoutGithubPr(\n workspace: string,\n prNumber: string,\n): Promise<string> {\n logger.info(`Fetching and checking out PR #${prNumber} in existing workspace`);\n await exec(\"git\", [\"fetch\", \"origin\"], { cwd: workspace });\n\n try {\n await exec(\"gh\", [\"pr\", \"checkout\", prNumber], { cwd: workspace });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to checkout PR #${prNumber}: ${msg}`);\n }\n\n return getGithubBaseBranch(workspace, prNumber);\n}\n\nasync function cloneAndCheckoutGithubPr(\n workspace: string,\n owner: string,\n repo: string,\n prNumber: string,\n): Promise<string> {\n logger.info(`Setting up GitHub workspace for ${owner}/${repo}/pull/${prNumber}`);\n\n try {\n await exec(\"gh\", [\"version\"]);\n } catch {\n throw new WorkspaceError(\"GitHub CLI (gh) is not available. Install it: https://cli.github.com\");\n }\n\n logger.info(`Cloning repository ${owner}/${repo}...`);\n try {\n await exec(\"gh\", [\"repo\", \"clone\", `${owner}/${repo}`, workspace]);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to clone repository ${owner}/${repo}: ${msg}`);\n }\n\n logger.info(`Checking out PR #${prNumber}...`);\n try {\n await exec(\"gh\", [\"pr\", \"checkout\", prNumber], { cwd: workspace });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to checkout PR #${prNumber}: ${msg}`);\n }\n\n return getGithubBaseBranch(workspace, prNumber);\n}\n\n// ---------------------------------------------------------------------------\n// GitLab helpers\n// ---------------------------------------------------------------------------\n\nasync function getGitlabMrBranches(\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<{ sourceBranch: string; targetBranch: string }> {\n const gitlabHost = host || process.env.GITLAB_HOST || \"gitlab.com\";\n let mrInfo: MrMetadata;\n try {\n mrInfo = await fetchGitlabMrInfo(owner, repo, Number(prNumber), gitlabHost);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to fetch MR info for !${prNumber}: ${msg}`);\n }\n\n const sourceBranch = mrInfo.source_branch;\n if (!sourceBranch) {\n throw new WorkspaceError(`Could not determine source branch for MR !${prNumber}`);\n }\n\n return { sourceBranch, targetBranch: mrInfo.target_branch ?? \"main\" };\n}\n\nasync function checkoutGitlabBranch(workspace: string, sourceBranch: string): Promise<void> {\n try {\n await exec(\"git\", [\"checkout\", \"-b\", sourceBranch, `origin/${sourceBranch}`], {\n cwd: workspace,\n });\n } catch {\n try {\n await exec(\"git\", [\"checkout\", sourceBranch], { cwd: workspace });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to checkout MR branch '${sourceBranch}': ${msg}`);\n }\n }\n}\n\nasync function fetchAndCheckoutGitlabMr(\n workspace: string,\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<string> {\n logger.info(`Fetching and checking out MR !${prNumber} in existing workspace`);\n await exec(\"git\", [\"fetch\", \"origin\"], { cwd: workspace });\n\n const { sourceBranch, targetBranch } = await getGitlabMrBranches(owner, repo, prNumber, host);\n logger.info(`Source branch: ${sourceBranch}, Target branch: ${targetBranch}`);\n await checkoutGitlabBranch(workspace, sourceBranch);\n\n return targetBranch;\n}\n\nasync function cloneAndCheckoutGitlabMr(\n workspace: string,\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<string> {\n const gitlabHost = host || process.env.GITLAB_HOST || \"gitlab.com\";\n logger.info(`Setting up GitLab workspace for ${owner}/${repo}/merge_requests/${prNumber}`);\n\n try {\n await exec(\"glab\", [\"version\"]);\n } catch {\n throw new WorkspaceError(\n \"GitLab CLI (glab) is not available. Install it: https://gitlab.com/gitlab-org/cli\",\n );\n }\n\n const cloneUrl = `https://${gitlabHost}/${owner}/${repo}.git`;\n logger.info(`Cloning from ${cloneUrl}...`);\n try {\n await exec(\"git\", [\"clone\", cloneUrl, workspace]);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"Permission denied\") || msg.includes(\"publickey\")) {\n throw new WorkspaceError(\n `Failed to clone ${owner}/${repo}: SSH authentication failed. ` +\n `Ensure your SSH key is available (ssh-add) or configure a GITLAB_TOKEN ` +\n `and use HTTPS: git config --global url.\"https://oauth2:$GITLAB_TOKEN@${gitlabHost}/\".insteadOf \"git@${gitlabHost}:\"`,\n );\n }\n throw new WorkspaceError(`Failed to clone ${owner}/${repo}: ${msg}`);\n }\n\n const { sourceBranch, targetBranch } = await getGitlabMrBranches(owner, repo, prNumber, host);\n logger.info(`Source branch: ${sourceBranch}, Target branch: ${targetBranch}`);\n await checkoutGitlabBranch(workspace, sourceBranch);\n\n return targetBranch;\n}\n\n// ---------------------------------------------------------------------------\n// Gitea helpers\n// ---------------------------------------------------------------------------\n\nfunction normalizeGiteaBaseUrl(host?: string): string {\n const giteaHost = host || process.env.GITEA_HOST || process.env.FORGEJO_HOST;\n if (!giteaHost) {\n throw new WorkspaceError(\"No Gitea/Forgejo host configured. Set GITEA_HOST or FORGEJO_HOST.\");\n }\n const trimmed = giteaHost.trim().replace(/\\/+$/, \"\");\n if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n return trimmed;\n }\n return `https://${trimmed}`;\n}\n\nfunction giteaGitArgs(args: string[]): string[] {\n const token = process.env.GITEA_TOKEN || process.env.FORGEJO_TOKEN;\n return token ? [\"-c\", `http.extraHeader=Authorization: token ${token}`, ...args] : args;\n}\n\nasync function getGiteaPrCheckoutInfo(\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<GiteaPrCheckoutInfo> {\n try {\n return await fetchGiteaPrCheckoutInfo(owner, repo, Number(prNumber), host);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to fetch PR info for #${prNumber}: ${msg}`);\n }\n}\n\nasync function checkoutGiteaBranch(\n workspace: string,\n prNumber: string,\n checkoutInfo: GiteaPrCheckoutInfo,\n): Promise<void> {\n const { sourceBranch, sourceCloneUrl } = checkoutInfo;\n const localBranch = `hodor-pr-${prNumber}`;\n\n if (sourceCloneUrl) {\n try {\n await exec(\"git\", giteaGitArgs([\"fetch\", sourceCloneUrl, sourceBranch]), { cwd: workspace });\n await exec(\"git\", [\"checkout\", \"-B\", localBranch, \"FETCH_HEAD\"], { cwd: workspace });\n return;\n } catch (err) {\n logger.warn(`Failed to fetch Gitea PR branch from source repo, falling back to origin: ${err}`);\n }\n }\n\n try {\n await exec(\"git\", [\"checkout\", \"-B\", localBranch, `origin/${sourceBranch}`], {\n cwd: workspace,\n });\n } catch {\n try {\n await exec(\"git\", [\"checkout\", sourceBranch], { cwd: workspace });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to checkout PR branch '${sourceBranch}': ${msg}`);\n }\n }\n}\n\nasync function fetchAndCheckoutGiteaPr(\n workspace: string,\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<string> {\n logger.info(`Fetching and checking out PR #${prNumber} in existing workspace`);\n await exec(\"git\", giteaGitArgs([\"fetch\", \"origin\"]), { cwd: workspace });\n\n const checkoutInfo = await getGiteaPrCheckoutInfo(owner, repo, prNumber, host);\n logger.info(`Source branch: ${checkoutInfo.sourceBranch}, Target branch: ${checkoutInfo.targetBranch}`);\n await checkoutGiteaBranch(workspace, prNumber, checkoutInfo);\n\n return checkoutInfo.targetBranch;\n}\n\nasync function cloneAndCheckoutGiteaPr(\n workspace: string,\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<string> {\n logger.info(`Setting up Gitea workspace for ${owner}/${repo}/pulls/${prNumber}`);\n\n const cloneUrl = `${normalizeGiteaBaseUrl(host)}/${owner}/${repo}.git`;\n logger.info(`Cloning from ${cloneUrl}...`);\n\n try {\n await exec(\"git\", giteaGitArgs([\"clone\", cloneUrl, workspace]));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to clone ${owner}/${repo}: ${msg}`);\n }\n\n const checkoutInfo = await getGiteaPrCheckoutInfo(owner, repo, prNumber, host);\n logger.info(`Source branch: ${checkoutInfo.sourceBranch}, Target branch: ${checkoutInfo.targetBranch}`);\n await checkoutGiteaBranch(workspace, prNumber, checkoutInfo);\n\n return checkoutInfo.targetBranch;\n}\n\n// ---------------------------------------------------------------------------\n// Main entry point\n// ---------------------------------------------------------------------------\n\nexport async function setupWorkspace(opts: {\n platform: Platform;\n owner: string;\n repo: string;\n prNumber: string;\n host?: string;\n workingDir?: string;\n reuse?: boolean;\n}): Promise<WorkspaceResult> {\n const { platform, owner, repo, prNumber, host, workingDir, reuse = true } = opts;\n\n try {\n const ci = await detectCiWorkspace(owner, repo);\n let detectedTargetBranch = ci.targetBranch;\n const detectedDiffBaseSha = ci.diffBaseSha;\n\n let workspace: string;\n let isTemporary = false;\n\n if (ci.path) {\n workspace = ci.path;\n if (platform === \"github\" && !detectedTargetBranch) {\n detectedTargetBranch = await getGithubBaseBranch(workspace, prNumber);\n }\n } else if (!workingDir) {\n workspace = await mkdtemp(join(tmpdir(), \"hodor-review-\"));\n isTemporary = true;\n logger.info(`Created temporary workspace: ${workspace}`);\n } else {\n workspace = workingDir;\n const { mkdir } = await import(\"node:fs/promises\");\n await mkdir(workspace, { recursive: true });\n\n if (reuse && (await isSameRepo(workspace, owner, repo))) {\n logger.info(`Reusing existing workspace: ${workspace}`);\n // Repo already cloned — just fetch and checkout the PR/MR branch\n if (platform === \"github\") {\n const tb = await fetchAndCheckoutGithubPr(workspace, prNumber);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n } else if (platform === \"gitlab\") {\n const tb = await fetchAndCheckoutGitlabMr(workspace, owner, repo, prNumber, host);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n } else if (platform === \"gitea\") {\n const tb = await fetchAndCheckoutGiteaPr(workspace, owner, repo, prNumber, host);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n }\n const finalTargetBranch = detectedTargetBranch ?? \"main\";\n logger.info(\n `Workspace ready at: ${workspace} (target: ${finalTargetBranch}, ` +\n `diff_base_sha: ${detectedDiffBaseSha?.slice(0, 8) ?? \"N/A\"})`,\n );\n return { workspace, targetBranch: finalTargetBranch, diffBaseSha: detectedDiffBaseSha, isTemporary: false };\n }\n }\n\n if (!ci.path) {\n if (platform === \"github\") {\n const tb = await cloneAndCheckoutGithubPr(workspace, owner, repo, prNumber);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n } else if (platform === \"gitlab\") {\n const tb = await cloneAndCheckoutGitlabMr(workspace, owner, repo, prNumber, host);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n } else if (platform === \"gitea\") {\n const tb = await cloneAndCheckoutGiteaPr(workspace, owner, repo, prNumber, host);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n } else {\n throw new WorkspaceError(`Unsupported platform: ${platform}`);\n }\n }\n\n const finalTargetBranch = detectedTargetBranch ?? \"main\";\n logger.info(\n `Workspace ready at: ${workspace} (target: ${finalTargetBranch}, ` +\n `diff_base_sha: ${detectedDiffBaseSha?.slice(0, 8) ?? \"N/A\"})`,\n );\n return { workspace, targetBranch: finalTargetBranch, diffBaseSha: detectedDiffBaseSha, isTemporary };\n } catch (err) {\n if (err instanceof WorkspaceError) throw err;\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to setup workspace: ${msg}`);\n }\n}\n\nexport async function cleanupWorkspace(workspace: string): Promise<void> {\n try {\n await rm(workspace, { recursive: true, force: true });\n logger.info(`Cleaned up workspace: ${workspace}`);\n } catch (err) {\n logger.warn(`Failed to cleanup workspace ${workspace}: ${err}`);\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve, sep } from \"node:path\";\nimport { logger } from \"./utils/logger.js\";\nimport type { ReviewOutput } from \"./types.js\";\n\n/**\n * Snippet-based line resolution.\n *\n * The review agent emits line numbers (`code_location.line_range`) by counting\n * lines in a raw diff — the thing LLMs are worst at. Wrong numbers silently\n * drop GitLab inline comments and misplace `suggestion:` blocks (see\n * docs/SNIPPET_LINE_RESOLUTION.md).\n *\n * Instead, the agent also quotes the verbatim code it is commenting on\n * (`existing_code`). Here we locate that snippet in the on-disk file (the PR\n * branch is already checked out) and correct the line range to where the code\n * actually lives. When the snippet is absent or cannot be matched we keep the\n * model's range unchanged — the resolver is pure upside, never a regression.\n */\n\nconst MAX_RESOLVE_BYTES = 2 * 1024 * 1024; // skip resolution for files > 2MB\n\nexport interface ResolveStats {\n total: number;\n noSnippet: number; // finding had no existing_code\n confirmed: number; // snippet matched at the model's range (no change)\n corrected: number; // snippet matched at a different range (range fixed)\n unmatched: number; // had a snippet but no match — kept model's range\n}\n\ninterface IndexedLine {\n lineNum: number; // 1-indexed line number in the original file\n content: string; // normalized content\n}\n\n/**\n * Normalize a line for matching: strip CR, trim outer whitespace, strip a\n * single leading diff marker (`+`/`-`) the model may have accidentally copied,\n * then trim again. Mirrors open-code-review's `normalizeLine`.\n */\nfunction normalizeLine(line: string): string {\n let s = line.replace(/\\r$/, \"\").trim();\n if (s.startsWith(\"+\") || s.startsWith(\"-\")) {\n s = s.slice(1).trim();\n }\n return s;\n}\n\n/** Split text into normalized, non-blank lines (blanks are dropped). */\nfunction normalizeSnippet(code: string): string[] {\n return code\n .split(\"\\n\")\n .map(normalizeLine)\n .filter((l) => l.length > 0);\n}\n\n/**\n * Index a file's non-blank lines with their original 1-indexed line numbers.\n * Dropping blank lines on both sides lets a snippet match across internal\n * blank lines — a small improvement over open-code-review, which required the\n * snippet to be blank-line-free.\n */\nfunction indexFile(content: string): IndexedLine[] {\n const result: IndexedLine[] = [];\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const normalized = normalizeLine(lines[i]);\n if (normalized.length > 0) {\n result.push({ lineNum: i + 1, content: normalized });\n }\n }\n return result;\n}\n\n/** True when `filePath` resolves to a location inside `root` (blocks path traversal). */\nfunction isWithinWorkspace(root: string, filePath: string): boolean {\n const target = resolve(filePath);\n return target === root || target.startsWith(root + sep);\n}\n\n/** All [start,end] ranges (1-indexed, inclusive) where `target` matches a consecutive run. */\nfunction findMatches(\n fileLines: IndexedLine[],\n target: string[],\n): Array<{ start: number; end: number }> {\n const matches: Array<{ start: number; end: number }> = [];\n if (target.length === 0 || fileLines.length < target.length) return matches;\n\n for (let i = 0; i <= fileLines.length - target.length; i++) {\n let ok = true;\n for (let j = 0; j < target.length; j++) {\n if (fileLines[i + j].content !== target[j]) {\n ok = false;\n break;\n }\n }\n if (ok) {\n matches.push({ start: fileLines[i].lineNum, end: fileLines[i + target.length - 1].lineNum });\n }\n }\n return matches;\n}\n\nexport interface ResolveInput {\n existingCode?: string;\n fileContent: string;\n modelRange: { start: number; end: number };\n /** New-side line numbers touched by the diff, for disambiguation. */\n changedLines?: Set<number>;\n}\n\nexport interface ResolveResult {\n start: number;\n end: number;\n /** \"no-snippet\" | \"unmatched\" => kept model range; \"confirmed\"/\"corrected\" => snippet matched. */\n status: \"no-snippet\" | \"unmatched\" | \"confirmed\" | \"corrected\";\n}\n\n/**\n * Pure core: resolve a single finding's line range from its snippet. No I/O.\n */\nexport function resolveLineRange(input: ResolveInput): ResolveResult {\n const { existingCode, fileContent, modelRange, changedLines } = input;\n const keep = (status: ResolveResult[\"status\"]): ResolveResult => ({\n start: modelRange.start,\n end: modelRange.end,\n status,\n });\n\n const target = existingCode ? normalizeSnippet(existingCode) : [];\n if (target.length === 0) return keep(\"no-snippet\");\n\n const matches = findMatches(indexFile(fileContent), target);\n if (matches.length === 0) return keep(\"unmatched\");\n\n let chosen: { start: number; end: number };\n if (matches.length === 1) {\n chosen = matches[0];\n } else {\n // Prefer a match that overlaps the diff; otherwise the one nearest the\n // model's emitted start line.\n const overlapping =\n changedLines && changedLines.size > 0\n ? matches.filter((m) => {\n for (let l = m.start; l <= m.end; l++) {\n if (changedLines.has(l)) return true;\n }\n return false;\n })\n : [];\n const pool = overlapping.length > 0 ? overlapping : matches;\n chosen = pool.reduce((best, m) =>\n Math.abs(m.start - modelRange.start) < Math.abs(best.start - modelRange.start) ? m : best,\n );\n }\n\n const status = chosen.start === modelRange.start && chosen.end === modelRange.end ? \"confirmed\" : \"corrected\";\n return { start: chosen.start, end: chosen.end, status };\n}\n\n/**\n * Parse a unified diff into a map of file path -> set of new-side line numbers\n * within its hunks (added + context lines — both are valid comment anchors).\n * Used only to disambiguate multiple snippet matches.\n */\nexport function parseChangedLines(diffText: string): Map<string, Set<number>> {\n const byFile = new Map<string, Set<number>>();\n if (!diffText) return byFile;\n\n let current: Set<number> | null = null;\n let newLine = 0;\n const hunkRe = /^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/;\n\n for (const line of diffText.split(\"\\n\")) {\n if (line.startsWith(\"+++ \")) {\n // \"+++ b/path/to/file\" (or \"+++ /dev/null\")\n let path = line.slice(4).trim();\n if (path.startsWith(\"b/\")) path = path.slice(2);\n if (path === \"/dev/null\") {\n current = null;\n } else {\n current = byFile.get(path) ?? new Set<number>();\n byFile.set(path, current);\n }\n continue;\n }\n const m = line.match(hunkRe);\n if (m) {\n newLine = parseInt(m[1], 10);\n continue;\n }\n if (!current) continue;\n if (line.startsWith(\"diff --git \") || line.startsWith(\"--- \")) continue;\n if (line.startsWith(\"+\")) {\n current.add(newLine);\n newLine++;\n } else if (line.startsWith(\"-\")) {\n // deleted line — does not advance the new-side counter\n } else if (line.startsWith(\"\\\\\")) {\n // \"\\"\n } else {\n // context line — part of the hunk's new side, usable as a comment anchor\n current.add(newLine);\n newLine++;\n }\n }\n return byFile;\n}\n\n/**\n * Resolve line ranges for every finding in a review against the checked-out\n * workspace. Returns a new review with corrected ranges plus resolution stats.\n * Findings are never dropped — on any miss or read error the model's range is kept.\n */\nexport function resolveReviewLocations(\n review: ReviewOutput,\n opts: { workspacePath?: string | null; diffText?: string | null },\n): { review: ReviewOutput; stats: ResolveStats } {\n const stats: ResolveStats = { total: 0, noSnippet: 0, confirmed: 0, corrected: 0, unmatched: 0 };\n if (review.findings.length === 0) return { review, stats };\n\n const changedByFile = opts.diffText ? parseChangedLines(opts.diffText) : new Map<string, Set<number>>();\n const fileCache = new Map<string, string | null>();\n const workspaceRoot = opts.workspacePath ? resolve(opts.workspacePath) : null;\n\n const readFile = (path: string): string | null => {\n if (fileCache.has(path)) return fileCache.get(path) ?? null;\n let content: string | null = null;\n try {\n const buf = readFileSync(path);\n if (buf.byteLength <= MAX_RESOLVE_BYTES) content = buf.toString(\"utf-8\");\n } catch {\n content = null;\n }\n fileCache.set(path, content);\n return content;\n };\n\n const findings = review.findings.map((finding) => {\n stats.total++;\n const { existing_code: existingCode, code_location: loc } = finding;\n if (!existingCode) {\n stats.noSnippet++;\n return finding;\n }\n\n // Only resolve against files inside the review workspace. The model supplies\n // absolute_file_path, so a malformed or injected payload must not be able to\n // read arbitrary files on disk.\n if (workspaceRoot && !isWithinWorkspace(workspaceRoot, loc.absolute_file_path)) {\n stats.unmatched++;\n logger.warn(\n `Location resolution: ${loc.absolute_file_path} is outside the workspace for \"${finding.title}\"; keeping model range`,\n );\n return finding;\n }\n\n const fileContent = readFile(loc.absolute_file_path);\n if (fileContent === null) {\n stats.unmatched++;\n logger.warn(`Location resolution: could not read ${loc.absolute_file_path} for \"${finding.title}\"`);\n return finding;\n }\n\n // Match the diff's repo-relative path against the file's tail (paths in the\n // diff are repo-relative; absolute_file_path is workspace-absolute).\n let changedLines: Set<number> | undefined;\n for (const [relPath, lines] of changedByFile) {\n if (loc.absolute_file_path.endsWith(`/${relPath}`) || loc.absolute_file_path === relPath) {\n changedLines = lines;\n break;\n }\n }\n\n const result = resolveLineRange({\n existingCode,\n fileContent,\n modelRange: loc.line_range,\n changedLines,\n });\n\n if (result.status === \"confirmed\") stats.confirmed++;\n else if (result.status === \"corrected\") stats.corrected++;\n else stats.unmatched++;\n\n if (result.status === \"corrected\") {\n logger.info(\n `Location resolution: corrected \"${finding.title}\" ${loc.line_range.start}-${loc.line_range.end} -> ${result.start}-${result.end}`,\n );\n return {\n ...finding,\n code_location: { ...loc, line_range: { start: result.start, end: result.end } },\n };\n }\n if (result.status === \"unmatched\") {\n logger.warn(\n `Location resolution: snippet not found for \"${finding.title}\" in ${loc.absolute_file_path}; keeping model range ${loc.line_range.start}-${loc.line_range.end}`,\n );\n }\n return finding;\n });\n\n return { review: { ...review, findings }, stats };\n}\n","export const REVIEW_SYSTEM_PROMPT = `You are a code review agent. You analyze pull request diffs to find production bugs.\n\n<ROLE>\n* You are in READ-ONLY mode. Do NOT modify any files, create files, commit, or install dependencies.\n* Your only job is to analyze the diff, identify bugs, and produce a review.\n* Submit the final review via the \\`submit_review\\` tool. Do NOT output the final review as normal assistant text.\n* Be proportional: scale your analysis depth to the diff size. A small, single-file diff needs only a few iterations; a large multi-file refactor warrants deeper investigation.\n* Do NOT write to PLAN.md or AGENTS.md.\n* Do NOT run package managers (npm install, go mod download, pip install, etc.).\n* Follow the instructions in the user prompt exactly as given.\n</ROLE>\n\n<EFFICIENCY>\n* Combine multiple bash commands where possible (e.g. \\`cmd1 && cmd2\\`).\n* Use the grep and find tools for code search — do not shell out to grep/find.\n* Prefer \\`git diff\\` to see changes for specific files. Only use read when you need surrounding context that the diff alone cannot provide.\n* Do not use cat/head/tail to read files.\n* Keep reasoning proportional to the task. A small diff does not need extensive deliberation.\n</EFFICIENCY>`;\n"],"mappings":"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","names":["join","finalTargetBranch","readFileSync","resolve","resolve","status","readFileSync","formatToolArgs","formatToolResult","join"]}
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  postReviewStructured,
8
8
  pushMetrics,
9
9
  reviewPr
10
- } from "./chunk-JSLG7EOX.js";
10
+ } from "./chunk-MRMLGIXO.js";
11
11
  import {
12
12
  renderMarkdown,
13
13
  setLogLevel
package/dist/index.d.ts CHANGED
@@ -63,6 +63,9 @@ interface ReviewFinding {
63
63
  end: number;
64
64
  };
65
65
  };
66
+ /** Verbatim copy of the source lines the finding refers to, used to resolve
67
+ * line_range against the on-disk file. Optional; falls back to line_range. */
68
+ existing_code?: string;
66
69
  suggestion?: string;
67
70
  }
68
71
  interface ReviewOutput {
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  pushMetrics,
12
12
  reviewPr,
13
13
  validateReviewOutput
14
- } from "./chunk-JSLG7EOX.js";
14
+ } from "./chunk-MRMLGIXO.js";
15
15
  import {
16
16
  renderMarkdown
17
17
  } from "./chunk-DVJVQTVW.js";
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.6.1",
6
+ "version": "0.6.2-rc.3",
7
7
  "description": "AI-powered code review agent that finds bugs, security issues, and logic errors in pull requests",
8
8
  "type": "module",
9
9
  "main": "dist/index.js",
@@ -45,8 +45,8 @@
45
45
  "dependencies": {
46
46
  "@sinclair/typebox": "^0.34.49",
47
47
  "@anthropic-ai/sdk": "^0.95.2",
48
- "@earendil-works/pi-ai": "^0.74.0",
49
- "@earendil-works/pi-coding-agent": "^0.74.0",
48
+ "@earendil-works/pi-ai": "^0.79.10",
49
+ "@earendil-works/pi-coding-agent": "^0.79.10",
50
50
  "chalk": "^5.6.2",
51
51
  "commander": "^14.0.3",
52
52
  "dotenv": "^17.4.2"
@@ -134,6 +134,7 @@ When you are done, call `submit_review` exactly once with the final structured r
134
134
  "absolute_file_path": "<absolute file path>",
135
135
  "line_range": {"start": <int>, "end": <int>}
136
136
  },
137
+ "existing_code": "<verbatim copy of the exact current source lines this finding covers>",
137
138
  "suggestion": "<optional: exact replacement code for the flagged line range>"
138
139
  }
139
140
  ],
@@ -151,6 +152,7 @@ When you are done, call `submit_review` exactly once with the final structured r
151
152
  * If `findings` is non-empty, `overall_correctness` must be `"patch is incorrect"`.
152
153
  * If `findings` is empty, `overall_correctness` must be `"patch is correct"`.
153
154
  * Every finding must include `title`, `body`, `priority`, and `code_location`.
155
+ * Always include `existing_code`: copy the exact current source lines the finding refers to, verbatim and contiguous, from the file or the diff's context/added lines. Do NOT include diff `+`/`-` markers, line numbers, or markdown fences. These lines are matched mechanically against the file to pinpoint the comment location, so they must be an exact quote covering the same lines as `line_range`.
154
156
  * Use absolute file paths (for example, `/workspace/path/to/file.py`) not relative paths.
155
157
  * The title must start with a priority tag: `[P0]`, `[P1]`, `[P2]`, or `[P3]`.
156
158
  * `overall_correctness` must be exactly `"patch is correct"` or `"patch is incorrect"`.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/prompt.ts","../src/model.ts","../src/metrics.ts","../src/review.ts","../src/agent.ts","../src/github.ts","../src/gitea.ts","../src/workspace.ts","../src/system-prompt.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { logger } from \"./utils/logger.js\";\nimport { summarizeGitlabNotes } from \"./gitlab.js\";\nimport type { MrMetadata, Platform } from \"./types.js\";\n\n// Resolve templates directory relative to this file (works in both src/ and dist/)\nfunction getTemplatesDir(): string {\n const currentDir = dirname(fileURLToPath(import.meta.url));\n return resolve(currentDir, \"..\", \"templates\");\n}\n\nexport function buildPrReviewPrompt(opts: {\n prUrl: string;\n platform: Platform;\n targetBranch?: string;\n diffBaseSha?: string | null;\n mrMetadata?: MrMetadata | null;\n customInstructions?: string | null;\n customPromptFile?: string | null;\n embeddedDiff?: string | null;\n previousReviewSha?: string | null;\n localMode?: boolean;\n}): string {\n const {\n prUrl,\n platform,\n targetBranch = \"main\",\n diffBaseSha,\n mrMetadata,\n customInstructions,\n customPromptFile,\n embeddedDiff,\n previousReviewSha,\n localMode = false,\n } = opts;\n\n // Step 1: Determine template (always tool submission; rendered to markdown post-hoc)\n let templateFile: string;\n if (customPromptFile) {\n templateFile = customPromptFile;\n logger.info(`Using custom prompt file: ${templateFile}`);\n } else {\n templateFile = resolve(getTemplatesDir(), \"tool-review.md\");\n logger.info(\"Using tool-based review template\");\n }\n\n // Step 2: Load template\n let templateText: string;\n try {\n templateText = readFileSync(templateFile, \"utf-8\");\n } catch (err) {\n throw new Error(`Failed to load prompt template from ${templateFile}: ${err}`);\n }\n\n // Validate ref inputs to prevent shell injection via branch/SHA names.\n // Block shell metacharacters while allowing valid git ref chars (@, +, ~, ^, etc.)\n const dangerousChars = /[;\\|`$&<>(){}\\n\\r\\0\\\\!]/;\n if (dangerousChars.test(targetBranch)) {\n throw new Error(`Invalid target branch name: ${targetBranch}`);\n }\n if (diffBaseSha && dangerousChars.test(diffBaseSha)) {\n throw new Error(`Invalid diff base SHA: ${diffBaseSha}`);\n }\n if (previousReviewSha && !/^[a-f0-9]{40}$/.test(previousReviewSha)) {\n throw new Error(`Invalid previous review SHA: ${previousReviewSha}`);\n }\n\n // Prepare platform-specific commands\n let prDiffCmd: string;\n let gitDiffCmd: string;\n\n // Incremental mode: three-dot diff from previous review SHA (excludes upstream changes)\n if (previousReviewSha) {\n prDiffCmd = `git --no-pager diff ${previousReviewSha}...HEAD --name-only`;\n gitDiffCmd = `git --no-pager diff ${previousReviewSha}...HEAD`;\n logger.info(`Incremental review: diffing from ${previousReviewSha.slice(0, 8)} to HEAD`);\n } else if (localMode) {\n // Plain two-arg diff includes uncommitted (staged + unstaged) changes\n prDiffCmd = `git --no-pager diff ${targetBranch} --name-only`;\n gitDiffCmd = `git --no-pager diff ${targetBranch}`;\n } else if (platform === \"github\" || platform === \"gitea\") {\n prDiffCmd = `git --no-pager diff origin/${targetBranch}...HEAD --name-only`;\n gitDiffCmd = `git --no-pager diff origin/${targetBranch}...HEAD`;\n } else {\n // gitlab\n if (diffBaseSha) {\n prDiffCmd = `git --no-pager diff ${diffBaseSha} HEAD --name-only`;\n gitDiffCmd = `git --no-pager diff ${diffBaseSha} HEAD`;\n logger.info(`Using GitLab CI_MERGE_REQUEST_DIFF_BASE_SHA: ${diffBaseSha.slice(0, 8)}`);\n } else {\n prDiffCmd = `git --no-pager diff origin/${targetBranch}...HEAD --name-only`;\n gitDiffCmd = `git --no-pager diff origin/${targetBranch}...HEAD`;\n }\n }\n\n // Diff explanation\n let diffExplanation: string;\n if (previousReviewSha) {\n diffExplanation =\n `**Incremental mode**: Showing only changes since the last hodor review ` +\n `(commit \\`${previousReviewSha.slice(0, 8)}\\`).`;\n } else if (diffBaseSha) {\n diffExplanation =\n `**GitLab CI Advantage**: This uses GitLab's pre-calculated merge base SHA ` +\n `(\\`CI_MERGE_REQUEST_DIFF_BASE_SHA\\`), which matches exactly what the GitLab UI shows. ` +\n `This is more reliable than three-dot syntax because it handles force pushes, rebases, ` +\n `and messy histories correctly.`;\n } else {\n diffExplanation =\n `**Three-dot syntax** shows ONLY changes introduced on the source branch, ` +\n `excluding changes already on \\`${targetBranch}\\`.`;\n }\n\n // Step 3: Build MR sections\n const { contextSection, notesSection, reminderSection } = buildMrSections(mrMetadata);\n\n // Step 3b: Build incremental review section\n let incrementalSection = \"\";\n if (previousReviewSha) {\n incrementalSection =\n \"## Incremental Review Mode\\n\\n\" +\n `This is a follow-up review. A previous hodor review was done at commit \\`${previousReviewSha.slice(0, 8)}\\`. ` +\n \"The diff below shows ONLY changes since that review. Your job is to review that delta, not the whole MR again.\\n\\n\" +\n \"Rules for incremental reviews:\\n\" +\n \"1. Only report bugs introduced or still affected by the new delta.\\n\" +\n \"2. Do not re-report issues that are already mentioned in existing notes unless the new delta changes the same code and the issue remains newly relevant.\\n\" +\n \"3. If the delta is small and self-contained, decide from the embedded diff and submit the review without broad repository exploration.\\n\" +\n \"4. For mechanical changes like route/path/string renames, verify the direct call sites or tests only when the diff itself leaves a concrete compatibility question.\\n\" +\n \"5. If the delta does not introduce a production bug, submit no findings.\\n\\n\";\n }\n\n // Step 3c: Build conditional sections based on whether diff is embedded\n let embeddedDiffSection: string;\n let diffFetchInstructions: string;\n let reviewProcessSection: string;\n let startInstruction: string;\n\n if (embeddedDiff) {\n embeddedDiffSection =\n \"## Full Diff (Pre-fetched)\\n\\n\" +\n \"The complete diff for this PR is provided below. Analyze it directly. \" +\n \"Use `read` or `grep` only if you need additional file context beyond what the diff shows.\\n\\n\" +\n \"````diff\\n\" + embeddedDiff + \"\\n````\\n\";\n\n diffFetchInstructions =\n \"## Review the Diff Above\\n\\n\" +\n \"### Critical Rules\\n\" +\n \"- ONLY review files that appear in the diff above\\n\" +\n \"- ONLY analyze actual code changes (+ and - lines in the diff)\\n\" +\n \"- NEVER review files not in the diff\\n\" +\n \"- NEVER flag \\\"files will be deleted when merging\\\" (outdated branch)\\n\" +\n \"- NEVER flag \\\"dependency version downgrade\\\" (branch not rebased)\\n\" +\n `- NEVER compare entire codebase to ${targetBranch} - DIFF ONLY\\n`;\n\n reviewProcessSection =\n \"## Review Process\\n\\n\" +\n \"1. Analyze the embedded diff above thoroughly\\n\" +\n \"2. Use `grep` to search for patterns when needed\\n\" +\n \"3. Use `read` only when surrounding context is essential\\n\" +\n \"4. Submit your review using `submit_review`\\n\";\n\n startInstruction = previousReviewSha\n ? \"Analyze only the incremental diff provided above. If it is self-contained, submit your review without extra tool calls.\"\n : \"Analyze the diff provided above, then submit your review using `submit_review`.\";\n } else {\n embeddedDiffSection = \"\";\n\n diffFetchInstructions =\n \"## Step 1: List Changed Files (MANDATORY FIRST STEP)\\n\\n\" +\n \"**Run this command FIRST to get the list of changed files:**\\n\" +\n \"```bash\\n\" + prDiffCmd + \"\\n```\\n\\n\" +\n \"This lists ONLY the filenames changed in this PR. **Do NOT dump the entire diff here** - \" +\n \"you'll inspect each file individually in Step 2. Only review files that appear in this output.\\n\\n\" +\n \"## Step 2: Review Changed Files Only\\n\\n\" +\n \"### Critical Rules\\n\" +\n \"- ONLY review files that appear in the diff from Step 1\\n\" +\n \"- ONLY analyze actual code changes (+ and - lines in the diff)\\n\" +\n \"- Use the most reliable diff command: `\" + gitDiffCmd + \"`\\n\" +\n \"- NEVER review files not in the diff\\n\" +\n \"- NEVER flag \\\"files will be deleted when merging\\\" (outdated branch)\\n\" +\n \"- NEVER flag \\\"dependency version downgrade\\\" (branch not rebased)\\n\" +\n `- NEVER compare entire codebase to ${targetBranch} - DIFF ONLY\\n\\n` +\n \"### Git Diff Command\\n\\n\" +\n \"**Most reliable command to see changes:**\\n\" +\n \"```bash\\n\" + gitDiffCmd + \"\\n```\\n\\n\" +\n diffExplanation;\n\n reviewProcessSection =\n \"## Review Process\\n\\n\" +\n \"**Efficient Sequential Workflow:**\\n\\n\" +\n `1. **List files first**: Run \\`${prDiffCmd}\\` to get the list of changed files (NOT full diff)\\n` +\n `2. **Per-file analysis**: For each file, run \\`${gitDiffCmd} -- path/to/file\\` to see its specific changes\\n` +\n \"3. **Batch pattern search**: Use `grep` across multiple files to find common bug patterns (null, undefined, TODO, FIXME, etc.)\\n\" +\n \"4. **Selective deep dive**: Only use `read` to read full file context when the diff alone is insufficient\\n\" +\n \"5. **Group related files**: Analyze related files together (e.g., implementation + tests, interfaces + implementations)\\n\" +\n \"6. **Avoid redundancy**: Don't re-read files unnecessarily; make decisions based on diff context\\n\";\n\n startInstruction =\n `Start by running \\`${prDiffCmd}\\` to list the changed files, then analyze each file individually using \\`${gitDiffCmd} -- path/to/file\\`.`;\n }\n\n // Step 4: Interpolate\n let prompt = templateText\n .replace(/\\{pr_url\\}/g, prUrl)\n .replace(/\\{pr_diff_cmd\\}/g, prDiffCmd)\n .replace(/\\{git_diff_cmd\\}/g, gitDiffCmd)\n .replace(/\\{target_branch\\}/g, targetBranch)\n .replace(/\\{diff_explanation\\}/g, diffExplanation)\n .replace(/\\{mr_context_section\\}/g, contextSection)\n .replace(/\\{mr_notes_section\\}/g, notesSection)\n .replace(/\\{mr_reminder_section\\}/g, reminderSection)\n .replace(/\\{incremental_section\\}/g, incrementalSection)\n .replace(/\\{embedded_diff_section\\}/g, embeddedDiffSection)\n .replace(/\\{diff_fetch_instructions\\}/g, diffFetchInstructions)\n .replace(/\\{review_process_section\\}/g, reviewProcessSection)\n .replace(/\\{start_instruction\\}/g, startInstruction);\n\n // Step 5: Append custom instructions\n if (customInstructions) {\n prompt += `\\n\\n## Additional Instructions\\n\\n${customInstructions}\\n`;\n logger.info(\"Appended custom instructions to prompt\");\n }\n\n return prompt;\n}\n\nexport function buildMrSections(mrMetadata?: MrMetadata | null): {\n contextSection: string;\n notesSection: string;\n reminderSection: string;\n} {\n if (!mrMetadata) {\n return { contextSection: \"\", notesSection: \"\", reminderSection: \"\" };\n }\n\n const contextLines: string[] = [];\n\n if (mrMetadata.title) {\n contextLines.push(`- Title: ${mrMetadata.title}`);\n }\n\n const author =\n mrMetadata.author?.username ?? mrMetadata.author?.name;\n if (author) {\n contextLines.push(`- Author: @${author}`);\n }\n\n if (mrMetadata.source_branch && mrMetadata.target_branch) {\n contextLines.push(\n `- Branches: ${mrMetadata.source_branch} → ${mrMetadata.target_branch}`,\n );\n }\n\n if (mrMetadata.changes_count) {\n contextLines.push(`- Files changed: ${mrMetadata.changes_count}`);\n }\n\n const pipelineStatus = mrMetadata.pipeline?.status;\n const pipelineUrl = mrMetadata.pipeline?.web_url;\n if (pipelineStatus) {\n const statusText = pipelineStatus.replace(/_/g, \" \");\n contextLines.push(\n pipelineUrl\n ? `- Pipeline: ${statusText} (${pipelineUrl})`\n : `- Pipeline: ${statusText}`,\n );\n }\n\n let labelNames = normalizeLabelNames(mrMetadata.label_details);\n if (labelNames.length === 0) {\n labelNames = normalizeLabelNames(mrMetadata.labels);\n }\n if (labelNames.length > 0) {\n contextLines.push(`- Labels: ${labelNames.join(\", \")}`);\n }\n\n const description = (mrMetadata.description ?? \"\").trim();\n let descriptionSection = \"\";\n if (description) {\n descriptionSection =\n \"**Author Description:**\\n\" + truncateBlock(description, 800);\n }\n\n let contextSection = \"\";\n if (contextLines.length > 0 || descriptionSection) {\n contextSection = \"## MR Context\\n\" + contextLines.join(\"\\n\");\n if (descriptionSection) {\n contextSection += \"\\n\\n\" + descriptionSection;\n }\n contextSection += \"\\n\";\n }\n\n let notesSection = \"\";\n const notesSummary = summarizeGitlabNotes(mrMetadata.Notes);\n if (notesSummary) {\n notesSection = `## Existing MR Notes\\n${notesSummary}\\n`;\n }\n\n let reminderSection = \"\";\n if (notesSummary) {\n reminderSection =\n \"## Review Note Deduplication\\n\\n\" +\n \"The discussions above may already cover some issues. Before reporting a finding:\\n\" +\n \"1. Check if it's already mentioned in existing notes\\n\" +\n \"2. Only report if your finding is materially different or more specific\\n\" +\n \"3. If an existing note is incorrect/outdated, explain why in your finding\\n\\n\" +\n \"Focus on discovering NEW issues not yet discussed.\\n\";\n }\n\n return { contextSection, notesSection, reminderSection };\n}\n\nfunction truncateBlock(text: string, limit: number): string {\n const trimmed = text.trim();\n if (trimmed.length <= limit) return trimmed;\n return trimmed.slice(0, limit - 1).trimEnd() + \"…\";\n}\n\nexport function normalizeLabelNames(\n rawLabels: unknown,\n): string[] {\n if (!rawLabels) return [];\n\n const names: string[] = [];\n\n function addLabel(value: unknown): void {\n let name = \"\";\n if (typeof value === \"string\") {\n name = value.trim();\n } else if (typeof value === \"object\" && value !== null) {\n const labelValue = (value as Record<string, unknown>).name;\n if (typeof labelValue === \"string\") {\n name = labelValue.trim();\n }\n } else if (value != null) {\n name = String(value).trim();\n }\n if (name) names.push(name);\n }\n\n if (Array.isArray(rawLabels)) {\n for (const label of rawLabels) addLabel(label);\n } else {\n addLabel(rawLabels);\n }\n\n return names;\n}\n","import { getEnvApiKey, getProviders } from \"@earendil-works/pi-ai\";\nimport type { ThinkingLevel } from \"@earendil-works/pi-ai\";\n\nexport interface ParsedModel {\n provider: string;\n modelId: string;\n}\n\nconst PROVIDER_ALIASES: Record<string, string> = {\n bedrock: \"amazon-bedrock\",\n};\n\n/**\n * Parse a model string like \"anthropic/claude-sonnet-4-5\" into { provider, modelId }.\n * Handles bare names like \"claude-sonnet-4-5\" or \"gpt-5\" via auto-detection.\n */\nexport function parseModelString(model: string): ParsedModel {\n const trimmed = model.trim();\n if (!trimmed) throw new Error(\"Model name must be provided\");\n\n const parts = trimmed.split(\"/\");\n\n // Explicit provider prefix. Hodor delegates provider/model support to pi-ai's\n // registry instead of maintaining its own curated allow-list. Keep `bedrock`\n // as a friendlier alias for pi-ai's `amazon-bedrock` provider name.\n if (parts.length >= 2) {\n const first = parts[0].toLowerCase();\n const provider = PROVIDER_ALIASES[first] ?? first;\n const knownProviders = new Set<string>(getProviders());\n\n if (provider === \"amazon-bedrock\") {\n // Strip optional \"converse/\" prefix from model ID for backwards compatibility.\n let modelId = parts.slice(1).join(\"/\");\n if (modelId.startsWith(\"converse/\")) {\n modelId = modelId.slice(\"converse/\".length);\n }\n return { provider, modelId };\n }\n\n if (knownProviders.has(provider)) {\n return { provider, modelId: parts.slice(1).join(\"/\") };\n }\n\n // OpenRouter adds new model slugs frequently. Allow it even if the installed\n // pi-ai registry ever lags; agent.ts has a conservative OpenRouter fallback.\n if (provider === \"openrouter\") {\n return { provider, modelId: parts.slice(1).join(\"/\") };\n }\n\n throw new Error(\n `Unsupported provider \"${first}\". Use a pi-ai provider prefix such as anthropic/, openai/, openrouter/, google/, mistral/, xai/, or bedrock/.`,\n );\n }\n\n // Auto-detect provider from bare model name\n const lower = trimmed.toLowerCase();\n if (lower.includes(\"claude\") || lower.includes(\"anthropic\")) {\n return { provider: \"anthropic\", modelId: trimmed };\n }\n if (\n lower.startsWith(\"gpt\") ||\n lower.startsWith(\"o1\") ||\n lower.startsWith(\"o3\") ||\n lower.startsWith(\"o4\") ||\n lower.includes(\"openai\")\n ) {\n return { provider: \"openai\", modelId: trimmed };\n }\n\n // Default to anthropic for unknown models\n return { provider: \"anthropic\", modelId: trimmed };\n}\n\n/**\n * Map reasoning effort strings to pi-ai thinking levels.\n * Returns undefined for no reasoning.\n */\nexport function mapReasoningEffort(\n effort: string | undefined,\n): ThinkingLevel | undefined {\n if (!effort) return undefined;\n switch (effort.toLowerCase()) {\n case \"minimal\":\n return \"minimal\";\n case \"low\":\n return \"low\";\n case \"medium\":\n return \"medium\";\n case \"high\":\n return \"high\";\n case \"xhigh\":\n return \"xhigh\";\n default:\n return undefined;\n }\n}\n\nfunction normalizeModelMatchValue(value: string): string {\n return value.toLowerCase().replace(/[\\s_.:/]+/g, \"-\");\n}\n\nexport function getDefaultReasoningEffortForModel(model: {\n id?: string;\n name?: string;\n}): ThinkingLevel | undefined {\n const values = [model.id, model.name].filter((value): value is string => Boolean(value));\n const isOpus47 = values\n .map(normalizeModelMatchValue)\n .some((value) => value.includes(\"opus-4-7\"));\n\n return isOpus47 ? \"xhigh\" : undefined;\n}\n\n/**\n * Get API key with provider-aware selection.\n *\n * Priority:\n * 1. LLM_API_KEY (universal override)\n * 2. Provider-specific key known by pi-ai (ANTHROPIC_API_KEY, OPENAI_API_KEY,\n * OPENROUTER_API_KEY, etc.)\n *\n * Returns null for bedrock (uses AWS credentials).\n */\nexport function getApiKey(model?: string): string | null {\n // Priority 1: Universal override\n const llmKey = process.env.LLM_API_KEY;\n if (llmKey) return llmKey;\n\n // Priority 2: Provider-specific\n if (model) {\n const { provider } = parseModelString(model);\n if (provider === \"amazon-bedrock\") return null;\n const key = getEnvApiKey(provider);\n if (key) return key;\n }\n\n throw new Error(\n model\n ? `No API key found for provider \"${parseModelString(model).provider}\". Set the provider-specific environment variable or LLM_API_KEY.`\n : \"No LLM API key found. Please set LLM_API_KEY or a provider-specific environment variable.\",\n );\n}\n","import chalk from \"chalk\";\nimport { logger } from \"./utils/logger.js\";\nimport type { ReviewMetrics } from \"./types.js\";\n\ntype FindingPriority = { priority: number };\n\nfunction tok(value: number): string {\n if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(2)}M`;\n if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`;\n return String(value);\n}\n\nfunction formatDuration(seconds: number): string {\n if (seconds >= 60) {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return s > 0 ? `${m}m ${s}s` : `${m}m`;\n }\n return `${seconds}s`;\n}\n\nexport function formatMetricsMarkdown(metrics: ReviewMetrics): string {\n const totalInput = metrics.inputTokens + metrics.cacheReadTokens;\n const parts = [`in \\`${tok(totalInput)}\\``];\n if (metrics.cacheReadTokens > 0) {\n parts.push(`cached \\`${tok(metrics.cacheReadTokens)}\\``);\n }\n parts.push(`out \\`${tok(metrics.outputTokens)}\\``);\n\n const lines = [\n `**Review Metrics** — ${metrics.turns} turns, ${metrics.toolCalls} tool calls, ${formatDuration(metrics.durationSeconds)}`,\n `- Tokens: ${parts.join(\" | \")} (total \\`${tok(metrics.totalTokens)}\\`)`,\n ];\n if (metrics.cost > 0) {\n lines.push(`- Cost: \\`$${metrics.cost.toFixed(4)}\\``);\n }\n return lines.join(\"\\n\");\n}\n\nexport function printMetrics(metrics: ReviewMetrics, stream: NodeJS.WritableStream = process.stderr): void {\n const dim = chalk.dim;\n const bold = chalk.bold;\n const cyan = chalk.cyan;\n\n const write = (line: string) => stream.write(line + \"\\n\");\n\n write(\"\");\n write(dim(\"─\".repeat(50)));\n\n // Tokens — inputTokens may be only fresh tokens (SDK reports cache hits separately)\n const totalInput = metrics.inputTokens + metrics.cacheReadTokens;\n let tokenLine = `${dim(\"Tokens:\")} ${bold(tok(totalInput))} in`;\n if (metrics.cacheReadTokens > 0) {\n const hitPct = ((metrics.cacheReadTokens / totalInput) * 100).toFixed(0);\n tokenLine += dim(` (${tok(metrics.cacheReadTokens)} cached ${hitPct}% · ${tok(metrics.inputTokens)} fresh)`);\n }\n tokenLine += ` ${bold(tok(metrics.outputTokens))} out`;\n tokenLine += dim(` (${tok(metrics.totalTokens)} total)`);\n write(tokenLine);\n\n // Agent work\n write(\n `${dim(\"Agent:\")} ${bold(String(metrics.turns))} turns ${bold(String(metrics.toolCalls))} tool calls ${cyan(formatDuration(metrics.durationSeconds))}`,\n );\n\n // Cost\n if (metrics.cost > 0) {\n write(`${dim(\"Cost:\")} ${bold(\"$\" + metrics.cost.toFixed(4))}`);\n }\n\n write(dim(\"─\".repeat(50)));\n}\n\n/**\n * Push review metrics to a Prometheus Pushgateway.\n * Failures are logged as warnings and never thrown.\n */\nexport async function pushMetrics(opts: {\n pushgatewayUrl: string;\n metrics: ReviewMetrics;\n findings?: FindingPriority[];\n labels?: Record<string, string>;\n}): Promise<void> {\n const { pushgatewayUrl, metrics, findings = [], labels = {} } = opts;\n\n // Build label string for all metrics\n const formatLabels = (extraLabels: Record<string, string> = {}) => {\n const labelPairs = Object.entries({ ...labels, ...extraLabels })\n .map(([k, v]) => `${k}=\"${v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\\n/g, \"\\\\n\").replace(/\"/g, '\\\\\"')}\"`)\n .join(\",\");\n return labelPairs ? `{${labelPairs}}` : \"\";\n };\n const labelSuffix = formatLabels();\n\n const totalInput = metrics.inputTokens + metrics.cacheReadTokens;\n const cacheHitRatio = totalInput > 0 ? metrics.cacheReadTokens / totalInput : 0;\n const priorityCounts = [0, 1, 2, 3].map((priority) => ({\n priority,\n count: findings.filter((finding) => finding.priority === priority).length,\n }));\n const lines = [\n `# HELP hodor_review_input_tokens_total Total input tokens (fresh + cached)`,\n `# TYPE hodor_review_input_tokens_total gauge`,\n `hodor_review_input_tokens_total${labelSuffix} ${totalInput}`,\n `# HELP hodor_review_output_tokens_total Total output tokens`,\n `# TYPE hodor_review_output_tokens_total gauge`,\n `hodor_review_output_tokens_total${labelSuffix} ${metrics.outputTokens}`,\n `# HELP hodor_review_cache_read_tokens_total Tokens served from prompt cache`,\n `# TYPE hodor_review_cache_read_tokens_total gauge`,\n `hodor_review_cache_read_tokens_total${labelSuffix} ${metrics.cacheReadTokens}`,\n `# HELP hodor_review_cache_write_tokens_total Tokens written to prompt cache`,\n `# TYPE hodor_review_cache_write_tokens_total gauge`,\n `hodor_review_cache_write_tokens_total${labelSuffix} ${metrics.cacheWriteTokens}`,\n `# HELP hodor_review_cache_hit_ratio Fraction of input tokens served from cache (0-1)`,\n `# TYPE hodor_review_cache_hit_ratio gauge`,\n `hodor_review_cache_hit_ratio${labelSuffix} ${cacheHitRatio}`,\n `# HELP hodor_review_findings_total Number of findings at each priority level`,\n `# TYPE hodor_review_findings_total gauge`,\n ...priorityCounts.map(({ priority, count }) =>\n `hodor_review_findings_total${formatLabels({ priority: `P${priority}` })} ${count}`,\n ),\n `# HELP hodor_review_cost_dollars Cost of the review in USD`,\n `# TYPE hodor_review_cost_dollars gauge`,\n `hodor_review_cost_dollars${labelSuffix} ${metrics.cost}`,\n `# HELP hodor_review_turns_total Number of agent turns`,\n `# TYPE hodor_review_turns_total gauge`,\n `hodor_review_turns_total${labelSuffix} ${metrics.turns}`,\n `# HELP hodor_review_tool_calls_total Number of tool calls`,\n `# TYPE hodor_review_tool_calls_total gauge`,\n `hodor_review_tool_calls_total${labelSuffix} ${metrics.toolCalls}`,\n `# HELP hodor_review_duration_seconds Review duration in seconds`,\n `# TYPE hodor_review_duration_seconds gauge`,\n `hodor_review_duration_seconds${labelSuffix} ${metrics.durationSeconds}`,\n \"\",\n ];\n const body = lines.join(\"\\n\");\n\n // POST to either a Prometheus Pushgateway base URL or a direct Prometheus text import endpoint.\n const baseUrl = pushgatewayUrl.replace(/\\/+$/, \"\");\n const url = baseUrl.endsWith(\"/api/v1/import/prometheus\")\n ? baseUrl\n : `${baseUrl}/metrics/job/hodor`;\n\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"text/plain\" },\n body,\n signal: AbortSignal.timeout(10_000),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n logger.warn(`Metrics endpoint returned ${res.status}: ${text.slice(0, 200)}`);\n } else {\n logger.info(\"Metrics pushed successfully\");\n }\n } catch (err) {\n logger.warn(`Failed to push metrics: ${err instanceof Error ? err.message : err}`);\n }\n}\n","import { isAbsolute } from \"node:path\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { ReviewOutput, ReviewPriority } from \"./types.js\";\n\nconst REVIEW_PRIORITY_TAGS = new Map<string, ReviewPriority>([\n [\"[P0]\", 0],\n [\"[P1]\", 1],\n [\"[P2]\", 2],\n [\"[P3]\", 3],\n]);\n\nexport const REVIEW_LOCATION_SCHEMA = Type.Object(\n {\n absolute_file_path: Type.String({ minLength: 1 }),\n line_range: Type.Object(\n {\n start: Type.Integer({ minimum: 1 }),\n end: Type.Integer({ minimum: 1 }),\n },\n { additionalProperties: false },\n ),\n },\n { additionalProperties: false },\n);\n\nexport const REVIEW_FINDING_SCHEMA = Type.Object(\n {\n title: Type.String({ minLength: 1 }),\n body: Type.String({ minLength: 1 }),\n priority: Type.Integer({ minimum: 0, maximum: 3 }),\n code_location: REVIEW_LOCATION_SCHEMA,\n suggestion: Type.Optional(Type.String({ minLength: 1 })),\n },\n { additionalProperties: false },\n);\n\nexport const SUBMIT_REVIEW_SCHEMA = Type.Object(\n {\n findings: Type.Array(REVIEW_FINDING_SCHEMA),\n overall_correctness: Type.Union([\n Type.Literal(\"patch is correct\"),\n Type.Literal(\"patch is incorrect\"),\n ]),\n overall_explanation: Type.String({ minLength: 1 }),\n },\n { additionalProperties: false },\n);\n\nexport function validateReviewOutput(review: ReviewOutput): ReviewOutput {\n if (review.overall_explanation.trim().length === 0) {\n throw new Error(\"submit_review overall_explanation must be non-empty\");\n }\n\n // Auto-correct verdict when it contradicts findings. Findings are the ground truth;\n // the verdict field is mechanically derivable. Throwing here would prevent `terminate: true`\n // from being returned, leaving pi's error-handling behavior undefined.\n if (review.findings.length > 0 && review.overall_correctness !== \"patch is incorrect\") {\n review = { ...review, overall_correctness: \"patch is incorrect\" };\n } else if (review.findings.length === 0 && review.overall_correctness !== \"patch is correct\") {\n review = { ...review, overall_correctness: \"patch is correct\" };\n }\n\n for (const [index, finding] of review.findings.entries()) {\n const label = `submit_review finding ${index + 1}`;\n if (finding.title.trim().length === 0) {\n throw new Error(`${label} title must be non-empty`);\n }\n if (finding.body.trim().length === 0) {\n throw new Error(`${label} body must be non-empty`);\n }\n\n const taggedPriority = getPriorityFromTitle(finding.title);\n if (taggedPriority == null) {\n throw new Error(`${label} title must start with [P0], [P1], [P2], or [P3]`);\n }\n if (finding.priority !== taggedPriority) {\n throw new Error(\n `${label} priority ${finding.priority} does not match title tag ${taggedPriority}`,\n );\n }\n\n const { absolute_file_path: filePath, line_range: lineRange } = finding.code_location;\n if (!isAbsolute(filePath)) {\n throw new Error(`${label} code_location.absolute_file_path must be absolute`);\n }\n if (lineRange.start > lineRange.end) {\n throw new Error(`${label} code_location line_range start must be <= end`);\n }\n }\n\n return review;\n}\n\nfunction getPriorityFromTitle(title: string): ReviewPriority | null {\n const match = title.match(/^\\[(P[0-3])\\]/);\n if (!match) return null;\n return REVIEW_PRIORITY_TAGS.get(`[${match[1]}]`) ?? null;\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { Value } from \"@sinclair/typebox/value\";\nimport type { AgentSession, ToolDefinition } from \"@earendil-works/pi-coding-agent\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { logger } from \"./utils/logger.js\";\nimport { exec } from \"./utils/exec.js\";\nimport {\n fetchGithubPrInfo,\n normalizeGithubMetadata,\n} from \"./github.js\";\nimport {\n fetchGitlabMrInfo,\n postGitlabMrComment,\n getGitlabMrDiffRefs,\n cleanupHodorComments,\n createGitlabDraftNote,\n bulkPublishGitlabDraftNotes,\n postGitlabCommitStatus,\n listHodorDiscussions,\n resolveGitlabDiscussions,\n HODOR_REVIEW_MARKER,\n} from \"./gitlab.js\";\nimport {\n fetchGiteaPrInfo,\n postGiteaPrComment,\n} from \"./gitea.js\";\nimport type { DiffRefs } from \"./gitlab.js\";\nimport { setupWorkspace, cleanupWorkspace } from \"./workspace.js\";\nimport { buildPrReviewPrompt } from \"./prompt.js\";\nimport {\n getDefaultReasoningEffortForModel,\n mapReasoningEffort,\n parseModelString,\n} from \"./model.js\";\nimport { formatMetricsMarkdown, printMetrics } from \"./metrics.js\";\nimport { SUBMIT_REVIEW_SCHEMA, validateReviewOutput } from \"./review.js\";\nimport { REVIEW_SYSTEM_PROMPT } from \"./system-prompt.js\";\nimport { renderMarkdown, renderSummaryMarkdown } from \"./render.js\";\nimport { relativizeWorkspacePath } from \"./utils/path.js\";\nimport type {\n Platform,\n ParsedPrUrl,\n ReviewMetrics,\n PostCommentResult,\n MrMetadata,\n ReviewOutput,\n} from \"./types.js\";\n\nexport interface AgentProgressEvent {\n type: \"tool_start\" | \"tool_end\" | \"thinking\" | \"turn_start\" | \"turn_end\" | \"agent_start\" | \"agent_end\" | \"text_delta\" | \"thinking_delta\" | \"tool_result\";\n toolName?: string;\n toolArgs?: string;\n isError?: boolean;\n turnIndex?: number;\n delta?: string;\n result?: string;\n}\n\nexport function detectPlatform(prUrl: string): Platform {\n const url = new URL(prUrl);\n const hostname = url.hostname;\n if (prUrl.includes(\"/-/merge_requests/\") || hostname.includes(\"gitlab\")) {\n return \"gitlab\";\n }\n // Gitea/Forgejo: /pulls/ (plural) — must check before GitHub since /pulls/ contains /pull/\n if (prUrl.includes(\"/pulls/\") || hostname.includes(\"gitea\") || hostname.includes(\"forgejo\") || hostname.includes(\"codeberg\")) {\n return \"gitea\";\n }\n if (prUrl.includes(\"/pull/\") || hostname.includes(\"github\")) {\n return \"github\";\n }\n throw new Error(\n `Cannot detect platform for URL: ${prUrl}. Expected a GitHub (/pull/), GitLab (/-/merge_requests/), or Gitea/Forgejo (/pulls/) URL.`,\n );\n}\n\nexport function parsePrUrl(prUrl: string): ParsedPrUrl {\n const url = new URL(prUrl);\n const pathParts = url.pathname.split(\"/\").filter(Boolean);\n const host = url.host;\n\n // GitHub format: /owner/repo/pull/123\n if (pathParts.length >= 4 && pathParts[2] === \"pull\") {\n const prNumber = parseInt(pathParts[3], 10);\n if (!Number.isSafeInteger(prNumber) || prNumber <= 0) {\n throw new Error(`Invalid PR number in URL: ${prUrl}. Expected a positive integer after /pull/.`);\n }\n return {\n owner: pathParts[0],\n repo: pathParts[1],\n prNumber,\n host,\n };\n }\n\n // Gitea/Forgejo format: /owner/repo/pulls/123\n if (pathParts.length >= 4 && pathParts[2] === \"pulls\") {\n const prNumber = parseInt(pathParts[3], 10);\n if (!Number.isSafeInteger(prNumber) || prNumber <= 0) {\n throw new Error(`Invalid PR number in URL: ${prUrl}. Expected a positive integer after /pulls/.`);\n }\n return {\n owner: pathParts[0],\n repo: pathParts[1],\n prNumber,\n host,\n };\n }\n\n // GitLab format: /group/subgroup/repo/-/merge_requests/123\n const mrIndex = pathParts.indexOf(\"merge_requests\");\n if (mrIndex >= 0) {\n if (mrIndex < 2 || mrIndex + 1 >= pathParts.length) {\n throw new Error(\n `Invalid GitLab MR URL format: ${prUrl}. Expected .../-/merge_requests/<number>`,\n );\n }\n if (pathParts[mrIndex - 1] !== \"-\") {\n throw new Error(\n `Invalid GitLab MR URL format: ${prUrl}. Missing '/-/' segment before merge_requests.`,\n );\n }\n\n const repo = pathParts[mrIndex - 2];\n const ownerParts = pathParts.slice(0, mrIndex - 2);\n const owner =\n ownerParts.length > 0 ? ownerParts.join(\"/\") : pathParts[0];\n const prNumber = parseInt(pathParts[mrIndex + 1], 10);\n if (!Number.isSafeInteger(prNumber) || prNumber <= 0) {\n throw new Error(`Invalid MR number in URL: ${prUrl}. Expected a positive integer after /merge_requests/.`);\n }\n return { owner, repo, prNumber, host };\n }\n\n throw new Error(\n `Invalid PR/MR URL format: ${prUrl}. Expected GitHub (/pull/), GitLab (/-/merge_requests/), or Gitea/Forgejo (/pulls/) URL.`,\n );\n}\n\nfunction formatLocationRelative(\n loc: { absolute_file_path: string },\n workspacePath?: string | null,\n): string {\n return relativizeWorkspacePath(loc.absolute_file_path, workspacePath ?? undefined);\n}\n\n/**\n * Post a pass/fail commit status to a GitLab MR head SHA based on review priorities.\n * Findings with priority <= 1 (P0/P1) are treated as blocking.\n */\nexport async function postGitlabReviewCommitStatus(\n parsed: ParsedPrUrl,\n review: ReviewOutput,\n diffRefs: DiffRefs,\n): Promise<void> {\n const blocking = review.findings.filter((f) => f.priority <= 1).length;\n const state = blocking > 0 ? \"failed\" : \"success\";\n const description =\n blocking > 0\n ? `${blocking} blocking issue(s) found`\n : review.findings.length > 0\n ? `${review.findings.length} non-blocking issue(s)`\n : \"No issues found\";\n\n await postGitlabCommitStatus(\n parsed.owner,\n parsed.repo,\n diffRefs.head_sha,\n state,\n parsed.host,\n { description },\n );\n}\n\nexport async function postReviewComment(opts: {\n prUrl: string;\n reviewText: string;\n model?: string | null;\n metricsFooter?: string | null;\n headSha?: string | null;\n}): Promise<PostCommentResult> {\n const { prUrl, reviewText, model, metricsFooter, headSha } = opts;\n const platform = detectPlatform(prUrl);\n logger.info(`Posting comment to ${platform} PR/MR: ${prUrl}`);\n\n let parsed: ParsedPrUrl;\n try {\n parsed = parsePrUrl(prUrl);\n } catch (err) {\n return { success: false, error: String(err) };\n }\n\n let body = reviewText;\n if (headSha) {\n body = `<!-- hodor:sha:${headSha} -->\\n${body}`;\n }\n if (model) {\n body = `${body}\\n\\n---\\n\\nReview generated by Hodor (model: \\`${model}\\`)`;\n }\n if (metricsFooter) {\n body = `${body}\\n\\n${metricsFooter}`;\n }\n\n try {\n if (platform === \"github\") {\n await exec(\"gh\", [\n \"pr\",\n \"review\",\n String(parsed.prNumber),\n \"--repo\",\n `${parsed.owner}/${parsed.repo}`,\n \"--comment\",\n \"--body\",\n body,\n ]);\n logger.info(`Successfully posted review to GitHub PR #${parsed.prNumber}`);\n return { success: true, platform: \"github\", prNumber: parsed.prNumber };\n } else if (platform === \"gitea\") {\n await postGiteaPrComment(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n body,\n parsed.host,\n );\n logger.info(`Successfully posted review to Gitea PR #${parsed.prNumber}`);\n return { success: true, platform: \"gitea\", prNumber: parsed.prNumber };\n } else {\n await postGitlabMrComment(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n body,\n parsed.host,\n );\n logger.info(\n `Successfully posted review to GitLab MR !${parsed.prNumber}`,\n );\n return {\n success: true,\n platform: \"gitlab\",\n mrNumber: parsed.prNumber,\n };\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.error(`Failed to post comment: ${msg}`);\n return { success: false, error: msg };\n }\n}\n\nconst HODOR_REVIEW_SHA_RE = /^\\s*<!--\\s*hodor:sha:([a-f0-9]{40})\\s*-->/i;\n\nexport function getHodorReviewShaCandidates(notes: MrMetadata[\"Notes\"] | undefined | null): string[] {\n if (!notes || notes.length === 0) return [];\n\n const candidates: Array<{ sha: string; createdAtMs: number | null; index: number }> = [];\n for (const [index, note] of notes.entries()) {\n const match = note.body?.match(HODOR_REVIEW_SHA_RE);\n if (!match) continue;\n\n const createdAtMs = Date.parse(note.created_at ?? \"\");\n candidates.push({\n sha: match[1],\n createdAtMs: Number.isFinite(createdAtMs) ? createdAtMs : null,\n index,\n });\n }\n\n candidates.sort((a, b) => {\n if (a.createdAtMs != null && b.createdAtMs != null && a.createdAtMs !== b.createdAtMs) {\n return b.createdAtMs - a.createdAtMs;\n }\n if (a.createdAtMs != null && b.createdAtMs == null) return -1;\n if (a.createdAtMs == null && b.createdAtMs != null) return 1;\n return a.index - b.index;\n });\n\n const seen = new Set<string>();\n const shas: string[] = [];\n for (const { sha } of candidates) {\n if (seen.has(sha)) continue;\n seen.add(sha);\n shas.push(sha);\n }\n return shas;\n}\n\nasync function findLatestValidReviewSha(\n notes: MrMetadata[\"Notes\"] | undefined | null,\n workspacePath: string,\n): Promise<string | null> {\n const candidates = getHodorReviewShaCandidates(notes);\n if (candidates.length === 0) return null;\n\n logger.info(`Found ${candidates.length} previous Hodor review marker(s)`);\n for (const sha of candidates) {\n try {\n const { stdout: objType } = await exec(\"git\", [\"cat-file\", \"-t\", sha], { cwd: workspacePath });\n if (objType.trim() !== \"commit\") throw new Error(\"not a commit\");\n await exec(\"git\", [\"merge-base\", \"--is-ancestor\", sha, \"HEAD\"], { cwd: workspacePath });\n return sha;\n } catch {\n logger.info(`Skipping previous review SHA ${sha.slice(0, 8)}; not a valid ancestor of HEAD`);\n }\n }\n\n return null;\n}\n\nexport async function postReviewStructured(opts: {\n prUrl: string;\n review: ReviewOutput;\n model?: string | null;\n metricsFooter?: string | null;\n reviewStyle?: \"summary\" | \"inline\" | \"hybrid\";\n commitStatus?: boolean;\n codeQualityPath?: string | null;\n headSha?: string | null;\n workspacePath?: string | null;\n}): Promise<PostCommentResult> {\n const {\n prUrl,\n review,\n model,\n metricsFooter,\n reviewStyle,\n commitStatus,\n codeQualityPath,\n headSha,\n workspacePath,\n } = opts;\n\n const platform = detectPlatform(prUrl);\n if (platform === \"github\") {\n return postReviewComment({\n prUrl,\n reviewText: renderMarkdown(review),\n model,\n metricsFooter,\n headSha,\n });\n }\n\n if (reviewStyle === \"summary\") {\n return postReviewComment({\n prUrl,\n reviewText: renderMarkdown(review),\n model,\n metricsFooter,\n headSha,\n });\n }\n\n const parsed = parsePrUrl(prUrl);\n\n try {\n const discussions = await listHodorDiscussions(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n parsed.host,\n );\n const unresolvedIds = [...new Set(\n discussions.filter((d) => !d.resolved).map((d) => d.discussionId),\n )];\n if (unresolvedIds.length > 0) {\n const resolved = await resolveGitlabDiscussions(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n unresolvedIds,\n parsed.host,\n );\n if (resolved > 0) logger.info(`Resolved ${resolved} old Hodor discussion(s)`);\n }\n } catch (err) {\n logger.warn(`Failed to resolve old discussions: ${err instanceof Error ? err.message : err}`);\n }\n\n try {\n const deleted = await cleanupHodorComments(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n parsed.host,\n );\n if (deleted > 0) logger.info(`Cleaned up ${deleted} old Hodor comment(s)`);\n } catch (err) {\n logger.warn(`Failed to cleanup old comments: ${err instanceof Error ? err.message : err}`);\n }\n\n let diffRefs: DiffRefs | null = null;\n try {\n diffRefs = await getGitlabMrDiffRefs(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n parsed.host,\n );\n } catch (err) {\n logger.warn(`Failed to get diff_refs, falling back to summary mode: ${err instanceof Error ? err.message : err}`);\n }\n\n if (!diffRefs) {\n return postReviewComment({\n prUrl,\n reviewText: renderMarkdown(review),\n model,\n metricsFooter,\n headSha,\n });\n }\n\n let inlineCount = 0;\n let failedCount = 0;\n let summaryPosted = false;\n let draftsPublished = false;\n let statusPosted = false;\n const postingErrors: string[] = [];\n\n for (const finding of review.findings) {\n const relPath = formatLocationRelative(finding.code_location, workspacePath);\n const priorityTag = `[P${finding.priority}]`;\n const title = /^\\[P[0-3]\\]/.test(finding.title)\n ? finding.title\n : `${priorityTag} ${finding.title}`;\n let body = `${HODOR_REVIEW_MARKER}\\n**${title}**\\n\\n${finding.body}`;\n\n if (finding.suggestion) {\n // GitLab suggestion blocks anchor to the comment's line and extend `+N` lines below.\n // Single-line: `suggestion:-0+0` (replace one line). Range: `suggestion:-0+N`\n // where N is the number of additional lines beyond the anchor.\n const { start, end } = finding.code_location.line_range;\n const span = Math.max(0, end - start);\n body += `\\n\\n\\`\\`\\`suggestion:-0+${span}\\n${finding.suggestion}\\n\\`\\`\\``;\n }\n\n try {\n await createGitlabDraftNote(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n body,\n parsed.host,\n {\n filePath: relPath,\n line: finding.code_location.line_range.start,\n diffRefs,\n },\n );\n inlineCount++;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn(`Failed to create inline note for \"${finding.title}\": ${msg}`);\n postingErrors.push(`inline note: ${msg}`);\n failedCount++;\n }\n }\n\n logger.info(`Created ${inlineCount} inline draft note(s)${failedCount > 0 ? ` (${failedCount} failed)` : \"\"}`);\n\n if (reviewStyle === \"hybrid\" || reviewStyle === undefined) {\n let summaryBody = renderSummaryMarkdown(review);\n if (headSha) summaryBody = `<!-- hodor:sha:${headSha} -->\\n${summaryBody}`;\n if (model) summaryBody += `\\n---\\n\\nReview generated by Hodor (model: \\`${model}\\`)`;\n if (metricsFooter) summaryBody += `\\n\\n${metricsFooter}`;\n try {\n await postGitlabMrComment(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n summaryBody,\n parsed.host,\n );\n summaryPosted = true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn(`Failed to post summary comment: ${msg}`);\n postingErrors.push(`summary comment: ${msg}`);\n }\n }\n\n if (inlineCount > 0) {\n try {\n await bulkPublishGitlabDraftNotes(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n parsed.host,\n );\n logger.info(\"Published all draft notes\");\n draftsPublished = true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn(`Failed to bulk publish draft notes: ${msg}`);\n postingErrors.push(`draft publish: ${msg}`);\n }\n }\n\n if (commitStatus && diffRefs) {\n try {\n await postGitlabReviewCommitStatus(parsed, review, diffRefs);\n logger.info(\"Posted commit status\");\n statusPosted = true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn(`Failed to post commit status: ${msg}`);\n postingErrors.push(`commit status: ${msg}`);\n }\n }\n\n if (codeQualityPath) {\n try {\n const { formatCodeQualityReport } = await import(\"./codequality.js\");\n const report = formatCodeQualityReport(review, workspacePath ?? undefined);\n const { writeFileSync } = await import(\"node:fs\");\n writeFileSync(codeQualityPath, report, \"utf-8\");\n logger.info(`Wrote code quality report to ${codeQualityPath}`);\n } catch (err) {\n logger.warn(`Failed to write code quality report: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n const visibleResult = summaryPosted || (inlineCount > 0 && draftsPublished) || statusPosted;\n const expectedInlineComments = reviewStyle === \"inline\" && review.findings.length > 0;\n if ((postingErrors.length > 0 && !visibleResult) || (expectedInlineComments && inlineCount === 0)) {\n return {\n success: false,\n platform: \"gitlab\",\n mrNumber: parsed.prNumber,\n error: postingErrors[0] ?? \"No GitLab inline comments were created\",\n };\n }\n\n return {\n success: true,\n platform: \"gitlab\",\n mrNumber: parsed.prNumber,\n };\n}\n\n// Paths that waste context without contributing reviewable logic.\nconst DIFF_SKIP_PATTERNS: RegExp[] = [\n /(?:^|\\/)testdata\\//, // test fixture directories\n /(?:^|\\/)(?:package-lock\\.json|yarn\\.lock|pnpm-lock\\.yaml|go\\.sum|Cargo\\.lock|poetry\\.lock|Gemfile\\.lock|composer\\.lock)$/,\n /\\.mdx?$/, // markdown docs\n];\n\nexport function filterEmbeddedDiff(rawDiff: string): { filtered: string; skippedFiles: string[] } {\n const skippedFiles: string[] = [];\n // Each file section starts with \"diff --git a/\". Split while preserving the delimiter.\n const sections = rawDiff.split(/(?=^diff --git )/m);\n const kept: string[] = [];\n for (const section of sections) {\n const match = section.match(/^diff --git a\\/(.*?) b\\//);\n if (!match) {\n kept.push(section);\n continue;\n }\n const filePath = match[1];\n if (DIFF_SKIP_PATTERNS.some((re) => re.test(filePath))) {\n skippedFiles.push(filePath);\n } else {\n kept.push(section);\n }\n }\n return { filtered: kept.join(\"\"), skippedFiles };\n}\n\nconst SUBMIT_REVIEW_RECOVERY_ATTEMPTS = 2;\n\nexport function buildSubmitReviewRecoveryPrompt(attempt: number, maxAttempts: number): string {\n const finalAttempt =\n attempt >= maxAttempts\n ? \"\\nThis is the final automatic recovery attempt; do not end the turn without calling `submit_review`.\"\n : \"\";\n\n return [\n \"Your previous assistant turn ended without a valid `submit_review` tool call, so Hodor cannot capture the review.\",\n \"Continue from the existing review context. Use only read-only tools and only the changed files/diff already identified.\",\n \"If more evidence is needed, inspect the relevant diff or file context now.\",\n \"When analysis is complete, call `submit_review` exactly once. Do not write the review as normal text.\",\n \"If there are no findings, call `submit_review` with `\\\"findings\\\": []` and `\\\"overall_correctness\\\": \\\"patch is correct\\\"`.\",\n finalAttempt,\n ].filter(Boolean).join(\"\\n\");\n}\n\nexport function parseReviewFromAssistantText(text: string): ReviewOutput | null {\n const candidates = getJsonCandidates(text);\n for (const candidate of candidates) {\n try {\n const parsed = JSON.parse(candidate) as unknown;\n if (!Value.Check(SUBMIT_REVIEW_SCHEMA, parsed)) {\n continue;\n }\n return validateReviewOutput(parsed as ReviewOutput);\n } catch {\n // Keep scanning; assistant text often contains prose around the payload.\n }\n }\n return null;\n}\n\nfunction getJsonCandidates(text: string): string[] {\n const candidates: string[] = [];\n const seen = new Set<string>();\n\n const addCandidate = (value: string): void => {\n const trimmed = value.trim();\n if (!trimmed || seen.has(trimmed)) return;\n seen.add(trimmed);\n candidates.push(trimmed);\n };\n\n addCandidate(text);\n\n const fencedJson = /```(?:json)?\\s*([\\s\\S]*?)```/gi;\n for (const match of text.matchAll(fencedJson)) {\n addCandidate(match[1] ?? \"\");\n }\n\n const firstBrace = text.indexOf(\"{\");\n const lastBrace = text.lastIndexOf(\"}\");\n if (firstBrace >= 0 && lastBrace > firstBrace) {\n addCandidate(text.slice(firstBrace, lastBrace + 1));\n }\n\n return candidates;\n}\n\nfunction summarizeLastAssistantMessage(session: AgentSession): string {\n const messages = session.messages as unknown as Array<Record<string, unknown>>;\n const lastAssistant = [...messages].reverse().find((msg) => msg.role === \"assistant\");\n if (!lastAssistant) {\n return \"no assistant message\";\n }\n\n const stopReason =\n typeof lastAssistant.stopReason === \"string\" ? lastAssistant.stopReason : \"unknown\";\n const errorMessage =\n typeof lastAssistant.errorMessage === \"string\" ? `, error=${JSON.stringify(truncateForLog(lastAssistant.errorMessage, 300))}` : \"\";\n const content = Array.isArray(lastAssistant.content)\n ? lastAssistant.content\n .map((item) => {\n const block = item as Record<string, unknown>;\n const type = typeof block.type === \"string\" ? block.type : \"unknown\";\n if (type === \"toolCall\" && typeof block.name === \"string\") {\n return `toolCall:${block.name}`;\n }\n return type;\n })\n .join(\",\")\n : \"unknown\";\n const rawText = session.getLastAssistantText()?.trim();\n const textSummary = rawText\n ? `, text=${JSON.stringify(truncateForLog(rawText.replace(/\\s+/g, \" \"), 500))}`\n : \"\";\n\n return `stopReason=${stopReason}, content=[${content || \"none\"}]${errorMessage}${textSummary}`;\n}\n\nfunction truncateForLog(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return `${text.slice(0, maxLength - 1)}…`;\n}\n\nexport async function reviewPr(opts: {\n prUrl?: string;\n model?: string;\n reasoningEffort?: string;\n customPrompt?: string | null;\n promptFile?: string | null;\n cleanup?: boolean;\n workspaceDir?: string | null;\n includeMetricsFooter?: boolean;\n onEvent?: (event: AgentProgressEvent) => void;\n bedrockTags?: Record<string, string> | null;\n localMode?: boolean;\n diffAgainst?: string;\n}): Promise<{ review: ReviewOutput; metricsFooter: string | null; headSha: string | null; metrics: ReviewMetrics; workspacePath: string }> {\n const {\n prUrl,\n model = \"anthropic/claude-sonnet-4-5-20250929\",\n reasoningEffort,\n customPrompt,\n promptFile,\n cleanup = true,\n workspaceDir,\n includeMetricsFooter = false,\n onEvent,\n bedrockTags,\n localMode = false,\n diffAgainst,\n } = opts;\n\n logger.info(`Starting PR review for: ${localMode ? \"local diff\" : prUrl}`);\n\n let owner = \"\", repo = \"\", host = \"\";\n let prNumber = 0;\n let platform: Platform = \"github\";\n\n if (!localMode && prUrl) {\n const urlParsed = parsePrUrl(prUrl);\n owner = urlParsed.owner;\n repo = urlParsed.repo;\n prNumber = urlParsed.prNumber;\n host = urlParsed.host;\n platform = detectPlatform(prUrl);\n logger.info(`Platform: ${platform}, Repo: ${owner}/${repo}, PR: ${prNumber}, Host: ${host}`);\n }\n\n // --- Preflight: validate model + credentials before any expensive I/O ---\n const parsed = parseModelString(model);\n\n // Snapshot env vars we may mutate, restore in finally block.\n const envSnapshot: Record<string, string | undefined> = {\n AWS_REGION: process.env.AWS_REGION,\n };\n\n // Import pi SDK\n const {\n AuthStorage,\n createAgentSession,\n DefaultResourceLoader,\n ModelRegistry,\n SessionManager,\n SettingsManager,\n getAgentDir,\n } = await import(\"@earendil-works/pi-coding-agent\");\n\n // In-memory auth storage avoids loading ~/.pi/auth.json — env vars only.\n const authStorage = AuthStorage.inMemory();\n if (process.env.LLM_API_KEY) {\n authStorage.setRuntimeApiKey(parsed.provider, process.env.LLM_API_KEY);\n }\n const modelRegistry = ModelRegistry.inMemory(authStorage);\n\n // Resolve model — use registry for known models, construct manually for custom ARNs\n let piModel: Model<Api>;\n if (parsed.modelId.startsWith(\"arn:\")) {\n // Custom bedrock ARN (inference profile, cross-region, etc.)\n // Extract region from ARN: arn:aws:bedrock:<region>:<account>:...\n const arnParts = parsed.modelId.split(\":\");\n const region = arnParts.length >= 4 ? arnParts[3] : \"us-east-1\";\n // Set AWS_REGION so the BedrockRuntimeClient uses the correct endpoint\n if (!process.env.AWS_REGION && !process.env.AWS_DEFAULT_REGION) {\n process.env.AWS_REGION = region;\n }\n piModel = {\n id: parsed.modelId,\n name: parsed.modelId,\n api: \"bedrock-converse-stream\",\n provider: \"amazon-bedrock\",\n baseUrl: `https://bedrock-runtime.${region}.amazonaws.com`,\n reasoning: false,\n input: [\"text\"] as (\"text\" | \"image\")[],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 200000,\n maxTokens: 16384,\n } as Model<Api>;\n logger.info(`Custom bedrock ARN model — region: ${region}`);\n } else {\n const registryModel = modelRegistry.find(parsed.provider, parsed.modelId);\n if (registryModel) {\n piModel = registryModel;\n } else if (parsed.provider === \"openrouter\") {\n piModel = {\n id: parsed.modelId,\n name: parsed.modelId,\n api: \"openai-completions\",\n provider: \"openrouter\",\n baseUrl: \"https://openrouter.ai/api/v1\",\n reasoning: true,\n input: [\"text\", \"image\"] as (\"text\" | \"image\")[],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 256000,\n maxTokens: 65536,\n } as Model<Api>;\n logger.warn(`Using best-effort unregistered OpenRouter model — ${parsed.modelId}`);\n } else {\n throw new Error(\n `Unsupported model \"${model}\". Provider \"${parsed.provider}\" is recognized by pi-ai, but model \"${parsed.modelId}\" was not found in the installed registry.`,\n );\n }\n }\n const thinkingLevel =\n mapReasoningEffort(reasoningEffort) ?? getDefaultReasoningEffortForModel(piModel);\n if (!reasoningEffort && thinkingLevel) {\n logger.info(`Default reasoning effort for ${piModel.name}: ${thinkingLevel}`);\n }\n\n // Note: For bedrock, don't preflight-check AWS credentials because the SDK\n // resolves them from many sources (env vars, IMDS, ECS task role, IRSA,\n // ~/.aws/credentials, etc.) and we can't reliably detect all of them.\n if (parsed.provider !== \"amazon-bedrock\") {\n const resolvedKey = await modelRegistry.getApiKeyForProvider(parsed.provider);\n if (!resolvedKey) {\n throw new Error(\n `No API key found for provider \"${parsed.provider}\". Set the provider-specific environment variable, configure pi auth, or set LLM_API_KEY.`,\n );\n }\n }\n logger.info(\"Preflight OK — model and credentials validated\");\n\n // --- End preflight ---\n\n // Setup workspace\n let workspacePath: string;\n let targetBranch: string;\n let diffBaseSha: string | null = null;\n let isTemporary = false;\n\n if (localMode) {\n // Resolve to git repo root so paths from git diff match tool expectations\n const cwd = workspaceDir ?? process.cwd();\n try {\n const { stdout: toplevel } = await exec(\"git\", [\"rev-parse\", \"--show-toplevel\"], { cwd });\n workspacePath = toplevel.trim();\n } catch {\n workspacePath = cwd; // fallback if not in a git repo\n }\n targetBranch = diffAgainst ?? \"origin/main\";\n logger.info(`Local mode: workspace=${workspacePath}, diffAgainst=${targetBranch}`);\n } else {\n const wsResult = await setupWorkspace({\n platform,\n owner,\n repo,\n prNumber: String(prNumber),\n host,\n workingDir: workspaceDir ?? undefined,\n reuse: workspaceDir != null,\n });\n workspacePath = wsResult.workspace;\n targetBranch = wsResult.targetBranch;\n diffBaseSha = wsResult.diffBaseSha;\n isTemporary = wsResult.isTemporary;\n }\n\n let activeSession: AgentSession | undefined;\n\n try {\n let mrMetadata: MrMetadata | null = null;\n if (!localMode && platform === \"gitlab\") {\n try {\n mrMetadata = await fetchGitlabMrInfo(owner, repo, prNumber, host, {\n includeComments: true,\n });\n } catch (err) {\n logger.warn(`Failed to fetch GitLab metadata: ${err}`);\n }\n } else if (!localMode && platform === \"github\") {\n try {\n const githubRaw = await fetchGithubPrInfo(owner, repo, prNumber);\n mrMetadata = normalizeGithubMetadata(githubRaw);\n } catch (err) {\n logger.warn(`Failed to fetch GitHub metadata: ${err}`);\n }\n } else if (!localMode && platform === \"gitea\") {\n try {\n mrMetadata = await fetchGiteaPrInfo(owner, repo, prNumber, host, {\n includeComments: true,\n });\n } catch (err) {\n logger.warn(`Failed to fetch Gitea metadata: ${err}`);\n }\n }\n\n // Detect previous Hodor review SHA for incremental mode. GitLab returns MR\n // notes newest-first by default, so select by timestamp instead of taking\n // the last match from API order.\n const previousReviewSha = await findLatestValidReviewSha(mrMetadata?.Notes, workspacePath);\n if (previousReviewSha) {\n logger.info(`Incremental mode: previous review at ${previousReviewSha.slice(0, 8)}`);\n }\n\n // Get HEAD SHA for embedding in posted comments (skip in local mode — no posting)\n let headSha: string | null = null;\n if (!localMode) {\n const { stdout: headShaRaw } = await exec(\"git\", [\"rev-parse\", \"HEAD\"], { cwd: workspacePath });\n headSha = headShaRaw.trim();\n }\n\n // Pre-fetch diff for embedding in prompt (avoids per-file tool calls)\n const MAX_EMBED_BYTES = 200 * 1024; // 200KB\n let embeddedDiff: string | null = null;\n try {\n const diffArgs = previousReviewSha\n ? [\"--no-pager\", \"diff\", `${previousReviewSha}...HEAD`]\n : diffBaseSha\n ? [\"--no-pager\", \"diff\", diffBaseSha, \"HEAD\"]\n : localMode\n ? [\"--no-pager\", \"diff\", targetBranch] // includes uncommitted changes\n : [\"--no-pager\", \"diff\", `origin/${targetBranch}...HEAD`];\n const { stdout: rawDiff } = await exec(\"git\", diffArgs, { cwd: workspacePath });\n const { filtered: filteredDiff, skippedFiles } = filterEmbeddedDiff(rawDiff);\n if (skippedFiles.length > 0) {\n logger.info(`Filtered ${skippedFiles.length} file(s) from embedded diff: ${skippedFiles.join(\", \")}`);\n }\n if (Buffer.byteLength(filteredDiff, \"utf-8\") <= MAX_EMBED_BYTES) {\n embeddedDiff = filteredDiff;\n logger.info(`Embedding diff in prompt (${Buffer.byteLength(filteredDiff, \"utf-8\")} bytes, raw: ${Buffer.byteLength(rawDiff, \"utf-8\")} bytes)`);\n } else {\n logger.info(`Diff too large to embed (${Buffer.byteLength(filteredDiff, \"utf-8\")} bytes filtered, ${Buffer.byteLength(rawDiff, \"utf-8\")} bytes raw), using command mode`);\n }\n } catch (err) {\n logger.warn(`Failed to pre-fetch diff, falling back to command mode: ${err}`);\n }\n\n // Build prompt (always uses JSON template; rendered to markdown post-hoc)\n const prompt = buildPrReviewPrompt({\n prUrl: prUrl ?? `local diff (against ${targetBranch})`,\n platform,\n targetBranch,\n diffBaseSha,\n mrMetadata,\n customInstructions: customPrompt,\n customPromptFile: promptFile,\n embeddedDiff,\n previousReviewSha,\n localMode,\n });\n\n const startTime = Date.now();\n const settingsManager = SettingsManager.inMemory({\n compaction: { enabled: true },\n });\n const skillPaths = [join(workspacePath, \".agents\", \"skills\")]\n .filter((p) => existsSync(p));\n const resourceLoader = new DefaultResourceLoader({\n cwd: workspacePath,\n agentDir: getAgentDir(),\n settingsManager,\n systemPrompt: REVIEW_SYSTEM_PROMPT,\n appendSystemPrompt: [],\n noExtensions: true,\n noSkills: true,\n noPromptTemplates: true,\n noThemes: true,\n additionalSkillPaths: skillPaths,\n agentsFilesOverride: () => ({ agentsFiles: [] }),\n });\n await resourceLoader.reload();\n const { skills, diagnostics: skillDiagnostics } = resourceLoader.getSkills();\n if (skills.length > 0) {\n logger.info(`Discovered ${skills.length} repository skill(s)`);\n for (const skill of skills) {\n logger.info(`Found skill: ${skill.name} (${skill.filePath})`);\n }\n }\n for (const diagnostic of skillDiagnostics) {\n const path = diagnostic.path ? ` (${diagnostic.path})` : \"\";\n logger.warn(`Skill diagnostic: ${diagnostic.message}${path}`);\n }\n\n let submittedReview: ReviewOutput | null = null;\n let submitReviewCalls = 0;\n const submitReviewTool: ToolDefinition = {\n name: \"submit_review\",\n label: \"Submit Review\",\n description: \"Submit the final structured review after the analysis is complete.\",\n promptSnippet: \"Submit the final structured review (call exactly once when done)\",\n parameters: SUBMIT_REVIEW_SCHEMA,\n execute: async (_toolCallId, params, _signal, _onUpdate, _ctx) => {\n submitReviewCalls++;\n if (submittedReview) {\n logger.warn(\"Agent called submit_review more than once; ignoring duplicate submission\");\n return {\n content: [{\n type: \"text\",\n text: \"Review already submitted. Do not call submit_review again.\",\n }],\n details: { ignoredDuplicate: true },\n };\n }\n\n try {\n submittedReview = validateReviewOutput(params as ReviewOutput);\n } catch (err) {\n logger.warn(`Invalid submit_review payload: ${err instanceof Error ? err.message : err}`);\n throw err;\n }\n logger.info(\n `Received structured review via submit_review (${submittedReview.findings.length} finding(s))`,\n );\n return {\n content: [{\n type: \"text\",\n text: \"Review received. Do not output the review as normal text.\",\n }],\n details: {},\n terminate: true,\n };\n },\n };\n\n const { session } = await createAgentSession({\n cwd: workspacePath,\n model: piModel,\n thinkingLevel,\n // pi v0.74 filters customTools through the same allowlist as built-ins\n // (see _refreshToolRegistry in @earendil-works/pi-coding-agent's\n // agent-session.ts). The submit_review custom tool must be named here\n // or the LLM never sees it and the agent loop exits without calling it.\n tools: [\"read\", \"bash\", \"grep\", \"find\", \"ls\", \"submit_review\"],\n customTools: [submitReviewTool],\n authStorage,\n modelRegistry,\n sessionManager: SessionManager.inMemory(),\n settingsManager,\n resourceLoader,\n });\n activeSession = session;\n\n // Inject Bedrock cost allocation tags into stream requests\n if (bedrockTags && parsed.provider === \"amazon-bedrock\") {\n type AgentWithStream = { agent: { streamFn: (...args: unknown[]) => unknown } };\n const agent = (session as unknown as AgentWithStream).agent;\n const originalStreamFn = agent.streamFn;\n agent.streamFn = (...args: unknown[]) => {\n const options = (args[2] ?? {}) as Record<string, unknown>;\n return originalStreamFn(args[0], args[1], { ...options, requestMetadata: bedrockTags });\n };\n logger.info(`Bedrock cost allocation tags: ${JSON.stringify(bedrockTags)}`);\n }\n\n // Subscribe to agent events for progress + metrics tracking\n let turnCount = 0;\n let toolCallCount = 0;\n\n /** Extract human-readable summary from tool args */\n function formatToolArgs(_toolName: string, args: unknown): string {\n if (typeof args === \"string\") return args.slice(0, 200);\n const obj = args as Record<string, unknown> | undefined;\n if (!obj) return \"\";\n // bash tool: show the command, strip workspace prefix\n if (obj.command) {\n return String(obj.command)\n .replace(new RegExp(`cd ${workspacePath.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")} && `), \"\")\n .slice(0, 200);\n }\n // grep/find: show pattern + path\n if (obj.pattern) {\n const path = obj.path ? ` in ${obj.path}` : \"\";\n return `${obj.pattern}${path}`;\n }\n // read/ls: show the path\n if (obj.path || obj.file_path) return String(obj.path ?? obj.file_path);\n return JSON.stringify(obj).slice(0, 200);\n }\n\n /** Extract text content from tool result */\n function formatToolResult(result: unknown): string {\n if (typeof result === \"string\") return result;\n const obj = result as Record<string, unknown> | undefined;\n if (!obj) return \"\";\n // pi-sdk wraps results as {content: [{type: \"text\", text: \"...\"}]}\n const content = obj.content as Array<{ type?: string; text?: string }> | undefined;\n if (Array.isArray(content)) {\n return content\n .filter((c) => c.type === \"text\" && c.text)\n .map((c) => c.text)\n .join(\"\\n\");\n }\n return JSON.stringify(result)?.slice(0, 500) ?? \"\";\n }\n\n session.subscribe((event) => {\n switch (event.type) {\n case \"agent_start\":\n onEvent?.({ type: \"agent_start\" });\n break;\n case \"agent_end\":\n onEvent?.({ type: \"agent_end\" });\n break;\n case \"turn_start\":\n turnCount++;\n onEvent?.({ type: \"turn_start\", turnIndex: turnCount });\n break;\n case \"turn_end\":\n onEvent?.({ type: \"turn_end\", turnIndex: turnCount });\n break;\n case \"tool_execution_start\":\n toolCallCount++;\n onEvent?.({\n type: \"tool_start\",\n toolName: event.toolName,\n toolArgs: formatToolArgs(event.toolName, event.args),\n });\n break;\n case \"tool_execution_end\":\n onEvent?.({\n type: \"tool_end\",\n toolName: event.toolName,\n isError: event.isError,\n result: formatToolResult(event.result),\n });\n break;\n case \"message_start\":\n onEvent?.({ type: \"thinking\" });\n break;\n case \"message_update\": {\n const msgEvent = (event as Record<string, unknown>).assistantMessageEvent as\n { type: string; delta?: string } | undefined;\n if (!msgEvent?.delta) break;\n if (msgEvent.type === \"text_delta\") {\n onEvent?.({ type: \"text_delta\", delta: msgEvent.delta });\n } else if (msgEvent.type === \"thinking_delta\") {\n onEvent?.({ type: \"thinking_delta\", delta: msgEvent.delta });\n }\n break;\n }\n }\n });\n\n const throwIfAgentErrored = (): void => {\n // pi-agent-core stores failed/aborted assistant turns in state.errorMessage.\n const agentError = session.state.errorMessage;\n if (agentError) {\n throw new Error(`LLM request failed: ${agentError}`);\n }\n };\n\n const recoverReviewFromAssistantText = (source: string): boolean => {\n const rawText = session.getLastAssistantText() ?? \"\";\n if (!rawText.trim()) return false;\n\n const parsedReview = parseReviewFromAssistantText(rawText);\n if (!parsedReview) return false;\n\n submittedReview = parsedReview;\n logger.warn(\n `Recovered structured review from assistant text after ${source}; model did not call submit_review`,\n );\n return true;\n };\n\n logger.info(\"Sending prompt to agent...\");\n await session.prompt(prompt);\n throwIfAgentErrored();\n\n if (!submittedReview) {\n recoverReviewFromAssistantText(\"initial agent run\");\n }\n\n for (\n let attempt = 1;\n !submittedReview && attempt <= SUBMIT_REVIEW_RECOVERY_ATTEMPTS;\n attempt++\n ) {\n logger.warn(\n `Agent ended without a valid submit_review (${summarizeLastAssistantMessage(session)}); ` +\n `requesting recovery ${attempt}/${SUBMIT_REVIEW_RECOVERY_ATTEMPTS}`,\n );\n await session.prompt(buildSubmitReviewRecoveryPrompt(attempt, SUBMIT_REVIEW_RECOVERY_ATTEMPTS));\n throwIfAgentErrored();\n recoverReviewFromAssistantText(`recovery attempt ${attempt}`);\n }\n\n if (!submittedReview) {\n const diagnostic = summarizeLastAssistantMessage(session);\n if (submitReviewCalls > 0) {\n throw new Error(\n `Agent called submit_review but did not provide a valid review payload after ` +\n `${SUBMIT_REVIEW_RECOVERY_ATTEMPTS} recovery attempt(s): ${diagnostic}`,\n );\n }\n throw new Error(\n `Agent did not call submit_review after ${SUBMIT_REVIEW_RECOVERY_ATTEMPTS} recovery attempt(s): ${diagnostic}`,\n );\n }\n\n const review = submittedReview as ReviewOutput;\n if (submitReviewCalls > 1) {\n logger.warn(`Agent called submit_review ${submitReviewCalls} times; using the first valid submission`);\n }\n logger.info(\n `Captured ${review.findings.length} finding(s), verdict: ${review.overall_correctness}`,\n );\n\n const durationSeconds = (Date.now() - startTime) / 1000;\n logger.info(`Review complete (${review.findings.length} finding(s))`);\n\n // Aggregate usage from all assistant messages\n interface MsgUsage {\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n totalTokens: number;\n cost: { total: number };\n }\n interface AssistantMsg {\n role: string;\n usage?: MsgUsage;\n }\n\n const allMessages = session.messages as AssistantMsg[];\n\n let inputTokens = 0;\n let outputTokens = 0;\n let cacheReadTokens = 0;\n let cacheWriteTokens = 0;\n let totalTokens = 0;\n let cost = 0;\n\n for (const msg of allMessages) {\n if (msg.role === \"assistant\" && msg.usage) {\n inputTokens += msg.usage.input ?? 0;\n outputTokens += msg.usage.output ?? 0;\n cacheReadTokens += msg.usage.cacheRead ?? 0;\n cacheWriteTokens += msg.usage.cacheWrite ?? 0;\n totalTokens += msg.usage.totalTokens ?? 0;\n cost += msg.usage.cost?.total ?? 0;\n }\n }\n\n const metrics: ReviewMetrics = {\n inputTokens,\n outputTokens,\n cacheReadTokens,\n cacheWriteTokens,\n totalTokens,\n cost,\n turns: turnCount,\n toolCalls: toolCallCount,\n durationSeconds: Math.round(durationSeconds),\n };\n printMetrics(metrics);\n\n let metricsFooter: string | null = null;\n if (includeMetricsFooter) {\n metricsFooter = formatMetricsMarkdown(metrics);\n }\n\n return { review, metricsFooter, headSha, metrics, workspacePath };\n } finally {\n activeSession?.dispose();\n\n // Restore mutated env vars\n for (const [key, val] of Object.entries(envSnapshot)) {\n if (val === undefined) {\n delete process.env[key];\n } else {\n process.env[key] = val;\n }\n }\n\n if (cleanup && isTemporary) {\n logger.info(\"Cleaning up workspace...\");\n await cleanupWorkspace(workspacePath);\n }\n }\n}\n","import { execJson } from \"./utils/exec.js\";\nimport type { MrMetadata, NoteEntry } from \"./types.js\";\n\nexport class GitHubAPIError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"GitHubAPIError\";\n }\n}\n\nexport async function fetchGithubPrInfo(\n owner: string,\n repo: string,\n prNumber: number | string,\n): Promise<Record<string, unknown>> {\n const fields = [\n \"number\",\n \"title\",\n \"body\",\n \"author\",\n \"baseRefName\",\n \"headRefName\",\n \"baseRefOid\",\n \"headRefOid\",\n \"changedFiles\",\n \"labels\",\n \"comments\",\n \"state\",\n \"isDraft\",\n \"createdAt\",\n \"updatedAt\",\n \"mergeable\",\n \"url\",\n ];\n\n const repoFullPath = `${owner}/${repo}`;\n try {\n return await execJson<Record<string, unknown>>(\"gh\", [\n \"pr\",\n \"view\",\n String(prNumber),\n \"-R\",\n repoFullPath,\n \"--json\",\n fields.join(\",\"),\n ]);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitHubAPIError(msg);\n }\n}\n\nexport function normalizeGithubMetadata(\n raw: Record<string, unknown>,\n): MrMetadata {\n const author = (raw.author as Record<string, string>) ?? {};\n const labels = (raw.labels as Array<Record<string, string>>) ?? [];\n const comments = raw.comments as\n | Record<string, unknown>\n | Array<Record<string, unknown>>\n | undefined;\n\n return {\n title: raw.title as string | undefined,\n description: (raw.body as string) ?? \"\",\n source_branch: raw.headRefName as string | undefined,\n target_branch: raw.baseRefName as string | undefined,\n changes_count: raw.changedFiles as number | undefined,\n labels: labels.map((lbl) => ({ name: lbl.name ?? lbl.id })),\n author: {\n username: author.login ?? author.name,\n name: author.name,\n },\n Notes: githubCommentsToNotes(comments),\n };\n}\n\nfunction githubCommentsToNotes(\n comments:\n | Record<string, unknown>\n | Array<Record<string, unknown>>\n | undefined\n | null,\n): NoteEntry[] {\n if (!comments) return [];\n\n let nodes: Array<Record<string, unknown>>;\n if (Array.isArray(comments)) {\n nodes = comments;\n } else if (typeof comments === \"object\") {\n nodes =\n (comments.nodes as Array<Record<string, unknown>>) ??\n (comments.edges as Array<Record<string, unknown>>) ??\n [];\n // Handle GraphQL edge format\n if (\n nodes.length > 0 &&\n typeof nodes[0] === \"object\" &&\n \"node\" in nodes[0]\n ) {\n nodes = nodes.map(\n (edge) =>\n (edge.node as Record<string, unknown>) ?? {},\n );\n }\n } else {\n nodes = [];\n }\n\n return nodes.map((node) => {\n const author = (node.author as Record<string, string>) ?? {};\n return {\n body: (node.body as string) ?? \"\",\n author: {\n username: author.login ?? author.name,\n name: author.name,\n },\n created_at: node.createdAt as string | undefined,\n };\n });\n}\n","import { logger } from \"./utils/logger.js\";\nimport type { MrMetadata, NoteEntry } from \"./types.js\";\n\nexport interface GiteaPrCheckoutInfo {\n sourceBranch: string;\n targetBranch: string;\n sourceCloneUrl?: string;\n}\n\nexport class GiteaAPIError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"GiteaAPIError\";\n }\n}\n\nfunction normalizeBaseUrl(host?: string | null): string {\n const candidate =\n host ||\n process.env.GITEA_HOST ||\n process.env.FORGEJO_HOST;\n if (!candidate) {\n throw new GiteaAPIError(\n \"No Gitea/Forgejo host configured. Set GITEA_HOST or FORGEJO_HOST, \" +\n \"or provide a full PR URL that includes the hostname.\",\n );\n }\n const trimmed = candidate.trim();\n if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n return trimmed.replace(/\\/+$/, \"\");\n }\n return `https://${trimmed}`.replace(/\\/+$/, \"\");\n}\n\nfunction giteaToken(): string | null {\n return process.env.GITEA_TOKEN ?? process.env.FORGEJO_TOKEN ?? null;\n}\n\nfunction requireGiteaToken(): string {\n const token = giteaToken();\n if (!token) {\n throw new GiteaAPIError(\n \"No Gitea/Forgejo token found. Set GITEA_TOKEN or FORGEJO_TOKEN environment variable.\",\n );\n }\n return token;\n}\n\nasync function giteaFetch<T>(\n host: string | null | undefined,\n path: string,\n options?: { method?: string; body?: unknown },\n): Promise<T> {\n const baseUrl = normalizeBaseUrl(host);\n const url = `${baseUrl}/api/v1/${path}`;\n const token = giteaToken();\n\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n };\n if (token) {\n headers.Authorization = `token ${token}`;\n }\n\n const init: RequestInit = {\n method: options?.method ?? \"GET\",\n headers,\n };\n\n if (options?.body) {\n init.body = JSON.stringify(options.body);\n }\n\n const response = await fetch(url, init);\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n if (response.status === 401 || response.status === 403) {\n throw new GiteaAPIError(\n `Authentication failed (${response.status}): check GITEA_TOKEN/FORGEJO_TOKEN. ${text}`,\n );\n }\n if (response.status === 429) {\n throw new GiteaAPIError(`Rate limited by Gitea API (429). ${text}`);\n }\n throw new GiteaAPIError(\n `Gitea API error ${response.status} for ${options?.method ?? \"GET\"} ${path}: ${text}`,\n );\n }\n\n return (await response.json()) as T;\n}\n\n/**\n * Fetch pull request metadata from Gitea/Forgejo.\n */\nexport async function fetchGiteaPrInfo(\n owner: string,\n repo: string,\n prNumber: number | string,\n host?: string | null,\n options?: { includeComments?: boolean },\n): Promise<MrMetadata> {\n let prData: Record<string, unknown>;\n try {\n prData = await giteaFetch<Record<string, unknown>>(\n host,\n `repos/${owner}/${repo}/pulls/${prNumber}`,\n );\n } catch (err) {\n if (err instanceof GiteaAPIError) throw err;\n const msg = err instanceof Error ? err.message : String(err);\n throw new GiteaAPIError(`Failed to fetch PR #${prNumber}: ${msg}`);\n }\n\n const user = (prData.user as Record<string, string>) ?? {};\n const head = (prData.head as Record<string, unknown>) ?? {};\n const base = (prData.base as Record<string, unknown>) ?? {};\n const labels = (prData.labels as Array<Record<string, string>>) ?? [];\n\n const metadata: MrMetadata = {\n title: prData.title as string | undefined,\n description: (prData.body as string) ?? \"\",\n source_branch: head.ref as string | undefined,\n target_branch: base.ref as string | undefined,\n changes_count: prData.changed_files as number | undefined,\n labels: labels.map((lbl) => ({ name: lbl.name })),\n author: {\n username: user.login,\n name: user.full_name || user.login,\n },\n state: prData.state as string | undefined,\n };\n\n if (options?.includeComments) {\n try {\n metadata.Notes = await fetchGiteaPrComments(owner, repo, prNumber, host);\n } catch (err) {\n logger.warn(`Failed to fetch PR comments: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n return metadata;\n}\n\n/**\n * Fetch the PR branch metadata needed to checkout same-repo and fork PRs.\n */\nexport async function fetchGiteaPrCheckoutInfo(\n owner: string,\n repo: string,\n prNumber: number | string,\n host?: string | null,\n): Promise<GiteaPrCheckoutInfo> {\n const prData = await giteaFetch<Record<string, unknown>>(\n host,\n `repos/${owner}/${repo}/pulls/${prNumber}`,\n );\n const head = (prData.head as Record<string, unknown>) ?? {};\n const base = (prData.base as Record<string, unknown>) ?? {};\n const headRepo = (head.repo as Record<string, unknown> | null) ?? null;\n const sourceBranch = head.ref as string | undefined;\n\n if (!sourceBranch) {\n throw new GiteaAPIError(`Could not determine source branch for PR #${prNumber}`);\n }\n\n return {\n sourceBranch,\n targetBranch: (base.ref as string | undefined) ?? \"main\",\n sourceCloneUrl: headRepo?.clone_url as string | undefined,\n };\n}\n\n/**\n * Fetch comments on a Gitea/Forgejo pull request.\n * PRs are a superset of issues in Gitea, so comments use the issues endpoint.\n */\nexport async function fetchGiteaPrComments(\n owner: string,\n repo: string,\n prNumber: number | string,\n host?: string | null,\n): Promise<NoteEntry[]> {\n const comments: Array<Record<string, unknown>> = [];\n const pageSize = 100;\n\n for (let page = 1; ; page++) {\n const batch = await giteaFetch<Array<Record<string, unknown>>>(\n host,\n `repos/${owner}/${repo}/issues/${prNumber}/comments?page=${page}&limit=${pageSize}`,\n );\n comments.push(...batch);\n if (batch.length < pageSize) break;\n }\n\n return comments.map((c) => {\n const user = (c.user as Record<string, string>) ?? {};\n return {\n body: (c.body as string) ?? \"\",\n author: {\n username: user.login,\n name: user.full_name || user.login,\n },\n created_at: c.created_at as string | undefined,\n system: false,\n };\n });\n}\n\n/**\n * Post a comment on a Gitea/Forgejo pull request.\n */\nexport async function postGiteaPrComment(\n owner: string,\n repo: string,\n prNumber: number | string,\n body: string,\n host?: string | null,\n): Promise<void> {\n // Posting requires authentication\n requireGiteaToken();\n\n try {\n await giteaFetch<unknown>(\n host,\n `repos/${owner}/${repo}/issues/${prNumber}/comments`,\n { method: \"POST\", body: { body } },\n );\n } catch (err) {\n if (err instanceof GiteaAPIError) throw err;\n const msg = err instanceof Error ? err.message : String(err);\n throw new GiteaAPIError(`Failed to post comment to PR #${prNumber}: ${msg}`);\n }\n}\n","import { mkdtemp, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { exec, execJson } from \"./utils/exec.js\";\nimport { logger } from \"./utils/logger.js\";\nimport { fetchGitlabMrInfo } from \"./gitlab.js\";\nimport { fetchGiteaPrCheckoutInfo } from \"./gitea.js\";\nimport type { GiteaPrCheckoutInfo } from \"./gitea.js\";\nimport type { MrMetadata, Platform } from \"./types.js\";\n\nexport class WorkspaceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WorkspaceError\";\n }\n}\n\nexport interface WorkspaceResult {\n workspace: string;\n targetBranch: string;\n diffBaseSha: string | null;\n isTemporary: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// CI detection\n// ---------------------------------------------------------------------------\n\ninterface CiWorkspace {\n path: string | null;\n targetBranch: string | null;\n diffBaseSha: string | null;\n}\n\nfunction envOrNull(name: string): string | null {\n const value = process.env[name]?.trim();\n return value ? value : null;\n}\n\nasync function detectCiWorkspace(owner: string, repo: string): Promise<CiWorkspace> {\n const expected = `${owner}/${repo}`;\n\n // GitLab CI\n if (process.env.GITLAB_CI === \"true\") {\n const projectDir = envOrNull(\"CI_PROJECT_DIR\");\n const projectPath = envOrNull(\"CI_PROJECT_PATH\");\n const targetBranch = envOrNull(\"CI_MERGE_REQUEST_TARGET_BRANCH_NAME\");\n const diffBaseSha = envOrNull(\"CI_MERGE_REQUEST_DIFF_BASE_SHA\");\n\n if (projectDir && projectPath && (projectPath === expected || projectPath.endsWith(`/${expected}`))) {\n if (await isSameRepo(projectDir, owner, repo)) {\n logger.info(`Detected GitLab CI environment (target: ${targetBranch ?? \"unknown\"})`);\n return { path: projectDir, targetBranch, diffBaseSha };\n }\n logger.warn(\n `Detected GitLab CI for ${projectPath}, but ${projectDir} is not a git checkout of ${expected}; falling back to clone`,\n );\n }\n }\n\n // Gitea Actions / Forgejo Actions\n // Must check BEFORE GITHUB_ACTIONS since Gitea/Forgejo Actions sets GITHUB_ACTIONS=true for compat\n if (process.env.GITEA_ACTIONS === \"true\" || process.env.FORGEJO_ACTIONS === \"true\") {\n const workspaceDir = envOrNull(\"GITHUB_WORKSPACE\");\n const repository = envOrNull(\"GITHUB_REPOSITORY\");\n const baseRef = envOrNull(\"GITHUB_BASE_REF\");\n\n if (workspaceDir && repository === expected) {\n const ciType = process.env.FORGEJO_ACTIONS ? \"Forgejo\" : \"Gitea\";\n if (await isSameRepo(workspaceDir, owner, repo)) {\n logger.info(`Detected ${ciType} Actions environment (base: ${baseRef ?? \"unknown\"})`);\n return { path: workspaceDir, targetBranch: baseRef, diffBaseSha: null };\n }\n logger.warn(\n `Detected ${ciType} Actions for ${repository}, but ${workspaceDir} is not a git checkout of ${expected}; falling back to clone`,\n );\n }\n }\n\n // GitHub Actions\n if (process.env.GITHUB_ACTIONS === \"true\") {\n const workspaceDir = envOrNull(\"GITHUB_WORKSPACE\");\n const repository = envOrNull(\"GITHUB_REPOSITORY\");\n const baseRef = envOrNull(\"GITHUB_BASE_REF\");\n\n if (workspaceDir && repository === expected) {\n if (await isSameRepo(workspaceDir, owner, repo)) {\n logger.info(`Detected GitHub Actions environment (base: ${baseRef ?? \"unknown\"})`);\n return { path: workspaceDir, targetBranch: baseRef, diffBaseSha: null };\n }\n logger.warn(\n `Detected GitHub Actions for ${repository}, but ${workspaceDir} is not a git checkout of ${expected}; falling back to clone`,\n );\n }\n }\n\n return { path: null, targetBranch: null, diffBaseSha: null };\n}\n\n// ---------------------------------------------------------------------------\n// Repo identity check\n// ---------------------------------------------------------------------------\n\nfunction normalizeGitRemotePath(remoteUrl: string): string {\n const trimmed = remoteUrl.trim().replace(/\\.git$/, \"\");\n\n try {\n const url = new URL(trimmed);\n return url.pathname.replace(/^\\/+/, \"\").replace(/\\.git$/, \"\");\n } catch {\n // Not a URL; try common SSH/scp-like forms below.\n }\n\n const scpLikeMatch = trimmed.match(/^[^@\\s]+@[^:\\s]+:(.+)$/);\n if (scpLikeMatch) {\n return scpLikeMatch[1].replace(/^\\/+/, \"\").replace(/\\.git$/, \"\");\n }\n\n return trimmed.replace(/^\\/+/, \"\").replace(/\\.git$/, \"\");\n}\n\n/**\n * Check if workspace is already cloned from the expected owner/repo.\n * Parses the remote URL to compare exact owner/repo, avoiding substring false positives.\n */\nasync function isSameRepo(\n workspace: string,\n owner: string,\n repo: string,\n): Promise<boolean> {\n try {\n const { stdout } = await exec(\"git\", [\"remote\", \"get-url\", \"origin\"], { cwd: workspace });\n const remoteUrl = stdout.trim();\n const expectedPath = `${owner}/${repo}`.replace(/\\.git$/, \"\");\n const remotePath = normalizeGitRemotePath(remoteUrl);\n return remotePath === expectedPath || remotePath.toLowerCase() === expectedPath.toLowerCase();\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// GitHub helpers\n// ---------------------------------------------------------------------------\n\nasync function getGithubBaseBranch(workspace: string, prNumber: string): Promise<string> {\n try {\n const prInfo = await execJson<Record<string, string>>(\n \"gh\",\n [\"pr\", \"view\", prNumber, \"--json\", \"headRefName,baseRefName\"],\n { cwd: workspace },\n );\n const baseBranch = prInfo.baseRefName ?? \"main\";\n logger.info(`Base branch: ${baseBranch}`);\n return baseBranch;\n } catch {\n logger.warn(\"Could not fetch PR metadata for base branch detection\");\n return \"main\";\n }\n}\n\nasync function fetchAndCheckoutGithubPr(\n workspace: string,\n prNumber: string,\n): Promise<string> {\n logger.info(`Fetching and checking out PR #${prNumber} in existing workspace`);\n await exec(\"git\", [\"fetch\", \"origin\"], { cwd: workspace });\n\n try {\n await exec(\"gh\", [\"pr\", \"checkout\", prNumber], { cwd: workspace });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to checkout PR #${prNumber}: ${msg}`);\n }\n\n return getGithubBaseBranch(workspace, prNumber);\n}\n\nasync function cloneAndCheckoutGithubPr(\n workspace: string,\n owner: string,\n repo: string,\n prNumber: string,\n): Promise<string> {\n logger.info(`Setting up GitHub workspace for ${owner}/${repo}/pull/${prNumber}`);\n\n try {\n await exec(\"gh\", [\"version\"]);\n } catch {\n throw new WorkspaceError(\"GitHub CLI (gh) is not available. Install it: https://cli.github.com\");\n }\n\n logger.info(`Cloning repository ${owner}/${repo}...`);\n try {\n await exec(\"gh\", [\"repo\", \"clone\", `${owner}/${repo}`, workspace]);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to clone repository ${owner}/${repo}: ${msg}`);\n }\n\n logger.info(`Checking out PR #${prNumber}...`);\n try {\n await exec(\"gh\", [\"pr\", \"checkout\", prNumber], { cwd: workspace });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to checkout PR #${prNumber}: ${msg}`);\n }\n\n return getGithubBaseBranch(workspace, prNumber);\n}\n\n// ---------------------------------------------------------------------------\n// GitLab helpers\n// ---------------------------------------------------------------------------\n\nasync function getGitlabMrBranches(\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<{ sourceBranch: string; targetBranch: string }> {\n const gitlabHost = host || process.env.GITLAB_HOST || \"gitlab.com\";\n let mrInfo: MrMetadata;\n try {\n mrInfo = await fetchGitlabMrInfo(owner, repo, Number(prNumber), gitlabHost);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to fetch MR info for !${prNumber}: ${msg}`);\n }\n\n const sourceBranch = mrInfo.source_branch;\n if (!sourceBranch) {\n throw new WorkspaceError(`Could not determine source branch for MR !${prNumber}`);\n }\n\n return { sourceBranch, targetBranch: mrInfo.target_branch ?? \"main\" };\n}\n\nasync function checkoutGitlabBranch(workspace: string, sourceBranch: string): Promise<void> {\n try {\n await exec(\"git\", [\"checkout\", \"-b\", sourceBranch, `origin/${sourceBranch}`], {\n cwd: workspace,\n });\n } catch {\n try {\n await exec(\"git\", [\"checkout\", sourceBranch], { cwd: workspace });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to checkout MR branch '${sourceBranch}': ${msg}`);\n }\n }\n}\n\nasync function fetchAndCheckoutGitlabMr(\n workspace: string,\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<string> {\n logger.info(`Fetching and checking out MR !${prNumber} in existing workspace`);\n await exec(\"git\", [\"fetch\", \"origin\"], { cwd: workspace });\n\n const { sourceBranch, targetBranch } = await getGitlabMrBranches(owner, repo, prNumber, host);\n logger.info(`Source branch: ${sourceBranch}, Target branch: ${targetBranch}`);\n await checkoutGitlabBranch(workspace, sourceBranch);\n\n return targetBranch;\n}\n\nasync function cloneAndCheckoutGitlabMr(\n workspace: string,\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<string> {\n const gitlabHost = host || process.env.GITLAB_HOST || \"gitlab.com\";\n logger.info(`Setting up GitLab workspace for ${owner}/${repo}/merge_requests/${prNumber}`);\n\n try {\n await exec(\"glab\", [\"version\"]);\n } catch {\n throw new WorkspaceError(\n \"GitLab CLI (glab) is not available. Install it: https://gitlab.com/gitlab-org/cli\",\n );\n }\n\n const cloneUrl = `https://${gitlabHost}/${owner}/${repo}.git`;\n logger.info(`Cloning from ${cloneUrl}...`);\n try {\n await exec(\"git\", [\"clone\", cloneUrl, workspace]);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"Permission denied\") || msg.includes(\"publickey\")) {\n throw new WorkspaceError(\n `Failed to clone ${owner}/${repo}: SSH authentication failed. ` +\n `Ensure your SSH key is available (ssh-add) or configure a GITLAB_TOKEN ` +\n `and use HTTPS: git config --global url.\"https://oauth2:$GITLAB_TOKEN@${gitlabHost}/\".insteadOf \"git@${gitlabHost}:\"`,\n );\n }\n throw new WorkspaceError(`Failed to clone ${owner}/${repo}: ${msg}`);\n }\n\n const { sourceBranch, targetBranch } = await getGitlabMrBranches(owner, repo, prNumber, host);\n logger.info(`Source branch: ${sourceBranch}, Target branch: ${targetBranch}`);\n await checkoutGitlabBranch(workspace, sourceBranch);\n\n return targetBranch;\n}\n\n// ---------------------------------------------------------------------------\n// Gitea helpers\n// ---------------------------------------------------------------------------\n\nfunction normalizeGiteaBaseUrl(host?: string): string {\n const giteaHost = host || process.env.GITEA_HOST || process.env.FORGEJO_HOST;\n if (!giteaHost) {\n throw new WorkspaceError(\"No Gitea/Forgejo host configured. Set GITEA_HOST or FORGEJO_HOST.\");\n }\n const trimmed = giteaHost.trim().replace(/\\/+$/, \"\");\n if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n return trimmed;\n }\n return `https://${trimmed}`;\n}\n\nfunction giteaGitArgs(args: string[]): string[] {\n const token = process.env.GITEA_TOKEN || process.env.FORGEJO_TOKEN;\n return token ? [\"-c\", `http.extraHeader=Authorization: token ${token}`, ...args] : args;\n}\n\nasync function getGiteaPrCheckoutInfo(\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<GiteaPrCheckoutInfo> {\n try {\n return await fetchGiteaPrCheckoutInfo(owner, repo, Number(prNumber), host);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to fetch PR info for #${prNumber}: ${msg}`);\n }\n}\n\nasync function checkoutGiteaBranch(\n workspace: string,\n prNumber: string,\n checkoutInfo: GiteaPrCheckoutInfo,\n): Promise<void> {\n const { sourceBranch, sourceCloneUrl } = checkoutInfo;\n const localBranch = `hodor-pr-${prNumber}`;\n\n if (sourceCloneUrl) {\n try {\n await exec(\"git\", giteaGitArgs([\"fetch\", sourceCloneUrl, sourceBranch]), { cwd: workspace });\n await exec(\"git\", [\"checkout\", \"-B\", localBranch, \"FETCH_HEAD\"], { cwd: workspace });\n return;\n } catch (err) {\n logger.warn(`Failed to fetch Gitea PR branch from source repo, falling back to origin: ${err}`);\n }\n }\n\n try {\n await exec(\"git\", [\"checkout\", \"-B\", localBranch, `origin/${sourceBranch}`], {\n cwd: workspace,\n });\n } catch {\n try {\n await exec(\"git\", [\"checkout\", sourceBranch], { cwd: workspace });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to checkout PR branch '${sourceBranch}': ${msg}`);\n }\n }\n}\n\nasync function fetchAndCheckoutGiteaPr(\n workspace: string,\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<string> {\n logger.info(`Fetching and checking out PR #${prNumber} in existing workspace`);\n await exec(\"git\", giteaGitArgs([\"fetch\", \"origin\"]), { cwd: workspace });\n\n const checkoutInfo = await getGiteaPrCheckoutInfo(owner, repo, prNumber, host);\n logger.info(`Source branch: ${checkoutInfo.sourceBranch}, Target branch: ${checkoutInfo.targetBranch}`);\n await checkoutGiteaBranch(workspace, prNumber, checkoutInfo);\n\n return checkoutInfo.targetBranch;\n}\n\nasync function cloneAndCheckoutGiteaPr(\n workspace: string,\n owner: string,\n repo: string,\n prNumber: string,\n host?: string,\n): Promise<string> {\n logger.info(`Setting up Gitea workspace for ${owner}/${repo}/pulls/${prNumber}`);\n\n const cloneUrl = `${normalizeGiteaBaseUrl(host)}/${owner}/${repo}.git`;\n logger.info(`Cloning from ${cloneUrl}...`);\n\n try {\n await exec(\"git\", giteaGitArgs([\"clone\", cloneUrl, workspace]));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to clone ${owner}/${repo}: ${msg}`);\n }\n\n const checkoutInfo = await getGiteaPrCheckoutInfo(owner, repo, prNumber, host);\n logger.info(`Source branch: ${checkoutInfo.sourceBranch}, Target branch: ${checkoutInfo.targetBranch}`);\n await checkoutGiteaBranch(workspace, prNumber, checkoutInfo);\n\n return checkoutInfo.targetBranch;\n}\n\n// ---------------------------------------------------------------------------\n// Main entry point\n// ---------------------------------------------------------------------------\n\nexport async function setupWorkspace(opts: {\n platform: Platform;\n owner: string;\n repo: string;\n prNumber: string;\n host?: string;\n workingDir?: string;\n reuse?: boolean;\n}): Promise<WorkspaceResult> {\n const { platform, owner, repo, prNumber, host, workingDir, reuse = true } = opts;\n\n try {\n const ci = await detectCiWorkspace(owner, repo);\n let detectedTargetBranch = ci.targetBranch;\n const detectedDiffBaseSha = ci.diffBaseSha;\n\n let workspace: string;\n let isTemporary = false;\n\n if (ci.path) {\n workspace = ci.path;\n if (platform === \"github\" && !detectedTargetBranch) {\n detectedTargetBranch = await getGithubBaseBranch(workspace, prNumber);\n }\n } else if (!workingDir) {\n workspace = await mkdtemp(join(tmpdir(), \"hodor-review-\"));\n isTemporary = true;\n logger.info(`Created temporary workspace: ${workspace}`);\n } else {\n workspace = workingDir;\n const { mkdir } = await import(\"node:fs/promises\");\n await mkdir(workspace, { recursive: true });\n\n if (reuse && (await isSameRepo(workspace, owner, repo))) {\n logger.info(`Reusing existing workspace: ${workspace}`);\n // Repo already cloned — just fetch and checkout the PR/MR branch\n if (platform === \"github\") {\n const tb = await fetchAndCheckoutGithubPr(workspace, prNumber);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n } else if (platform === \"gitlab\") {\n const tb = await fetchAndCheckoutGitlabMr(workspace, owner, repo, prNumber, host);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n } else if (platform === \"gitea\") {\n const tb = await fetchAndCheckoutGiteaPr(workspace, owner, repo, prNumber, host);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n }\n const finalTargetBranch = detectedTargetBranch ?? \"main\";\n logger.info(\n `Workspace ready at: ${workspace} (target: ${finalTargetBranch}, ` +\n `diff_base_sha: ${detectedDiffBaseSha?.slice(0, 8) ?? \"N/A\"})`,\n );\n return { workspace, targetBranch: finalTargetBranch, diffBaseSha: detectedDiffBaseSha, isTemporary: false };\n }\n }\n\n if (!ci.path) {\n if (platform === \"github\") {\n const tb = await cloneAndCheckoutGithubPr(workspace, owner, repo, prNumber);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n } else if (platform === \"gitlab\") {\n const tb = await cloneAndCheckoutGitlabMr(workspace, owner, repo, prNumber, host);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n } else if (platform === \"gitea\") {\n const tb = await cloneAndCheckoutGiteaPr(workspace, owner, repo, prNumber, host);\n if (!detectedTargetBranch) detectedTargetBranch = tb;\n } else {\n throw new WorkspaceError(`Unsupported platform: ${platform}`);\n }\n }\n\n const finalTargetBranch = detectedTargetBranch ?? \"main\";\n logger.info(\n `Workspace ready at: ${workspace} (target: ${finalTargetBranch}, ` +\n `diff_base_sha: ${detectedDiffBaseSha?.slice(0, 8) ?? \"N/A\"})`,\n );\n return { workspace, targetBranch: finalTargetBranch, diffBaseSha: detectedDiffBaseSha, isTemporary };\n } catch (err) {\n if (err instanceof WorkspaceError) throw err;\n const msg = err instanceof Error ? err.message : String(err);\n throw new WorkspaceError(`Failed to setup workspace: ${msg}`);\n }\n}\n\nexport async function cleanupWorkspace(workspace: string): Promise<void> {\n try {\n await rm(workspace, { recursive: true, force: true });\n logger.info(`Cleaned up workspace: ${workspace}`);\n } catch (err) {\n logger.warn(`Failed to cleanup workspace ${workspace}: ${err}`);\n }\n}\n","export const REVIEW_SYSTEM_PROMPT = `You are a code review agent. You analyze pull request diffs to find production bugs.\n\n<ROLE>\n* You are in READ-ONLY mode. Do NOT modify any files, create files, commit, or install dependencies.\n* Your only job is to analyze the diff, identify bugs, and produce a review.\n* Submit the final review via the \\`submit_review\\` tool. Do NOT output the final review as normal assistant text.\n* Be proportional: scale your analysis depth to the diff size. A small, single-file diff needs only a few iterations; a large multi-file refactor warrants deeper investigation.\n* Do NOT write to PLAN.md or AGENTS.md.\n* Do NOT run package managers (npm install, go mod download, pip install, etc.).\n* Follow the instructions in the user prompt exactly as given.\n</ROLE>\n\n<EFFICIENCY>\n* Combine multiple bash commands where possible (e.g. \\`cmd1 && cmd2\\`).\n* Use the grep and find tools for code search — do not shell out to grep/find.\n* Prefer \\`git diff\\` to see changes for specific files. Only use read when you need surrounding context that the diff alone cannot provide.\n* Do not use cat/head/tail to read files.\n* Keep reasoning proportional to the task. A small diff does not need extensive deliberation.\n</EFFICIENCY>`;\n"],"mappings":"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","names":["join","finalTargetBranch","formatToolArgs","formatToolResult","join"]}