@mrkaran/hodor 0.7.0 → 0.7.1

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/cli.js CHANGED
@@ -8,8 +8,9 @@ import {
8
8
  postReviewStructured,
9
9
  pushMetrics,
10
10
  reviewPr
11
- } from "./chunk-Q5AQKNSE.js";
11
+ } from "./chunk-VERO4XYJ.js";
12
12
  import {
13
+ logger,
13
14
  renderMarkdown,
14
15
  setLogLevel
15
16
  } from "./chunk-W7ZUJIFT.js";
@@ -80,7 +81,7 @@ program.name("hodor").description(
80
81
  false
81
82
  ).option(
82
83
  "--bedrock-tags <json>",
83
- `JSON object of cost allocation tags for Bedrock requests (e.g., '{"team":"platform"}')`
84
+ `JSON object of Bedrock requestMetadata for filtering model invocation logs (e.g., '{"team":"platform"}'). NOT billing tags: AWS ignores this for cost allocation. For per-team cost in Cost Explorer, pass a tagged application inference profile ARN via --model instead.`
84
85
  ).option(
85
86
  "--prometheus-push <url>",
86
87
  "Push review metrics to a Prometheus Pushgateway URL"
@@ -99,6 +100,10 @@ program.name("hodor").description(
99
100
  ).option(
100
101
  "--target-branch <ref>",
101
102
  "Override the target branch to diff against for a full review (default: the MR/PR's target branch). Only used with --full."
103
+ ).option(
104
+ "--tiny-diff-fast-path",
105
+ "For tiny, low-risk, fully embedded diffs, expose only submit_review so the review completes in one turn (cheaper; no repository exploration)",
106
+ false
102
107
  ).action(async (prUrl, cmdOpts) => {
103
108
  const verbose = cmdOpts.verbose;
104
109
  const post = cmdOpts.post;
@@ -119,6 +124,7 @@ program.name("hodor").description(
119
124
  const diffAgainst = cmdOpts.diffAgainst;
120
125
  const full = cmdOpts.full;
121
126
  const targetBranchOverride = cmdOpts.targetBranch;
127
+ const tinyDiffFastPath = cmdOpts.tinyDiffFastPath;
122
128
  if (!localMode && !prUrl) {
123
129
  console.error(chalk.red("Error: pr-url is required unless --local is specified"));
124
130
  process.exit(1);
@@ -305,7 +311,8 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
305
311
  localMode,
306
312
  diffAgainst,
307
313
  full,
308
- targetBranchOverride
314
+ targetBranchOverride,
315
+ tinyDiffFastPath
309
316
  });
310
317
  const reviewText = renderMarkdown(review);
311
318
  streamLog(chalk.green("\u2714 Review complete!"));
@@ -426,7 +433,8 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
426
433
  outcome: metricsOutcome,
427
434
  review_mode: metrics.reviewMode ?? "unknown",
428
435
  reasoning_effort: metrics.reasoningEffort ?? reasoningEffort ?? "none",
429
- reused: metrics.reused ? "true" : "false"
436
+ reused: metrics.reused ? "true" : "false",
437
+ fast_path: metrics.fastPath ? "true" : "false"
430
438
  };
431
439
  if (metricsProject) labels.project = metricsProject;
432
440
  await pushMetrics({
@@ -448,17 +456,29 @@ Error: ${err instanceof Error ? err.message : err}`)
448
456
  if (verbose && err instanceof Error && err.stack) {
449
457
  console.error(chalk.dim(err.stack));
450
458
  }
451
- if (prometheusPush) {
452
- let failurePlatform = "local";
453
- let failureProject;
454
- if (!localMode && prUrl) {
455
- try {
456
- failurePlatform = detectPlatform(prUrl);
457
- const parsed = parsePrUrl(prUrl);
458
- failureProject = `${parsed.owner}/${parsed.repo}`;
459
- } catch {
460
- }
459
+ let failurePlatform = "local";
460
+ let failureProject;
461
+ if (!localMode && prUrl) {
462
+ try {
463
+ failurePlatform = detectPlatform(prUrl);
464
+ const parsed = parsePrUrl(prUrl);
465
+ failureProject = `${parsed.owner}/${parsed.repo}`;
466
+ } catch {
461
467
  }
468
+ }
469
+ logger.info(`Review telemetry: ${JSON.stringify({
470
+ project: failureProject ?? null,
471
+ mr: null,
472
+ headSha: null,
473
+ model,
474
+ outcome: "review_failed",
475
+ reviewMode: null,
476
+ reasoningEffort: reasoningEffort ?? "auto",
477
+ fastPath: null,
478
+ reused: false,
479
+ error: err instanceof Error ? err.message : String(err)
480
+ })}`);
481
+ if (prometheusPush) {
462
482
  const labels = {
463
483
  platform: failurePlatform,
464
484
  model,
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/review-policy.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 {\n hasBlockingFinding,\n parseFailOnPriority,\n type FailOnPriority,\n} from \"./review-policy.js\";\nimport { loadReviewInstructionsFile } from \"./review-instructions.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.7.0\")\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(\n \"--additional-instructions <text>\",\n \"Additional review instructions appended after the selected review profile\",\n )\n .option(\n \"--review-instructions <path>\",\n \"Path to a custom review instruction profile\",\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 \"--require-delivery\",\n \"Exit non-zero if requested comments, statuses, or artifacts are not delivered\",\n false,\n )\n .option(\n \"--fail-on-priority <priority>\",\n \"Exit non-zero when findings at or above this severity exist: P0, P1, P2, or P3\",\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 additionalInstructions = cmdOpts.additionalInstructions as string | undefined;\n const reviewInstructionsPath = cmdOpts.reviewInstructions 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 requireDelivery = cmdOpts.requireDelivery as boolean;\n const failOnPriorityRaw = cmdOpts.failOnPriority as string | undefined;\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 if (requireDelivery && !post && !codeQuality) {\n console.error(chalk.red(\"Error: --require-delivery requires --post or --code-quality\"));\n process.exit(1);\n }\n\n let failOnPriority: FailOnPriority | undefined;\n if (failOnPriorityRaw) {\n try {\n failOnPriority = parseFailOnPriority(failOnPriorityRaw.toUpperCase());\n } catch (error) {\n console.error(chalk.red(`Error: ${error instanceof Error ? error.message : error}`));\n process.exit(1);\n }\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 const reviewInstructions = reviewInstructionsPath\n ? loadReviewInstructionsFile(reviewInstructionsPath)\n : undefined;\n // Detect platform and warn about missing tokens\n let platform: string = \"local\";\n let metricsProject: string | undefined;\n let metricsOutcome = \"reviewed\";\n let requestedExitCode = 0;\n if (!localMode && prUrl) {\n platform = detectPlatform(prUrl);\n const parsedPr = parsePrUrl(prUrl);\n metricsProject = `${parsedPr.owner}/${parsedPr.repo}`;\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 log(chalk.dim(`Review instructions: ${reviewInstructionsPath ?? \"bundled default\"}`));\n if (additionalInstructions) {\n log(chalk.dim(\"Additional instructions: supplied\"));\n }\n if (reasoningEffort) {\n log(chalk.dim(`Reasoning Effort: ${reasoningEffort}`));\n }\n log();\n\n streamLog(chalk.dim(\"▶ Setting up workspace...\"));\n const {\n review,\n metricsFooter,\n headSha,\n metrics,\n workspacePath,\n cacheMarker,\n reusedReview,\n } = await reviewPr({\n prUrl: localMode ? undefined : prUrl,\n model,\n reasoningEffort,\n reviewInstructions,\n additionalInstructions,\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 if (reusedReview) {\n log(chalk.dim(\"Reused the existing review for this HEAD; no LLM request was made.\"));\n }\n\n let codeQualityWritten = false;\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 codeQualityWritten = true;\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 metricsOutcome = \"delivery_failed\";\n if (requireDelivery) requestedExitCode = 1;\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 reconcileDiscussions: full,\n cacheMarker,\n skipSummary: reusedReview,\n });\n } else if (reusedReview) {\n result = {\n success: true,\n platform: platform as \"github\" | \"gitlab\" | \"gitea\",\n summaryPosted: true,\n };\n } else {\n result = await postReviewComment({\n prUrl,\n reviewText,\n model,\n metricsFooter,\n headSha,\n cacheMarker,\n });\n }\n\n if (!useStructured && 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 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 metricsOutcome = \"delivery_failed\";\n if (requireDelivery) requestedExitCode = 1;\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 if (requireDelivery && codeQuality && !codeQualityWritten) {\n requestedExitCode = 1;\n }\n if (failOnPriority && hasBlockingFinding(review, failOnPriority)) {\n const maximumPriority = Number(failOnPriority.slice(1));\n const blocking = review.findings.filter(\n (finding) => finding.priority <= maximumPriority,\n ).length;\n log(\n chalk.bold.red(\n `Review policy failed: ${blocking} finding(s) at ${failOnPriority} or higher`,\n ),\n );\n metricsOutcome = \"policy_failed\";\n requestedExitCode = 1;\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 outcome: metricsOutcome,\n review_mode: metrics.reviewMode ?? \"unknown\",\n reasoning_effort: metrics.reasoningEffort ?? reasoningEffort ?? \"none\",\n reused: metrics.reused ? \"true\" : \"false\",\n };\n if (metricsProject) labels.project = metricsProject;\n\n await pushMetrics({\n pushgatewayUrl: prometheusPush,\n metrics,\n // Reused reviews are delivery/cache events, not newly discovered\n // findings. Keep them observable without double-counting findings.\n findings: metrics.reused ? [] : review.findings,\n labels,\n });\n }\n if (requestedExitCode !== 0) process.exitCode = requestedExitCode;\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 if (prometheusPush) {\n let failurePlatform = \"local\";\n let failureProject: string | undefined;\n if (!localMode && prUrl) {\n try {\n failurePlatform = detectPlatform(prUrl);\n const parsed = parsePrUrl(prUrl);\n failureProject = `${parsed.owner}/${parsed.repo}`;\n } catch {\n // The original validation error is more useful than metrics-label parsing.\n }\n }\n const labels: Record<string, string> = {\n platform: failurePlatform,\n model,\n verdict: \"unknown\",\n outcome: \"review_failed\",\n };\n if (failureProject) labels.project = failureProject;\n await pushMetrics({\n pushgatewayUrl: prometheusPush,\n metrics: {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n totalTokens: 0,\n cost: 0,\n turns: 0,\n toolCalls: 0,\n durationSeconds: 0,\n },\n labels,\n });\n }\n process.exitCode = 1;\n }\n });\n\nprogram.parse();\n","import type { ReviewOutput, ReviewPriority } from \"./types.js\";\n\nexport type FailOnPriority = `P${ReviewPriority}`;\n\nexport function parseFailOnPriority(value: string): FailOnPriority {\n if (!/^P[0-3]$/.test(value)) {\n throw new Error(\"--fail-on-priority must be one of: P0, P1, P2, P3\");\n }\n return value as FailOnPriority;\n}\n\nexport function hasBlockingFinding(\n review: ReviewOutput,\n threshold: FailOnPriority,\n): boolean {\n const maximumPriority = Number(threshold.slice(1));\n return review.findings.some((finding) => finding.priority <= maximumPriority);\n}\n"],"mappings":"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","names":["platform"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/review-policy.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 {\n hasBlockingFinding,\n parseFailOnPriority,\n type FailOnPriority,\n} from \"./review-policy.js\";\nimport { loadReviewInstructionsFile } from \"./review-instructions.js\";\nimport { logger, 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.7.0\")\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(\n \"--additional-instructions <text>\",\n \"Additional review instructions appended after the selected review profile\",\n )\n .option(\n \"--review-instructions <path>\",\n \"Path to a custom review instruction profile\",\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 \"--require-delivery\",\n \"Exit non-zero if requested comments, statuses, or artifacts are not delivered\",\n false,\n )\n .option(\n \"--fail-on-priority <priority>\",\n \"Exit non-zero when findings at or above this severity exist: P0, P1, P2, or P3\",\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 Bedrock requestMetadata for filtering model invocation logs (e.g., '{\\\"team\\\":\\\"platform\\\"}'). \" +\n \"NOT billing tags: AWS ignores this for cost allocation. For per-team cost in Cost Explorer, \" +\n \"pass a tagged application inference profile ARN via --model instead.\",\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 .option(\n \"--tiny-diff-fast-path\",\n \"For tiny, low-risk, fully embedded diffs, expose only submit_review so the review completes in one turn (cheaper; no repository exploration)\",\n false,\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 additionalInstructions = cmdOpts.additionalInstructions as string | undefined;\n const reviewInstructionsPath = cmdOpts.reviewInstructions 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 requireDelivery = cmdOpts.requireDelivery as boolean;\n const failOnPriorityRaw = cmdOpts.failOnPriority as string | undefined;\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 const tinyDiffFastPath = cmdOpts.tinyDiffFastPath as boolean;\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 if (requireDelivery && !post && !codeQuality) {\n console.error(chalk.red(\"Error: --require-delivery requires --post or --code-quality\"));\n process.exit(1);\n }\n\n let failOnPriority: FailOnPriority | undefined;\n if (failOnPriorityRaw) {\n try {\n failOnPriority = parseFailOnPriority(failOnPriorityRaw.toUpperCase());\n } catch (error) {\n console.error(chalk.red(`Error: ${error instanceof Error ? error.message : error}`));\n process.exit(1);\n }\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 const reviewInstructions = reviewInstructionsPath\n ? loadReviewInstructionsFile(reviewInstructionsPath)\n : undefined;\n // Detect platform and warn about missing tokens\n let platform: string = \"local\";\n let metricsProject: string | undefined;\n let metricsOutcome = \"reviewed\";\n let requestedExitCode = 0;\n if (!localMode && prUrl) {\n platform = detectPlatform(prUrl);\n const parsedPr = parsePrUrl(prUrl);\n metricsProject = `${parsedPr.owner}/${parsedPr.repo}`;\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 log(chalk.dim(`Review instructions: ${reviewInstructionsPath ?? \"bundled default\"}`));\n if (additionalInstructions) {\n log(chalk.dim(\"Additional instructions: supplied\"));\n }\n if (reasoningEffort) {\n log(chalk.dim(`Reasoning Effort: ${reasoningEffort}`));\n }\n log();\n\n streamLog(chalk.dim(\"▶ Setting up workspace...\"));\n const {\n review,\n metricsFooter,\n headSha,\n metrics,\n workspacePath,\n cacheMarker,\n reusedReview,\n } = await reviewPr({\n prUrl: localMode ? undefined : prUrl,\n model,\n reasoningEffort,\n reviewInstructions,\n additionalInstructions,\n cleanup: !workspace,\n workspaceDir: workspace,\n includeMetricsFooter: post && !localMode,\n onEvent: handleEvent,\n bedrockTags,\n localMode,\n diffAgainst,\n full,\n targetBranchOverride,\n tinyDiffFastPath,\n });\n const reviewText = renderMarkdown(review);\n\n streamLog(chalk.green(\"✔ Review complete!\"));\n if (reusedReview) {\n log(chalk.dim(\"Reused the existing review for this HEAD; no LLM request was made.\"));\n }\n\n let codeQualityWritten = false;\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 codeQualityWritten = true;\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 metricsOutcome = \"delivery_failed\";\n if (requireDelivery) requestedExitCode = 1;\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 reconcileDiscussions: full,\n cacheMarker,\n skipSummary: reusedReview,\n });\n } else if (reusedReview) {\n result = {\n success: true,\n platform: platform as \"github\" | \"gitlab\" | \"gitea\",\n summaryPosted: true,\n };\n } else {\n result = await postReviewComment({\n prUrl,\n reviewText,\n model,\n metricsFooter,\n headSha,\n cacheMarker,\n });\n }\n\n if (!useStructured && 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 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 metricsOutcome = \"delivery_failed\";\n if (requireDelivery) requestedExitCode = 1;\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 if (requireDelivery && codeQuality && !codeQualityWritten) {\n requestedExitCode = 1;\n }\n if (failOnPriority && hasBlockingFinding(review, failOnPriority)) {\n const maximumPriority = Number(failOnPriority.slice(1));\n const blocking = review.findings.filter(\n (finding) => finding.priority <= maximumPriority,\n ).length;\n log(\n chalk.bold.red(\n `Review policy failed: ${blocking} finding(s) at ${failOnPriority} or higher`,\n ),\n );\n metricsOutcome = \"policy_failed\";\n requestedExitCode = 1;\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 outcome: metricsOutcome,\n review_mode: metrics.reviewMode ?? \"unknown\",\n reasoning_effort: metrics.reasoningEffort ?? reasoningEffort ?? \"none\",\n reused: metrics.reused ? \"true\" : \"false\",\n fast_path: metrics.fastPath ? \"true\" : \"false\",\n };\n if (metricsProject) labels.project = metricsProject;\n\n await pushMetrics({\n pushgatewayUrl: prometheusPush,\n metrics,\n // Reused reviews are delivery/cache events, not newly discovered\n // findings. Keep them observable without double-counting findings.\n findings: metrics.reused ? [] : review.findings,\n labels,\n });\n }\n if (requestedExitCode !== 0) process.exitCode = requestedExitCode;\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 let failurePlatform = \"local\";\n let failureProject: string | undefined;\n if (!localMode && prUrl) {\n try {\n failurePlatform = detectPlatform(prUrl);\n const parsed = parsePrUrl(prUrl);\n failureProject = `${parsed.owner}/${parsed.repo}`;\n } catch {\n // The original validation error is more useful than metrics-label parsing.\n }\n }\n\n // Failures are the expensive outlier case: a review can burn a full\n // budget of turns and then die before submit_review. Emit the same\n // telemetry shape as a successful review so they aren't invisible.\n logger.info(`Review telemetry: ${JSON.stringify({\n project: failureProject ?? null,\n mr: null,\n headSha: null,\n model,\n outcome: \"review_failed\",\n reviewMode: null,\n reasoningEffort: reasoningEffort ?? \"auto\",\n fastPath: null,\n reused: false,\n error: err instanceof Error ? err.message : String(err),\n })}`);\n\n if (prometheusPush) {\n const labels: Record<string, string> = {\n platform: failurePlatform,\n model,\n verdict: \"unknown\",\n outcome: \"review_failed\",\n };\n if (failureProject) labels.project = failureProject;\n await pushMetrics({\n pushgatewayUrl: prometheusPush,\n metrics: {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n totalTokens: 0,\n cost: 0,\n turns: 0,\n toolCalls: 0,\n durationSeconds: 0,\n },\n labels,\n });\n }\n process.exitCode = 1;\n }\n });\n\nprogram.parse();\n","import type { ReviewOutput, ReviewPriority } from \"./types.js\";\n\nexport type FailOnPriority = `P${ReviewPriority}`;\n\nexport function parseFailOnPriority(value: string): FailOnPriority {\n if (!/^P[0-3]$/.test(value)) {\n throw new Error(\"--fail-on-priority must be one of: P0, P1, P2, P3\");\n }\n return value as FailOnPriority;\n}\n\nexport function hasBlockingFinding(\n review: ReviewOutput,\n threshold: FailOnPriority,\n): boolean {\n const maximumPriority = Number(threshold.slice(1));\n return review.findings.some((finding) => finding.priority <= maximumPriority);\n}\n"],"mappings":"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","names":["platform"]}
package/dist/index.d.ts CHANGED
@@ -56,6 +56,8 @@ interface ReviewMetrics {
56
56
  diffDeletions?: number;
57
57
  diffBytes?: number;
58
58
  reused?: boolean;
59
+ /** Whether the single-turn tiny-diff fast path gated tools to submit_review. */
60
+ fastPath?: boolean;
59
61
  }
60
62
  type ReviewPriority = 0 | 1 | 2 | 3;
61
63
  type ReviewCorrectness = "patch is correct" | "patch is incorrect";
@@ -142,6 +144,7 @@ declare function reviewPr(opts: {
142
144
  diffAgainst?: string;
143
145
  full?: boolean;
144
146
  targetBranchOverride?: string;
147
+ tinyDiffFastPath?: boolean;
145
148
  }): Promise<{
146
149
  review: ReviewOutput;
147
150
  metricsFooter: string | null;
@@ -163,11 +166,20 @@ declare function buildPrReviewPrompt(opts: {
163
166
  reviewDiffMode?: ReviewDiffMode;
164
167
  changedFiles?: string[];
165
168
  localMode?: boolean;
169
+ singleTurn?: boolean;
166
170
  }): string;
167
171
 
168
172
  interface ParsedModel {
169
173
  provider: string;
170
174
  modelId: string;
175
+ /**
176
+ * Registry model id whose descriptor should back a custom Bedrock ARN.
177
+ * Application inference profile ARNs do not contain the model name, and
178
+ * pi-ai gates prompt caching, adaptive thinking, and cost accounting on
179
+ * substring matches against the model id/name. Without this hint those
180
+ * capabilities silently switch off.
181
+ */
182
+ baseModelId?: string;
171
183
  }
172
184
  /**
173
185
  * Parse a model string like "anthropic/claude-sonnet-4-5" into { provider, modelId }.
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  reviewPr,
18
18
  validateReviewInstructions,
19
19
  validateReviewOutput
20
- } from "./chunk-Q5AQKNSE.js";
20
+ } from "./chunk-VERO4XYJ.js";
21
21
  import {
22
22
  renderMarkdown
23
23
  } from "./chunk-W7ZUJIFT.js";
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.7.0",
6
+ "version": "0.7.1",
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",
@@ -14,13 +14,7 @@ Review the change at {pr_url}. The branch is checked out in the workspace.
14
14
 
15
15
  {diff_fetch_instructions}
16
16
 
17
- ## Runtime Tools
18
-
19
- - `{pr_diff_cmd}` lists the changed files when a diff is not embedded.
20
- - `{git_diff_cmd} -- path/to/file` shows the delta for one changed file.
21
- - `read` provides bounded surrounding context.
22
- - `grep` searches for directly relevant code and contracts.
23
- - `submit_review` submits the completed review.
17
+ {runtime_tools_section}
24
18
 
25
19
  {review_process_section}
26
20