@mrkaran/hodor 0.6.2-rc.2 → 0.6.2-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MRMLGIXO.js → chunk-LQDQ7CHL.js} +24 -4
- package/dist/chunk-LQDQ7CHL.js.map +1 -0
- package/dist/cli.js +22 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-MRMLGIXO.js.map +0 -1
|
@@ -1869,7 +1869,9 @@ async function reviewPr(opts) {
|
|
|
1869
1869
|
onEvent,
|
|
1870
1870
|
bedrockTags,
|
|
1871
1871
|
localMode = false,
|
|
1872
|
-
diffAgainst
|
|
1872
|
+
diffAgainst,
|
|
1873
|
+
full = false,
|
|
1874
|
+
targetBranchOverride
|
|
1873
1875
|
} = opts;
|
|
1874
1876
|
logger.info(`Starting PR review for: ${localMode ? "local diff" : prUrl}`);
|
|
1875
1877
|
let owner = "", repo = "", host = "";
|
|
@@ -1988,6 +1990,22 @@ async function reviewPr(opts) {
|
|
|
1988
1990
|
diffBaseSha = wsResult.diffBaseSha;
|
|
1989
1991
|
isTemporary = wsResult.isTemporary;
|
|
1990
1992
|
}
|
|
1993
|
+
if (!localMode && full && targetBranchOverride) {
|
|
1994
|
+
logger.info(`Full review: overriding target branch to '${targetBranchOverride}'`);
|
|
1995
|
+
try {
|
|
1996
|
+
await exec("git", ["fetch", "--quiet", "origin", targetBranchOverride], { cwd: workspacePath });
|
|
1997
|
+
} catch (err) {
|
|
1998
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1999
|
+
throw new Error(`Failed to fetch --target-branch '${targetBranchOverride}' from origin for --full review: ${msg}`);
|
|
2000
|
+
}
|
|
2001
|
+
try {
|
|
2002
|
+
await exec("git", ["rev-parse", "--verify", "--quiet", `origin/${targetBranchOverride}`], { cwd: workspacePath });
|
|
2003
|
+
} catch {
|
|
2004
|
+
throw new Error(`--target-branch 'origin/${targetBranchOverride}' not found after fetch; cannot run --full review against it.`);
|
|
2005
|
+
}
|
|
2006
|
+
targetBranch = targetBranchOverride;
|
|
2007
|
+
diffBaseSha = null;
|
|
2008
|
+
}
|
|
1991
2009
|
let activeSession;
|
|
1992
2010
|
try {
|
|
1993
2011
|
let formatToolArgs2 = function(_toolName, args) {
|
|
@@ -2039,8 +2057,10 @@ async function reviewPr(opts) {
|
|
|
2039
2057
|
logger.warn(`Failed to fetch Gitea metadata: ${err}`);
|
|
2040
2058
|
}
|
|
2041
2059
|
}
|
|
2042
|
-
const previousReviewSha = await findLatestValidReviewSha(mrMetadata?.Notes, workspacePath);
|
|
2043
|
-
if (
|
|
2060
|
+
const previousReviewSha = full ? null : await findLatestValidReviewSha(mrMetadata?.Notes, workspacePath);
|
|
2061
|
+
if (full) {
|
|
2062
|
+
logger.info("Full review mode: ignoring previous hodor reviews, diffing entire source-vs-target range");
|
|
2063
|
+
} else if (previousReviewSha) {
|
|
2044
2064
|
logger.info(`Incremental mode: previous review at ${previousReviewSha.slice(0, 8)}`);
|
|
2045
2065
|
}
|
|
2046
2066
|
let headSha = null;
|
|
@@ -2347,4 +2367,4 @@ export {
|
|
|
2347
2367
|
postReviewStructured,
|
|
2348
2368
|
reviewPr
|
|
2349
2369
|
};
|
|
2350
|
-
//# sourceMappingURL=chunk-
|
|
2370
|
+
//# sourceMappingURL=chunk-LQDQ7CHL.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 */\n// NOTE: There is no \"max\" thinking level yet. The Anthropic API (Opus 4.7+) and\n// upcoming OpenAI models expose a \"max\" reasoning tier, but the pi-ai SDK's\n// ThinkingLevel union tops out at \"xhigh\", so \"max\" is unreachable from here.\n// Tracked upstream: https://github.com/earendil-works/pi/issues/6097\n// When pi-ai adds \"max\", add a case below and bump the full-review job to use it.\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 full?: boolean;\n targetBranchOverride?: 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 full = false,\n targetBranchOverride,\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 // --full with an explicit target overrides the detected base. Drop the CI\n // merge-base SHA so the diff uses origin/<target>...HEAD against the given ref.\n // CI clones don't fetch arbitrary branches, so fetch-and-verify the ref first\n // and fail loudly rather than silently reviewing against a missing base.\n if (!localMode && full && targetBranchOverride) {\n logger.info(`Full review: overriding target branch to '${targetBranchOverride}'`);\n try {\n await exec(\"git\", [\"fetch\", \"--quiet\", \"origin\", targetBranchOverride], { cwd: workspacePath });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to fetch --target-branch '${targetBranchOverride}' from origin for --full review: ${msg}`);\n }\n try {\n await exec(\"git\", [\"rev-parse\", \"--verify\", \"--quiet\", `origin/${targetBranchOverride}`], { cwd: workspacePath });\n } catch {\n throw new Error(`--target-branch 'origin/${targetBranchOverride}' not found after fetch; cannot run --full review against it.`);\n }\n targetBranch = targetBranchOverride;\n diffBaseSha = null;\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. --full skips this entirely so the review\n // always covers the whole source-vs-target diff.\n const previousReviewSha = full\n ? null\n : await findLatestValidReviewSha(mrMetadata?.Notes, workspacePath);\n if (full) {\n logger.info(\"Full review mode: ignoring previous hodor reviews, diffing entire source-vs-target range\");\n } else 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-
|
|
10
|
+
} from "./chunk-LQDQ7CHL.js";
|
|
11
11
|
import {
|
|
12
12
|
renderMarkdown,
|
|
13
13
|
setLogLevel
|
|
@@ -67,6 +67,13 @@ program.name("hodor").description(
|
|
|
67
67
|
"--diff-against <ref>",
|
|
68
68
|
"Git ref to diff against in local mode (e.g., origin/main, HEAD~1)",
|
|
69
69
|
"origin/main"
|
|
70
|
+
).option(
|
|
71
|
+
"--full",
|
|
72
|
+
"Force a full review of the entire source-vs-target diff, ignoring any previous hodor reviews on the MR/PR (disables incremental mode)",
|
|
73
|
+
false
|
|
74
|
+
).option(
|
|
75
|
+
"--target-branch <ref>",
|
|
76
|
+
"Override the target branch to diff against for a full review (default: the MR/PR's target branch). Only used with --full."
|
|
70
77
|
).action(async (prUrl, cmdOpts) => {
|
|
71
78
|
const verbose = cmdOpts.verbose;
|
|
72
79
|
const post = cmdOpts.post;
|
|
@@ -83,6 +90,8 @@ program.name("hodor").description(
|
|
|
83
90
|
const prometheusPush = cmdOpts.prometheusPush;
|
|
84
91
|
const localMode = cmdOpts.local;
|
|
85
92
|
const diffAgainst = cmdOpts.diffAgainst;
|
|
93
|
+
const full = cmdOpts.full;
|
|
94
|
+
const targetBranchOverride = cmdOpts.targetBranch;
|
|
86
95
|
if (!localMode && !prUrl) {
|
|
87
96
|
console.error(chalk.red("Error: pr-url is required unless --local is specified"));
|
|
88
97
|
process.exit(1);
|
|
@@ -95,6 +104,12 @@ program.name("hodor").description(
|
|
|
95
104
|
console.error(chalk.red("Error: --review-style must be one of: summary, inline, hybrid"));
|
|
96
105
|
process.exit(1);
|
|
97
106
|
}
|
|
107
|
+
if (targetBranchOverride && !full) {
|
|
108
|
+
console.error(chalk.yellow("Warning: --target-branch is only used with --full; ignoring it."));
|
|
109
|
+
}
|
|
110
|
+
if (full && localMode) {
|
|
111
|
+
console.error(chalk.yellow("Warning: --full has no effect in --local mode (local reviews are always full)."));
|
|
112
|
+
}
|
|
98
113
|
const isCI = !!(process.env.CI || process.env.GITLAB_CI || process.env.GITHUB_ACTIONS || process.env.GITEA_ACTIONS || process.env.FORGEJO_ACTIONS);
|
|
99
114
|
if (verbose) setLogLevel("debug");
|
|
100
115
|
else if (isCI) setLogLevel("info");
|
|
@@ -213,6 +228,9 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
|
|
|
213
228
|
} else {
|
|
214
229
|
log(chalk.dim(`Platform: ${platform.toUpperCase()}`));
|
|
215
230
|
log(chalk.dim(`PR URL: ${prUrl}`));
|
|
231
|
+
if (full) {
|
|
232
|
+
log(chalk.dim(`Mode: Full review (source vs ${targetBranchOverride ?? "target branch"}, incremental disabled)`));
|
|
233
|
+
}
|
|
216
234
|
}
|
|
217
235
|
log(chalk.dim(`Model: ${model}`));
|
|
218
236
|
if (reasoningEffort) {
|
|
@@ -232,7 +250,9 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
|
|
|
232
250
|
onEvent: handleEvent,
|
|
233
251
|
bedrockTags,
|
|
234
252
|
localMode,
|
|
235
|
-
diffAgainst
|
|
253
|
+
diffAgainst,
|
|
254
|
+
full,
|
|
255
|
+
targetBranchOverride
|
|
236
256
|
});
|
|
237
257
|
const reviewText = renderMarkdown(review);
|
|
238
258
|
streamLog(chalk.green("\u2714 Review complete!"));
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport \"dotenv/config\";\n\nimport { detectPlatform, parsePrUrl, postGitlabReviewCommitStatus, postReviewComment, postReviewStructured, reviewPr } from \"./agent.js\";\nimport type { AgentProgressEvent } from \"./agent.js\";\nimport type { PostCommentResult } from \"./types.js\";\nimport { renderMarkdown } from \"./render.js\";\nimport { pushMetrics } from \"./metrics.js\";\nimport { setLogLevel } from \"./utils/logger.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"hodor\")\n .description(\n \"AI-powered code review agent for GitHub PRs, GitLab MRs, Gitea/Forgejo PRs, and local diffs.\\n\\n\" +\n \"Hodor uses an AI agent that clones the repository, checks out the PR branch,\\n\" +\n \"and analyzes the code using tools (gh, git, glab) for metadata fetching and comment posting.\\n\\n\" +\n \"For local reviews, use --local with --diff-against to review changes in your current git repository.\",\n )\n .version(\"0.6.1\")\n .argument(\"[pr-url]\", \"URL of the GitHub PR, GitLab MR, or Gitea/Forgejo PR to review (optional with --local)\")\n .option(\n \"--model <model>\",\n \"LLM model to use as provider/model-id (e.g., anthropic/claude-sonnet-4-5-20250929, openrouter/moonshotai/kimi-k2.6)\",\n \"anthropic/claude-sonnet-4-5-20250929\",\n )\n .option(\n \"--reasoning-effort <level>\",\n \"Reasoning effort level: minimal, low, medium, high, xhigh\",\n )\n .option(\"-v, --verbose\", \"Enable verbose logging\", false)\n .option(\n \"--post\",\n \"Post the review directly to the PR/MR as a comment\",\n false,\n )\n .option(\"--prompt <text>\", \"Custom inline prompt text\")\n .option(\n \"--prompt-file <path>\",\n \"Path to file containing custom prompt instructions\",\n )\n .option(\n \"--workspace <dir>\",\n \"Workspace directory (creates temp dir if not specified)\",\n )\n .option(\n \"--review-style <style>\",\n \"How to post reviews on GitLab: summary (single comment), inline (diff comments), hybrid (both). Default: hybrid.\",\n \"hybrid\",\n )\n .option(\n \"--code-quality <path>\",\n \"Write a gl-code-quality-report.json CodeClimate artifact to this path\",\n )\n .option(\n \"--commit-status\",\n \"Post a pass/fail commit status to the MR head SHA\",\n false,\n )\n .option(\n \"--ultrathink\",\n \"Enable maximum reasoning effort with extended thinking budget\",\n false,\n )\n .option(\n \"--bedrock-tags <json>\",\n \"JSON object of cost allocation tags for Bedrock requests (e.g., '{\\\"team\\\":\\\"platform\\\"}')\",\n )\n .option(\n \"--prometheus-push <url>\",\n \"Push review metrics to a Prometheus Pushgateway URL\",\n )\n .option(\n \"--local\",\n \"Review local changes in the current directory (no PR URL required)\",\n false,\n )\n .option(\n \"--diff-against <ref>\",\n \"Git ref to diff against in local mode (e.g., origin/main, HEAD~1)\",\n \"origin/main\",\n )\n .action(async (prUrl: string | undefined, cmdOpts: Record<string, unknown>) => {\n const verbose = cmdOpts.verbose as boolean;\n const post = cmdOpts.post as boolean;\n const model = cmdOpts.model as string;\n let reasoningEffort = cmdOpts.reasoningEffort as string | undefined;\n const prompt = cmdOpts.prompt as string | undefined;\n const promptFile = cmdOpts.promptFile as string | undefined;\n const workspace = cmdOpts.workspace as string | undefined;\n const reviewStyle = cmdOpts.reviewStyle as \"summary\" | \"inline\" | \"hybrid\" | undefined;\n const codeQuality = cmdOpts.codeQuality as string | undefined;\n const commitStatus = cmdOpts.commitStatus as boolean;\n const ultrathink = cmdOpts.ultrathink as boolean;\n const bedrockTagsRaw = cmdOpts.bedrockTags as string | undefined;\n const prometheusPush = cmdOpts.prometheusPush as string | undefined;\n const localMode = cmdOpts.local as boolean;\n const diffAgainst = cmdOpts.diffAgainst as string;\n\n if (!localMode && !prUrl) {\n console.error(chalk.red(\"Error: pr-url is required unless --local is specified\"));\n process.exit(1);\n }\n if (localMode && post) {\n console.error(chalk.red(\"Error: --post is not supported in --local mode (no remote to post to)\"));\n process.exit(1);\n }\n if (![\"summary\", \"inline\", \"hybrid\"].includes(reviewStyle ?? \"hybrid\")) {\n console.error(chalk.red(\"Error: --review-style must be one of: summary, inline, hybrid\"));\n process.exit(1);\n }\n\n // Auto-detect CI environment\n const isCI = !!(process.env.CI || process.env.GITLAB_CI || process.env.GITHUB_ACTIONS || process.env.GITEA_ACTIONS || process.env.FORGEJO_ACTIONS);\n\n if (verbose) setLogLevel(\"debug\");\n else if (isCI) setLogLevel(\"info\");\n\n // Handle ultrathink\n if (ultrathink) {\n reasoningEffort = \"xhigh\";\n }\n\n // Parse Bedrock cost allocation tags\n let bedrockTags: Record<string, string> | null = null;\n if (bedrockTagsRaw) {\n try {\n bedrockTags = JSON.parse(bedrockTagsRaw) as Record<string, string>;\n } catch {\n console.error(chalk.red(\"Error: --bedrock-tags must be valid JSON\"));\n process.exit(1);\n }\n }\n\n const log = console.log;\n const logStream = process.stdout;\n\n const toolIcons: Record<string, string> = {\n bash: \"$\",\n read: \"cat\",\n grep: \"grep\",\n find: \"find\",\n ls: \"ls\",\n };\n\n /** Write a line to the log stream */\n function streamLog(msg: string): void {\n logStream.write(`${msg}\\n`);\n }\n\n /** Write inline text (no newline) for streaming deltas */\n function streamWrite(text: string): void {\n process.stderr.write(text);\n }\n\n function handleEvent(event: AgentProgressEvent): void {\n switch (event.type) {\n case \"agent_start\":\n streamLog(chalk.dim(\"▶ Agent started\"));\n break;\n case \"turn_start\":\n streamLog(chalk.dim(`\\n── Turn ${event.turnIndex ?? \"?\"} ──`));\n break;\n case \"tool_start\": {\n const icon = toolIcons[event.toolName ?? \"\"] ?? event.toolName;\n const preview = event.toolArgs ? ` ${event.toolArgs}` : \"\";\n const maxLen = 160;\n const truncated = preview.length > maxLen ? preview.slice(0, maxLen) + \"…\" : preview;\n streamLog(chalk.green(` ${icon}${truncated}`));\n break;\n }\n case \"tool_end\": {\n if (event.isError) {\n streamLog(chalk.red(` ✗ error`));\n }\n if (event.result) {\n const lines = event.result.split(\"\\n\");\n const maxLines = verbose ? 15 : 6;\n const maxChars = verbose ? 400 : 200;\n let chars = 0;\n for (let i = 0; i < Math.min(lines.length, maxLines); i++) {\n const line = lines[i];\n if (chars + line.length > maxChars) {\n streamLog(chalk.dim(` …(${lines.length - i} more lines)`));\n break;\n }\n streamLog(chalk.dim(` ${line}`));\n chars += line.length;\n }\n }\n break;\n }\n case \"text_delta\":\n if (verbose && event.delta) {\n streamWrite(event.delta);\n }\n break;\n case \"thinking_delta\":\n // Only show reasoning in verbose mode\n if (verbose && event.delta) {\n streamWrite(chalk.dim(event.delta));\n }\n break;\n case \"agent_end\":\n streamLog(chalk.dim(\"\\n▶ Extracting review...\"));\n break;\n }\n }\n\n try {\n // Detect platform and warn about missing tokens\n let platform: string = \"local\";\n let metricsProject: string | undefined;\n let metricsMrIid: string | undefined;\n if (!localMode && prUrl) {\n platform = detectPlatform(prUrl);\n const parsedPr = parsePrUrl(prUrl);\n metricsProject = `${parsedPr.owner}/${parsedPr.repo}`;\n metricsMrIid = String(parsedPr.prNumber);\n const githubToken = process.env.GITHUB_TOKEN;\n const gitlabToken =\n process.env.GITLAB_TOKEN ??\n process.env.GITLAB_PRIVATE_TOKEN ??\n process.env.CI_JOB_TOKEN;\n\n if (platform === \"github\" && !githubToken) {\n console.error(chalk.yellow(\"Warning: GITHUB_TOKEN not set. You may encounter rate limits.\"));\n console.error(chalk.dim(\" Set GITHUB_TOKEN or run: gh auth login\\n\"));\n } else if (platform === \"gitlab\" && !gitlabToken) {\n console.error(chalk.yellow(\"Warning: No GitLab token detected. Set GITLAB_TOKEN (api scope).\"));\n console.error(chalk.dim(\" Export GITLAB_TOKEN and optionally GITLAB_HOST.\\n\"));\n } else if (platform === \"gitea\") {\n const giteaToken = process.env.GITEA_TOKEN ?? process.env.FORGEJO_TOKEN;\n if (!giteaToken) {\n console.error(chalk.yellow(\"Warning: No Gitea/Forgejo token detected. Set GITEA_TOKEN for authentication.\"));\n console.error(chalk.dim(\" Export GITEA_TOKEN (or FORGEJO_TOKEN) for API access.\\n\"));\n }\n }\n }\n\n log(`\\n${chalk.bold.cyan(\"Hodor - AI Code Review Agent\")}`);\n if (localMode) {\n log(chalk.dim(`Mode: Local diff review`));\n log(chalk.dim(`Diff against: ${diffAgainst}`));\n log(chalk.dim(`Workspace: ${workspace ?? process.cwd()}`));\n } else {\n log(chalk.dim(`Platform: ${platform.toUpperCase()}`));\n log(chalk.dim(`PR URL: ${prUrl}`));\n }\n log(chalk.dim(`Model: ${model}`));\n if (reasoningEffort) {\n log(chalk.dim(`Reasoning Effort: ${reasoningEffort}`));\n }\n log();\n\n streamLog(chalk.dim(\"▶ Setting up workspace...\"));\n const { review, metricsFooter, headSha, metrics, workspacePath } = await reviewPr({\n prUrl: localMode ? undefined : prUrl,\n model,\n reasoningEffort,\n customPrompt: prompt,\n promptFile,\n cleanup: !workspace,\n workspaceDir: workspace,\n includeMetricsFooter: post && !localMode,\n onEvent: handleEvent,\n bedrockTags,\n localMode,\n diffAgainst,\n });\n const reviewText = renderMarkdown(review);\n\n streamLog(chalk.green(\"✔ Review complete!\"));\n\n if (codeQuality) {\n try {\n const { formatCodeQualityReport } = await import(\"./codequality.js\");\n const { writeFileSync } = await import(\"node:fs\");\n writeFileSync(\n codeQuality,\n formatCodeQualityReport(review, process.env.CI_PROJECT_DIR ?? workspacePath),\n \"utf-8\",\n );\n log(chalk.dim(`Wrote code quality report to ${codeQuality}`));\n } catch (err) {\n log(chalk.yellow(`Failed to write code quality report: ${err}`));\n }\n }\n\n if (post && prUrl) {\n log(chalk.cyan(\"\\nPosting review to PR/MR...\"));\n\n const platform = detectPlatform(prUrl);\n const useStructured = platform === \"gitlab\" && reviewStyle !== \"summary\";\n\n let result: PostCommentResult;\n if (useStructured) {\n result = await postReviewStructured({\n prUrl,\n review,\n model,\n metricsFooter,\n reviewStyle: reviewStyle ?? \"hybrid\",\n commitStatus,\n headSha,\n workspacePath,\n });\n } else {\n result = await postReviewComment({\n prUrl,\n reviewText,\n model,\n metricsFooter,\n headSha,\n });\n\n if (platform === \"gitlab\" && commitStatus) {\n try {\n const { getGitlabMrDiffRefs } = await import(\"./gitlab.js\");\n const parsed = parsePrUrl(prUrl);\n const diffRefs = await getGitlabMrDiffRefs(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n parsed.host,\n );\n await postGitlabReviewCommitStatus(parsed, review, diffRefs);\n } catch (err) {\n result = {\n success: false,\n platform: \"gitlab\",\n mrNumber: parsePrUrl(prUrl).prNumber,\n error: `Failed to post commit status: ${err instanceof Error ? err.message : err}`,\n };\n }\n }\n }\n\n if (result.success) {\n log(chalk.bold.green(\"Review posted successfully!\"));\n log(chalk.dim(` ${platform === \"gitlab\" ? \"MR\" : \"PR\"}: ${prUrl}`));\n } else {\n log(chalk.bold.red(`Failed to post review: ${result.error}`));\n log(chalk.yellow(\"\\nReview output:\\n\"));\n console.log(reviewText);\n }\n } else {\n log(chalk.bold.green(\"Review Complete\\n\"));\n console.log(reviewText);\n if (!localMode) {\n log(chalk.dim(\"\\nTip: Use --post to automatically post this review to the PR/MR\"));\n }\n }\n\n // Push metrics to Prometheus Pushgateway (best-effort, never fails the run)\n if (prometheusPush) {\n const labels: Record<string, string> = {\n platform,\n model,\n verdict: review.overall_correctness === \"patch is correct\" ? \"correct\" : \"incorrect\",\n };\n if (metricsProject) labels.project = metricsProject;\n if (metricsMrIid) labels.mr_iid = metricsMrIid;\n\n await pushMetrics({\n pushgatewayUrl: prometheusPush,\n metrics,\n findings: review.findings,\n labels,\n });\n }\n } catch (err) {\n streamLog(chalk.red(\"✗ Review failed\"));\n console.error(\n chalk.bold.red(`\\nError: ${err instanceof Error ? err.message : err}`),\n );\n if (verbose && err instanceof Error && err.stack) {\n console.error(chalk.dim(err.stack));\n }\n process.exit(1);\n }\n });\n\nprogram.parse();\n"],"mappings":"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","names":["platform"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport \"dotenv/config\";\n\nimport { detectPlatform, parsePrUrl, postGitlabReviewCommitStatus, postReviewComment, postReviewStructured, reviewPr } from \"./agent.js\";\nimport type { AgentProgressEvent } from \"./agent.js\";\nimport type { PostCommentResult } from \"./types.js\";\nimport { renderMarkdown } from \"./render.js\";\nimport { pushMetrics } from \"./metrics.js\";\nimport { setLogLevel } from \"./utils/logger.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"hodor\")\n .description(\n \"AI-powered code review agent for GitHub PRs, GitLab MRs, Gitea/Forgejo PRs, and local diffs.\\n\\n\" +\n \"Hodor uses an AI agent that clones the repository, checks out the PR branch,\\n\" +\n \"and analyzes the code using tools (gh, git, glab) for metadata fetching and comment posting.\\n\\n\" +\n \"For local reviews, use --local with --diff-against to review changes in your current git repository.\",\n )\n .version(\"0.6.1\")\n .argument(\"[pr-url]\", \"URL of the GitHub PR, GitLab MR, or Gitea/Forgejo PR to review (optional with --local)\")\n .option(\n \"--model <model>\",\n \"LLM model to use as provider/model-id (e.g., anthropic/claude-sonnet-4-5-20250929, openrouter/moonshotai/kimi-k2.6)\",\n \"anthropic/claude-sonnet-4-5-20250929\",\n )\n .option(\n \"--reasoning-effort <level>\",\n \"Reasoning effort level: minimal, low, medium, high, xhigh\",\n )\n .option(\"-v, --verbose\", \"Enable verbose logging\", false)\n .option(\n \"--post\",\n \"Post the review directly to the PR/MR as a comment\",\n false,\n )\n .option(\"--prompt <text>\", \"Custom inline prompt text\")\n .option(\n \"--prompt-file <path>\",\n \"Path to file containing custom prompt instructions\",\n )\n .option(\n \"--workspace <dir>\",\n \"Workspace directory (creates temp dir if not specified)\",\n )\n .option(\n \"--review-style <style>\",\n \"How to post reviews on GitLab: summary (single comment), inline (diff comments), hybrid (both). Default: hybrid.\",\n \"hybrid\",\n )\n .option(\n \"--code-quality <path>\",\n \"Write a gl-code-quality-report.json CodeClimate artifact to this path\",\n )\n .option(\n \"--commit-status\",\n \"Post a pass/fail commit status to the MR head SHA\",\n false,\n )\n .option(\n \"--ultrathink\",\n \"Enable maximum reasoning effort with extended thinking budget\",\n false,\n )\n .option(\n \"--bedrock-tags <json>\",\n \"JSON object of cost allocation tags for Bedrock requests (e.g., '{\\\"team\\\":\\\"platform\\\"}')\",\n )\n .option(\n \"--prometheus-push <url>\",\n \"Push review metrics to a Prometheus Pushgateway URL\",\n )\n .option(\n \"--local\",\n \"Review local changes in the current directory (no PR URL required)\",\n false,\n )\n .option(\n \"--diff-against <ref>\",\n \"Git ref to diff against in local mode (e.g., origin/main, HEAD~1)\",\n \"origin/main\",\n )\n .option(\n \"--full\",\n \"Force a full review of the entire source-vs-target diff, ignoring any previous hodor reviews on the MR/PR (disables incremental mode)\",\n false,\n )\n .option(\n \"--target-branch <ref>\",\n \"Override the target branch to diff against for a full review (default: the MR/PR's target branch). Only used with --full.\",\n )\n .action(async (prUrl: string | undefined, cmdOpts: Record<string, unknown>) => {\n const verbose = cmdOpts.verbose as boolean;\n const post = cmdOpts.post as boolean;\n const model = cmdOpts.model as string;\n let reasoningEffort = cmdOpts.reasoningEffort as string | undefined;\n const prompt = cmdOpts.prompt as string | undefined;\n const promptFile = cmdOpts.promptFile as string | undefined;\n const workspace = cmdOpts.workspace as string | undefined;\n const reviewStyle = cmdOpts.reviewStyle as \"summary\" | \"inline\" | \"hybrid\" | undefined;\n const codeQuality = cmdOpts.codeQuality as string | undefined;\n const commitStatus = cmdOpts.commitStatus as boolean;\n const ultrathink = cmdOpts.ultrathink as boolean;\n const bedrockTagsRaw = cmdOpts.bedrockTags as string | undefined;\n const prometheusPush = cmdOpts.prometheusPush as string | undefined;\n const localMode = cmdOpts.local as boolean;\n const diffAgainst = cmdOpts.diffAgainst as string;\n const full = cmdOpts.full as boolean;\n const targetBranchOverride = cmdOpts.targetBranch as string | undefined;\n\n if (!localMode && !prUrl) {\n console.error(chalk.red(\"Error: pr-url is required unless --local is specified\"));\n process.exit(1);\n }\n if (localMode && post) {\n console.error(chalk.red(\"Error: --post is not supported in --local mode (no remote to post to)\"));\n process.exit(1);\n }\n if (![\"summary\", \"inline\", \"hybrid\"].includes(reviewStyle ?? \"hybrid\")) {\n console.error(chalk.red(\"Error: --review-style must be one of: summary, inline, hybrid\"));\n process.exit(1);\n }\n if (targetBranchOverride && !full) {\n console.error(chalk.yellow(\"Warning: --target-branch is only used with --full; ignoring it.\"));\n }\n if (full && localMode) {\n console.error(chalk.yellow(\"Warning: --full has no effect in --local mode (local reviews are always full).\"));\n }\n\n // Auto-detect CI environment\n const isCI = !!(process.env.CI || process.env.GITLAB_CI || process.env.GITHUB_ACTIONS || process.env.GITEA_ACTIONS || process.env.FORGEJO_ACTIONS);\n\n if (verbose) setLogLevel(\"debug\");\n else if (isCI) setLogLevel(\"info\");\n\n // Handle ultrathink\n if (ultrathink) {\n reasoningEffort = \"xhigh\";\n }\n\n // Parse Bedrock cost allocation tags\n let bedrockTags: Record<string, string> | null = null;\n if (bedrockTagsRaw) {\n try {\n bedrockTags = JSON.parse(bedrockTagsRaw) as Record<string, string>;\n } catch {\n console.error(chalk.red(\"Error: --bedrock-tags must be valid JSON\"));\n process.exit(1);\n }\n }\n\n const log = console.log;\n const logStream = process.stdout;\n\n const toolIcons: Record<string, string> = {\n bash: \"$\",\n read: \"cat\",\n grep: \"grep\",\n find: \"find\",\n ls: \"ls\",\n };\n\n /** Write a line to the log stream */\n function streamLog(msg: string): void {\n logStream.write(`${msg}\\n`);\n }\n\n /** Write inline text (no newline) for streaming deltas */\n function streamWrite(text: string): void {\n process.stderr.write(text);\n }\n\n function handleEvent(event: AgentProgressEvent): void {\n switch (event.type) {\n case \"agent_start\":\n streamLog(chalk.dim(\"▶ Agent started\"));\n break;\n case \"turn_start\":\n streamLog(chalk.dim(`\\n── Turn ${event.turnIndex ?? \"?\"} ──`));\n break;\n case \"tool_start\": {\n const icon = toolIcons[event.toolName ?? \"\"] ?? event.toolName;\n const preview = event.toolArgs ? ` ${event.toolArgs}` : \"\";\n const maxLen = 160;\n const truncated = preview.length > maxLen ? preview.slice(0, maxLen) + \"…\" : preview;\n streamLog(chalk.green(` ${icon}${truncated}`));\n break;\n }\n case \"tool_end\": {\n if (event.isError) {\n streamLog(chalk.red(` ✗ error`));\n }\n if (event.result) {\n const lines = event.result.split(\"\\n\");\n const maxLines = verbose ? 15 : 6;\n const maxChars = verbose ? 400 : 200;\n let chars = 0;\n for (let i = 0; i < Math.min(lines.length, maxLines); i++) {\n const line = lines[i];\n if (chars + line.length > maxChars) {\n streamLog(chalk.dim(` …(${lines.length - i} more lines)`));\n break;\n }\n streamLog(chalk.dim(` ${line}`));\n chars += line.length;\n }\n }\n break;\n }\n case \"text_delta\":\n if (verbose && event.delta) {\n streamWrite(event.delta);\n }\n break;\n case \"thinking_delta\":\n // Only show reasoning in verbose mode\n if (verbose && event.delta) {\n streamWrite(chalk.dim(event.delta));\n }\n break;\n case \"agent_end\":\n streamLog(chalk.dim(\"\\n▶ Extracting review...\"));\n break;\n }\n }\n\n try {\n // Detect platform and warn about missing tokens\n let platform: string = \"local\";\n let metricsProject: string | undefined;\n let metricsMrIid: string | undefined;\n if (!localMode && prUrl) {\n platform = detectPlatform(prUrl);\n const parsedPr = parsePrUrl(prUrl);\n metricsProject = `${parsedPr.owner}/${parsedPr.repo}`;\n metricsMrIid = String(parsedPr.prNumber);\n const githubToken = process.env.GITHUB_TOKEN;\n const gitlabToken =\n process.env.GITLAB_TOKEN ??\n process.env.GITLAB_PRIVATE_TOKEN ??\n process.env.CI_JOB_TOKEN;\n\n if (platform === \"github\" && !githubToken) {\n console.error(chalk.yellow(\"Warning: GITHUB_TOKEN not set. You may encounter rate limits.\"));\n console.error(chalk.dim(\" Set GITHUB_TOKEN or run: gh auth login\\n\"));\n } else if (platform === \"gitlab\" && !gitlabToken) {\n console.error(chalk.yellow(\"Warning: No GitLab token detected. Set GITLAB_TOKEN (api scope).\"));\n console.error(chalk.dim(\" Export GITLAB_TOKEN and optionally GITLAB_HOST.\\n\"));\n } else if (platform === \"gitea\") {\n const giteaToken = process.env.GITEA_TOKEN ?? process.env.FORGEJO_TOKEN;\n if (!giteaToken) {\n console.error(chalk.yellow(\"Warning: No Gitea/Forgejo token detected. Set GITEA_TOKEN for authentication.\"));\n console.error(chalk.dim(\" Export GITEA_TOKEN (or FORGEJO_TOKEN) for API access.\\n\"));\n }\n }\n }\n\n log(`\\n${chalk.bold.cyan(\"Hodor - AI Code Review Agent\")}`);\n if (localMode) {\n log(chalk.dim(`Mode: Local diff review`));\n log(chalk.dim(`Diff against: ${diffAgainst}`));\n log(chalk.dim(`Workspace: ${workspace ?? process.cwd()}`));\n } else {\n log(chalk.dim(`Platform: ${platform.toUpperCase()}`));\n log(chalk.dim(`PR URL: ${prUrl}`));\n if (full) {\n log(chalk.dim(`Mode: Full review (source vs ${targetBranchOverride ?? \"target branch\"}, incremental disabled)`));\n }\n }\n log(chalk.dim(`Model: ${model}`));\n if (reasoningEffort) {\n log(chalk.dim(`Reasoning Effort: ${reasoningEffort}`));\n }\n log();\n\n streamLog(chalk.dim(\"▶ Setting up workspace...\"));\n const { review, metricsFooter, headSha, metrics, workspacePath } = await reviewPr({\n prUrl: localMode ? undefined : prUrl,\n model,\n reasoningEffort,\n customPrompt: prompt,\n promptFile,\n cleanup: !workspace,\n workspaceDir: workspace,\n includeMetricsFooter: post && !localMode,\n onEvent: handleEvent,\n bedrockTags,\n localMode,\n diffAgainst,\n full,\n targetBranchOverride,\n });\n const reviewText = renderMarkdown(review);\n\n streamLog(chalk.green(\"✔ Review complete!\"));\n\n if (codeQuality) {\n try {\n const { formatCodeQualityReport } = await import(\"./codequality.js\");\n const { writeFileSync } = await import(\"node:fs\");\n writeFileSync(\n codeQuality,\n formatCodeQualityReport(review, process.env.CI_PROJECT_DIR ?? workspacePath),\n \"utf-8\",\n );\n log(chalk.dim(`Wrote code quality report to ${codeQuality}`));\n } catch (err) {\n log(chalk.yellow(`Failed to write code quality report: ${err}`));\n }\n }\n\n if (post && prUrl) {\n log(chalk.cyan(\"\\nPosting review to PR/MR...\"));\n\n const platform = detectPlatform(prUrl);\n const useStructured = platform === \"gitlab\" && reviewStyle !== \"summary\";\n\n let result: PostCommentResult;\n if (useStructured) {\n result = await postReviewStructured({\n prUrl,\n review,\n model,\n metricsFooter,\n reviewStyle: reviewStyle ?? \"hybrid\",\n commitStatus,\n headSha,\n workspacePath,\n });\n } else {\n result = await postReviewComment({\n prUrl,\n reviewText,\n model,\n metricsFooter,\n headSha,\n });\n\n if (platform === \"gitlab\" && commitStatus) {\n try {\n const { getGitlabMrDiffRefs } = await import(\"./gitlab.js\");\n const parsed = parsePrUrl(prUrl);\n const diffRefs = await getGitlabMrDiffRefs(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n parsed.host,\n );\n await postGitlabReviewCommitStatus(parsed, review, diffRefs);\n } catch (err) {\n result = {\n success: false,\n platform: \"gitlab\",\n mrNumber: parsePrUrl(prUrl).prNumber,\n error: `Failed to post commit status: ${err instanceof Error ? err.message : err}`,\n };\n }\n }\n }\n\n if (result.success) {\n log(chalk.bold.green(\"Review posted successfully!\"));\n log(chalk.dim(` ${platform === \"gitlab\" ? \"MR\" : \"PR\"}: ${prUrl}`));\n } else {\n log(chalk.bold.red(`Failed to post review: ${result.error}`));\n log(chalk.yellow(\"\\nReview output:\\n\"));\n console.log(reviewText);\n }\n } else {\n log(chalk.bold.green(\"Review Complete\\n\"));\n console.log(reviewText);\n if (!localMode) {\n log(chalk.dim(\"\\nTip: Use --post to automatically post this review to the PR/MR\"));\n }\n }\n\n // Push metrics to Prometheus Pushgateway (best-effort, never fails the run)\n if (prometheusPush) {\n const labels: Record<string, string> = {\n platform,\n model,\n verdict: review.overall_correctness === \"patch is correct\" ? \"correct\" : \"incorrect\",\n };\n if (metricsProject) labels.project = metricsProject;\n if (metricsMrIid) labels.mr_iid = metricsMrIid;\n\n await pushMetrics({\n pushgatewayUrl: prometheusPush,\n metrics,\n findings: review.findings,\n labels,\n });\n }\n } catch (err) {\n streamLog(chalk.red(\"✗ Review failed\"));\n console.error(\n chalk.bold.red(`\\nError: ${err instanceof Error ? err.message : err}`),\n );\n if (verbose && err instanceof Error && err.stack) {\n console.error(chalk.dim(err.stack));\n }\n process.exit(1);\n }\n });\n\nprogram.parse();\n"],"mappings":";;;;;;;;;;;;;;;;;AAEA,SAAS,eAAe;AACxB,OAAO,WAAW;AAClB,OAAO;AASP,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,OAAO,EACZ;AAAA,EACC;AAIF,EACC,QAAQ,OAAO,EACf,SAAS,YAAY,wFAAwF,EAC7G;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,iBAAiB,0BAA0B,KAAK,EACvD;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC,OAAO,mBAAmB,2BAA2B,EACrD;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,OAAO,OAA2B,YAAqC;AAC7E,QAAM,UAAU,QAAQ;AACxB,QAAM,OAAO,QAAQ;AACrB,QAAM,QAAQ,QAAQ;AACtB,MAAI,kBAAkB,QAAQ;AAC9B,QAAM,SAAS,QAAQ;AACvB,QAAM,aAAa,QAAQ;AAC3B,QAAM,YAAY,QAAQ;AAC1B,QAAM,cAAc,QAAQ;AAC5B,QAAM,cAAc,QAAQ;AAC5B,QAAM,eAAe,QAAQ;AAC7B,QAAM,aAAa,QAAQ;AAC3B,QAAM,iBAAiB,QAAQ;AAC/B,QAAM,iBAAiB,QAAQ;AAC/B,QAAM,YAAY,QAAQ;AAC1B,QAAM,cAAc,QAAQ;AAC5B,QAAM,OAAO,QAAQ;AACrB,QAAM,uBAAuB,QAAQ;AAErC,MAAI,CAAC,aAAa,CAAC,OAAO;AACxB,YAAQ,MAAM,MAAM,IAAI,uDAAuD,CAAC;AAChF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,MAAI,aAAa,MAAM;AACrB,YAAQ,MAAM,MAAM,IAAI,uEAAuE,CAAC;AAChG,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,MAAI,CAAC,CAAC,WAAW,UAAU,QAAQ,EAAE,SAAS,eAAe,QAAQ,GAAG;AACtE,YAAQ,MAAM,MAAM,IAAI,+DAA+D,CAAC;AACxF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,MAAI,wBAAwB,CAAC,MAAM;AACjC,YAAQ,MAAM,MAAM,OAAO,iEAAiE,CAAC;AAAA,EAC/F;AACA,MAAI,QAAQ,WAAW;AACrB,YAAQ,MAAM,MAAM,OAAO,gFAAgF,CAAC;AAAA,EAC9G;AAGA,QAAM,OAAO,CAAC,EAAE,QAAQ,IAAI,MAAM,QAAQ,IAAI,aAAa,QAAQ,IAAI,kBAAkB,QAAQ,IAAI,iBAAiB,QAAQ,IAAI;AAElI,MAAI,QAAS,aAAY,OAAO;AAAA,WACvB,KAAM,aAAY,MAAM;AAGjC,MAAI,YAAY;AACd,sBAAkB;AAAA,EACpB;AAGA,MAAI,cAA6C;AACjD,MAAI,gBAAgB;AAClB,QAAI;AACF,oBAAc,KAAK,MAAM,cAAc;AAAA,IACzC,QAAQ;AACN,cAAQ,MAAM,MAAM,IAAI,0CAA0C,CAAC;AACnE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,MAAM,QAAQ;AACpB,QAAM,YAAY,QAAQ;AAE1B,QAAM,YAAoC;AAAA,IACxC,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,IAAI;AAAA,EACN;AAGA,WAAS,UAAU,KAAmB;AACpC,cAAU,MAAM,GAAG,GAAG;AAAA,CAAI;AAAA,EAC5B;AAGA,WAAS,YAAY,MAAoB;AACvC,YAAQ,OAAO,MAAM,IAAI;AAAA,EAC3B;AAEA,WAAS,YAAY,OAAiC;AACpD,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH,kBAAU,MAAM,IAAI,sBAAiB,CAAC;AACtC;AAAA,MACF,KAAK;AACH,kBAAU,MAAM,IAAI;AAAA,oBAAa,MAAM,aAAa,GAAG,eAAK,CAAC;AAC7D;AAAA,MACF,KAAK,cAAc;AACjB,cAAM,OAAO,UAAU,MAAM,YAAY,EAAE,KAAK,MAAM;AACtD,cAAM,UAAU,MAAM,WAAW,IAAI,MAAM,QAAQ,KAAK;AACxD,cAAM,SAAS;AACf,cAAM,YAAY,QAAQ,SAAS,SAAS,QAAQ,MAAM,GAAG,MAAM,IAAI,WAAM;AAC7E,kBAAU,MAAM,MAAM,KAAK,IAAI,GAAG,SAAS,EAAE,CAAC;AAC9C;AAAA,MACF;AAAA,MACA,KAAK,YAAY;AACf,YAAI,MAAM,SAAS;AACjB,oBAAU,MAAM,IAAI,gBAAW,CAAC;AAAA,QAClC;AACA,YAAI,MAAM,QAAQ;AAChB,gBAAM,QAAQ,MAAM,OAAO,MAAM,IAAI;AACrC,gBAAM,WAAW,UAAU,KAAK;AAChC,gBAAM,WAAW,UAAU,MAAM;AACjC,cAAI,QAAQ;AACZ,mBAAS,IAAI,GAAG,IAAI,KAAK,IAAI,MAAM,QAAQ,QAAQ,GAAG,KAAK;AACzD,kBAAM,OAAO,MAAM,CAAC;AACpB,gBAAI,QAAQ,KAAK,SAAS,UAAU;AAClC,wBAAU,MAAM,IAAI,cAAS,MAAM,SAAS,CAAC,cAAc,CAAC;AAC5D;AAAA,YACF;AACA,sBAAU,MAAM,IAAI,OAAO,IAAI,EAAE,CAAC;AAClC,qBAAS,KAAK;AAAA,UAChB;AAAA,QACF;AACA;AAAA,MACF;AAAA,MACA,KAAK;AACH,YAAI,WAAW,MAAM,OAAO;AAC1B,sBAAY,MAAM,KAAK;AAAA,QACzB;AACA;AAAA,MACF,KAAK;AAEH,YAAI,WAAW,MAAM,OAAO;AAC1B,sBAAY,MAAM,IAAI,MAAM,KAAK,CAAC;AAAA,QACpC;AACA;AAAA,MACF,KAAK;AACH,kBAAU,MAAM,IAAI,+BAA0B,CAAC;AAC/C;AAAA,IACJ;AAAA,EACF;AAEA,MAAI;AAEF,QAAI,WAAmB;AACvB,QAAI;AACJ,QAAI;AACJ,QAAI,CAAC,aAAa,OAAO;AACvB,iBAAW,eAAe,KAAK;AAC/B,YAAM,WAAW,WAAW,KAAK;AACjC,uBAAiB,GAAG,SAAS,KAAK,IAAI,SAAS,IAAI;AACnD,qBAAe,OAAO,SAAS,QAAQ;AACvC,YAAM,cAAc,QAAQ,IAAI;AAChC,YAAM,cACJ,QAAQ,IAAI,gBACZ,QAAQ,IAAI,wBACZ,QAAQ,IAAI;AAEd,UAAI,aAAa,YAAY,CAAC,aAAa;AACzC,gBAAQ,MAAM,MAAM,OAAO,+DAA+D,CAAC;AAC3F,gBAAQ,MAAM,MAAM,IAAI,4CAA4C,CAAC;AAAA,MACvE,WAAW,aAAa,YAAY,CAAC,aAAa;AAChD,gBAAQ,MAAM,MAAM,OAAO,kEAAkE,CAAC;AAC9F,gBAAQ,MAAM,MAAM,IAAI,qDAAqD,CAAC;AAAA,MAChF,WAAW,aAAa,SAAS;AAC/B,cAAM,aAAa,QAAQ,IAAI,eAAe,QAAQ,IAAI;AAC1D,YAAI,CAAC,YAAY;AACf,kBAAQ,MAAM,MAAM,OAAO,+EAA+E,CAAC;AAC3G,kBAAQ,MAAM,MAAM,IAAI,2DAA2D,CAAC;AAAA,QACtF;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AAAA,EAAK,MAAM,KAAK,KAAK,8BAA8B,CAAC,EAAE;AAC1D,QAAI,WAAW;AACb,UAAI,MAAM,IAAI,yBAAyB,CAAC;AACxC,UAAI,MAAM,IAAI,iBAAiB,WAAW,EAAE,CAAC;AAC7C,UAAI,MAAM,IAAI,cAAc,aAAa,QAAQ,IAAI,CAAC,EAAE,CAAC;AAAA,IAC3D,OAAO;AACL,UAAI,MAAM,IAAI,aAAa,SAAS,YAAY,CAAC,EAAE,CAAC;AACpD,UAAI,MAAM,IAAI,WAAW,KAAK,EAAE,CAAC;AACjC,UAAI,MAAM;AACR,YAAI,MAAM,IAAI,gCAAgC,wBAAwB,eAAe,yBAAyB,CAAC;AAAA,MACjH;AAAA,IACF;AACA,QAAI,MAAM,IAAI,UAAU,KAAK,EAAE,CAAC;AAChC,QAAI,iBAAiB;AACnB,UAAI,MAAM,IAAI,qBAAqB,eAAe,EAAE,CAAC;AAAA,IACvD;AACA,QAAI;AAEJ,cAAU,MAAM,IAAI,gCAA2B,CAAC;AAChD,UAAM,EAAE,QAAQ,eAAe,SAAS,SAAS,cAAc,IAAI,MAAM,SAAS;AAAA,MAChF,OAAO,YAAY,SAAY;AAAA,MAC/B;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA,SAAS,CAAC;AAAA,MACV,cAAc;AAAA,MACd,sBAAsB,QAAQ,CAAC;AAAA,MAC/B,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,aAAa,eAAe,MAAM;AAExC,cAAU,MAAM,MAAM,yBAAoB,CAAC;AAE3C,QAAI,aAAa;AACf,UAAI;AACF,cAAM,EAAE,wBAAwB,IAAI,MAAM,OAAO,2BAAkB;AACnE,cAAM,EAAE,cAAc,IAAI,MAAM,OAAO,IAAS;AAChD;AAAA,UACE;AAAA,UACA,wBAAwB,QAAQ,QAAQ,IAAI,kBAAkB,aAAa;AAAA,UAC3E;AAAA,QACF;AACA,YAAI,MAAM,IAAI,gCAAgC,WAAW,EAAE,CAAC;AAAA,MAC9D,SAAS,KAAK;AACZ,YAAI,MAAM,OAAO,wCAAwC,GAAG,EAAE,CAAC;AAAA,MACjE;AAAA,IACF;AAEA,QAAI,QAAQ,OAAO;AACjB,UAAI,MAAM,KAAK,8BAA8B,CAAC;AAE9C,YAAMA,YAAW,eAAe,KAAK;AACrC,YAAM,gBAAgBA,cAAa,YAAY,gBAAgB;AAE/D,UAAI;AACJ,UAAI,eAAe;AACjB,iBAAS,MAAM,qBAAqB;AAAA,UAClC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa,eAAe;AAAA,UAC5B;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH,OAAO;AACL,iBAAS,MAAM,kBAAkB;AAAA,UAC/B;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAIA,cAAa,YAAY,cAAc;AACzC,cAAI;AACF,kBAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,sBAAa;AAC1D,kBAAM,SAAS,WAAW,KAAK;AAC/B,kBAAM,WAAW,MAAM;AAAA,cACrB,OAAO;AAAA,cACP,OAAO;AAAA,cACP,OAAO;AAAA,cACP,OAAO;AAAA,YACT;AACA,kBAAM,6BAA6B,QAAQ,QAAQ,QAAQ;AAAA,UAC7D,SAAS,KAAK;AACZ,qBAAS;AAAA,cACP,SAAS;AAAA,cACT,UAAU;AAAA,cACV,UAAU,WAAW,KAAK,EAAE;AAAA,cAC5B,OAAO,iCAAiC,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,YAClF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,UAAI,OAAO,SAAS;AAClB,YAAI,MAAM,KAAK,MAAM,6BAA6B,CAAC;AACnD,YAAI,MAAM,IAAI,KAAKA,cAAa,WAAW,OAAO,IAAI,KAAK,KAAK,EAAE,CAAC;AAAA,MACrE,OAAO;AACL,YAAI,MAAM,KAAK,IAAI,0BAA0B,OAAO,KAAK,EAAE,CAAC;AAC5D,YAAI,MAAM,OAAO,oBAAoB,CAAC;AACtC,gBAAQ,IAAI,UAAU;AAAA,MACxB;AAAA,IACF,OAAO;AACL,UAAI,MAAM,KAAK,MAAM,mBAAmB,CAAC;AACzC,cAAQ,IAAI,UAAU;AACtB,UAAI,CAAC,WAAW;AACd,YAAI,MAAM,IAAI,kEAAkE,CAAC;AAAA,MACnF;AAAA,IACF;AAGA,QAAI,gBAAgB;AAClB,YAAM,SAAiC;AAAA,QACrC;AAAA,QACA;AAAA,QACA,SAAS,OAAO,wBAAwB,qBAAqB,YAAY;AAAA,MAC3E;AACA,UAAI,eAAgB,QAAO,UAAU;AACrC,UAAI,aAAc,QAAO,SAAS;AAElC,YAAM,YAAY;AAAA,QAChB,gBAAgB;AAAA,QAChB;AAAA,QACA,UAAU,OAAO;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,cAAU,MAAM,IAAI,sBAAiB,CAAC;AACtC,YAAQ;AAAA,MACN,MAAM,KAAK,IAAI;AAAA,SAAY,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAAA,IACvE;AACA,QAAI,WAAW,eAAe,SAAS,IAAI,OAAO;AAChD,cAAQ,MAAM,MAAM,IAAI,IAAI,KAAK,CAAC;AAAA,IACpC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QAAQ,MAAM;","names":["platform"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -121,6 +121,8 @@ declare function reviewPr(opts: {
|
|
|
121
121
|
bedrockTags?: Record<string, string> | null;
|
|
122
122
|
localMode?: boolean;
|
|
123
123
|
diffAgainst?: string;
|
|
124
|
+
full?: boolean;
|
|
125
|
+
targetBranchOverride?: string;
|
|
124
126
|
}): Promise<{
|
|
125
127
|
review: ReviewOutput;
|
|
126
128
|
metricsFooter: string | null;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.6.2-rc.
|
|
6
|
+
"version": "0.6.2-rc.4",
|
|
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",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"node": ">=22"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@sinclair/typebox": "^0.34.49",
|
|
47
46
|
"@anthropic-ai/sdk": "^0.95.2",
|
|
48
|
-
"@earendil-works/pi-ai": "^0.
|
|
49
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
47
|
+
"@earendil-works/pi-ai": "^0.79.10",
|
|
48
|
+
"@earendil-works/pi-coding-agent": "^0.79.10",
|
|
49
|
+
"@sinclair/typebox": "^0.34.49",
|
|
50
50
|
"chalk": "^5.6.2",
|
|
51
51
|
"commander": "^14.0.3",
|
|
52
52
|
"dotenv": "^17.4.2"
|
|
@@ -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/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"]}
|