@mrkaran/hodor 0.6.2 → 0.6.3-rc.2

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
@@ -7,21 +7,35 @@ import {
7
7
  postReviewStructured,
8
8
  pushMetrics,
9
9
  reviewPr
10
- } from "./chunk-LQDQ7CHL.js";
10
+ } from "./chunk-ZUYM6HY3.js";
11
11
  import {
12
12
  renderMarkdown,
13
13
  setLogLevel
14
- } from "./chunk-DVJVQTVW.js";
14
+ } from "./chunk-LO2QJD45.js";
15
15
  import "./chunk-AMUK6GDX.js";
16
16
 
17
17
  // src/cli.ts
18
18
  import { Command } from "commander";
19
19
  import chalk from "chalk";
20
20
  import "dotenv/config";
21
+
22
+ // src/review-policy.ts
23
+ function parseFailOnPriority(value) {
24
+ if (!/^P[0-3]$/.test(value)) {
25
+ throw new Error("--fail-on-priority must be one of: P0, P1, P2, P3");
26
+ }
27
+ return value;
28
+ }
29
+ function hasBlockingFinding(review, threshold) {
30
+ const maximumPriority = Number(threshold.slice(1));
31
+ return review.findings.some((finding) => finding.priority <= maximumPriority);
32
+ }
33
+
34
+ // src/cli.ts
21
35
  var program = new Command();
22
36
  program.name("hodor").description(
23
37
  "AI-powered code review agent for GitHub PRs, GitLab MRs, Gitea/Forgejo PRs, and local diffs.\n\nHodor uses an AI agent that clones the repository, checks out the PR branch,\nand analyzes the code using tools (gh, git, glab) for metadata fetching and comment posting.\n\nFor local reviews, use --local with --diff-against to review changes in your current git repository."
24
- ).version("0.6.2").argument("[pr-url]", "URL of the GitHub PR, GitLab MR, or Gitea/Forgejo PR to review (optional with --local)").option(
38
+ ).version("0.6.3-rc.2").argument("[pr-url]", "URL of the GitHub PR, GitLab MR, or Gitea/Forgejo PR to review (optional with --local)").option(
25
39
  "--model <model>",
26
40
  "LLM model to use as provider/model-id (e.g., anthropic/claude-sonnet-4-5-20250929, openrouter/moonshotai/kimi-k2.6)",
27
41
  "anthropic/claude-sonnet-4-5-20250929"
@@ -49,6 +63,13 @@ program.name("hodor").description(
49
63
  "--commit-status",
50
64
  "Post a pass/fail commit status to the MR head SHA",
51
65
  false
66
+ ).option(
67
+ "--require-delivery",
68
+ "Exit non-zero if requested comments, statuses, or artifacts are not delivered",
69
+ false
70
+ ).option(
71
+ "--fail-on-priority <priority>",
72
+ "Exit non-zero when findings at or above this severity exist: P0, P1, P2, or P3"
52
73
  ).option(
53
74
  "--ultrathink",
54
75
  "Enable maximum reasoning effort with extended thinking budget",
@@ -85,6 +106,8 @@ program.name("hodor").description(
85
106
  const reviewStyle = cmdOpts.reviewStyle;
86
107
  const codeQuality = cmdOpts.codeQuality;
87
108
  const commitStatus = cmdOpts.commitStatus;
109
+ const requireDelivery = cmdOpts.requireDelivery;
110
+ const failOnPriorityRaw = cmdOpts.failOnPriority;
88
111
  const ultrathink = cmdOpts.ultrathink;
89
112
  const bedrockTagsRaw = cmdOpts.bedrockTags;
90
113
  const prometheusPush = cmdOpts.prometheusPush;
@@ -110,6 +133,19 @@ program.name("hodor").description(
110
133
  if (full && localMode) {
111
134
  console.error(chalk.yellow("Warning: --full has no effect in --local mode (local reviews are always full)."));
112
135
  }
136
+ if (requireDelivery && !post && !codeQuality) {
137
+ console.error(chalk.red("Error: --require-delivery requires --post or --code-quality"));
138
+ process.exit(1);
139
+ }
140
+ let failOnPriority;
141
+ if (failOnPriorityRaw) {
142
+ try {
143
+ failOnPriority = parseFailOnPriority(failOnPriorityRaw.toUpperCase());
144
+ } catch (error) {
145
+ console.error(chalk.red(`Error: ${error instanceof Error ? error.message : error}`));
146
+ process.exit(1);
147
+ }
148
+ }
113
149
  const isCI = !!(process.env.CI || process.env.GITLAB_CI || process.env.GITHUB_ACTIONS || process.env.GITEA_ACTIONS || process.env.FORGEJO_ACTIONS);
114
150
  if (verbose) setLogLevel("debug");
115
151
  else if (isCI) setLogLevel("info");
@@ -197,12 +233,12 @@ program.name("hodor").description(
197
233
  try {
198
234
  let platform = "local";
199
235
  let metricsProject;
200
- let metricsMrIid;
236
+ let metricsOutcome = "reviewed";
237
+ let requestedExitCode = 0;
201
238
  if (!localMode && prUrl) {
202
239
  platform = detectPlatform(prUrl);
203
240
  const parsedPr = parsePrUrl(prUrl);
204
241
  metricsProject = `${parsedPr.owner}/${parsedPr.repo}`;
205
- metricsMrIid = String(parsedPr.prNumber);
206
242
  const githubToken = process.env.GITHUB_TOKEN;
207
243
  const gitlabToken = process.env.GITLAB_TOKEN ?? process.env.GITLAB_PRIVATE_TOKEN ?? process.env.CI_JOB_TOKEN;
208
244
  if (platform === "github" && !githubToken) {
@@ -256,6 +292,7 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
256
292
  });
257
293
  const reviewText = renderMarkdown(review);
258
294
  streamLog(chalk.green("\u2714 Review complete!"));
295
+ let codeQualityWritten = false;
259
296
  if (codeQuality) {
260
297
  try {
261
298
  const { formatCodeQualityReport } = await import("./codequality-DTJK2LGF.js");
@@ -265,9 +302,12 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
265
302
  formatCodeQualityReport(review, process.env.CI_PROJECT_DIR ?? workspacePath),
266
303
  "utf-8"
267
304
  );
305
+ codeQualityWritten = true;
268
306
  log(chalk.dim(`Wrote code quality report to ${codeQuality}`));
269
307
  } catch (err) {
270
308
  log(chalk.yellow(`Failed to write code quality report: ${err}`));
309
+ metricsOutcome = "delivery_failed";
310
+ if (requireDelivery) requestedExitCode = 1;
271
311
  }
272
312
  }
273
313
  if (post && prUrl) {
@@ -284,7 +324,8 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
284
324
  reviewStyle: reviewStyle ?? "hybrid",
285
325
  commitStatus,
286
326
  headSha,
287
- workspacePath
327
+ workspacePath,
328
+ reconcileDiscussions: full
288
329
  });
289
330
  } else {
290
331
  result = await postReviewComment({
@@ -296,7 +337,7 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
296
337
  });
297
338
  if (platform2 === "gitlab" && commitStatus) {
298
339
  try {
299
- const { getGitlabMrDiffRefs } = await import("./gitlab-5TWHYHEP.js");
340
+ const { getGitlabMrDiffRefs } = await import("./gitlab-OMV6OJ5Q.js");
300
341
  const parsed = parsePrUrl(prUrl);
301
342
  const diffRefs = await getGitlabMrDiffRefs(
302
343
  parsed.owner,
@@ -322,6 +363,8 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
322
363
  log(chalk.bold.red(`Failed to post review: ${result.error}`));
323
364
  log(chalk.yellow("\nReview output:\n"));
324
365
  console.log(reviewText);
366
+ metricsOutcome = "delivery_failed";
367
+ if (requireDelivery) requestedExitCode = 1;
325
368
  }
326
369
  } else {
327
370
  log(chalk.bold.green("Review Complete\n"));
@@ -330,14 +373,30 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
330
373
  log(chalk.dim("\nTip: Use --post to automatically post this review to the PR/MR"));
331
374
  }
332
375
  }
376
+ if (requireDelivery && codeQuality && !codeQualityWritten) {
377
+ requestedExitCode = 1;
378
+ }
379
+ if (failOnPriority && hasBlockingFinding(review, failOnPriority)) {
380
+ const maximumPriority = Number(failOnPriority.slice(1));
381
+ const blocking = review.findings.filter(
382
+ (finding) => finding.priority <= maximumPriority
383
+ ).length;
384
+ log(
385
+ chalk.bold.red(
386
+ `Review policy failed: ${blocking} finding(s) at ${failOnPriority} or higher`
387
+ )
388
+ );
389
+ metricsOutcome = "policy_failed";
390
+ requestedExitCode = 1;
391
+ }
333
392
  if (prometheusPush) {
334
393
  const labels = {
335
394
  platform,
336
395
  model,
337
- verdict: review.overall_correctness === "patch is correct" ? "correct" : "incorrect"
396
+ verdict: review.overall_correctness === "patch is correct" ? "correct" : "incorrect",
397
+ outcome: metricsOutcome
338
398
  };
339
399
  if (metricsProject) labels.project = metricsProject;
340
- if (metricsMrIid) labels.mr_iid = metricsMrIid;
341
400
  await pushMetrics({
342
401
  pushgatewayUrl: prometheusPush,
343
402
  metrics,
@@ -345,6 +404,7 @@ ${chalk.bold.cyan("Hodor - AI Code Review Agent")}`);
345
404
  labels
346
405
  });
347
406
  }
407
+ if (requestedExitCode !== 0) process.exitCode = requestedExitCode;
348
408
  } catch (err) {
349
409
  streamLog(chalk.red("\u2717 Review failed"));
350
410
  console.error(
@@ -354,7 +414,41 @@ Error: ${err instanceof Error ? err.message : err}`)
354
414
  if (verbose && err instanceof Error && err.stack) {
355
415
  console.error(chalk.dim(err.stack));
356
416
  }
357
- process.exit(1);
417
+ if (prometheusPush) {
418
+ let failurePlatform = "local";
419
+ let failureProject;
420
+ if (!localMode && prUrl) {
421
+ try {
422
+ failurePlatform = detectPlatform(prUrl);
423
+ const parsed = parsePrUrl(prUrl);
424
+ failureProject = `${parsed.owner}/${parsed.repo}`;
425
+ } catch {
426
+ }
427
+ }
428
+ const labels = {
429
+ platform: failurePlatform,
430
+ model,
431
+ verdict: "unknown",
432
+ outcome: "review_failed"
433
+ };
434
+ if (failureProject) labels.project = failureProject;
435
+ await pushMetrics({
436
+ pushgatewayUrl: prometheusPush,
437
+ metrics: {
438
+ inputTokens: 0,
439
+ outputTokens: 0,
440
+ cacheReadTokens: 0,
441
+ cacheWriteTokens: 0,
442
+ totalTokens: 0,
443
+ cost: 0,
444
+ turns: 0,
445
+ toolCalls: 0,
446
+ durationSeconds: 0
447
+ },
448
+ labels
449
+ });
450
+ }
451
+ process.exitCode = 1;
358
452
  }
359
453
  });
360
454
  program.parse();
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.2\")\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":"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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 { 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.3-rc.2\")\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 \"--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 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 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 // 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 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 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 });\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 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 };\n if (metricsProject) labels.project = metricsProject;\n\n await pushMetrics({\n pushgatewayUrl: prometheusPush,\n metrics,\n findings: 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"]}
@@ -2,32 +2,32 @@ import {
2
2
  GitLabAPIError,
3
3
  HODOR_REVIEW_MARKER,
4
4
  bulkPublishGitlabDraftNotes,
5
- cleanupHodorComments,
6
5
  createGitlabDraftNote,
7
6
  fetchGitlabMrInfo,
8
7
  getGitlabMrDiffRefs,
8
+ isHodorGeneratedNote,
9
9
  listHodorDiscussions,
10
10
  parseGlabPaginatedJson,
11
11
  postGitlabCommitStatus,
12
- postGitlabInlineComment,
13
12
  postGitlabMrComment,
14
13
  resolveGitlabDiscussions,
15
- summarizeGitlabNotes
16
- } from "./chunk-DVJVQTVW.js";
14
+ summarizeGitlabNotes,
15
+ summarizeHodorNotes
16
+ } from "./chunk-LO2QJD45.js";
17
17
  export {
18
18
  GitLabAPIError,
19
19
  HODOR_REVIEW_MARKER,
20
20
  bulkPublishGitlabDraftNotes,
21
- cleanupHodorComments,
22
21
  createGitlabDraftNote,
23
22
  fetchGitlabMrInfo,
24
23
  getGitlabMrDiffRefs,
24
+ isHodorGeneratedNote,
25
25
  listHodorDiscussions,
26
26
  parseGlabPaginatedJson,
27
27
  postGitlabCommitStatus,
28
- postGitlabInlineComment,
29
28
  postGitlabMrComment,
30
29
  resolveGitlabDiscussions,
31
- summarizeGitlabNotes
30
+ summarizeGitlabNotes,
31
+ summarizeHodorNotes
32
32
  };
33
- //# sourceMappingURL=gitlab-5TWHYHEP.js.map
33
+ //# sourceMappingURL=gitlab-OMV6OJ5Q.js.map
package/dist/index.d.ts CHANGED
@@ -79,8 +79,18 @@ interface PostCommentResult {
79
79
  prNumber?: number;
80
80
  mrNumber?: number;
81
81
  error?: string;
82
+ errors?: string[];
83
+ summaryPosted?: boolean;
84
+ inlineCreated?: number;
85
+ inlineFailed?: number;
86
+ draftsPublished?: boolean;
87
+ commitStatusPosted?: boolean;
88
+ reconciledDiscussions?: number;
82
89
  }
83
90
 
91
+ declare function detectPlatform(prUrl: string): Platform;
92
+ declare function parsePrUrl(prUrl: string): ParsedPrUrl;
93
+
84
94
  /**
85
95
  * Render structured review output into clean markdown for PR/MR comments.
86
96
  */
@@ -90,6 +100,14 @@ interface PostCommentResult {
90
100
  */
91
101
  declare function renderMarkdown(review: ReviewOutput): string;
92
102
 
103
+ declare function postReviewComment(opts: {
104
+ prUrl: string;
105
+ reviewText: string;
106
+ model?: string | null;
107
+ metricsFooter?: string | null;
108
+ headSha?: string | null;
109
+ }): Promise<PostCommentResult>;
110
+
93
111
  interface AgentProgressEvent {
94
112
  type: "tool_start" | "tool_end" | "thinking" | "turn_start" | "turn_end" | "agent_start" | "agent_end" | "text_delta" | "thinking_delta" | "tool_result";
95
113
  toolName?: string;
@@ -99,15 +117,6 @@ interface AgentProgressEvent {
99
117
  delta?: string;
100
118
  result?: string;
101
119
  }
102
- declare function detectPlatform(prUrl: string): Platform;
103
- declare function parsePrUrl(prUrl: string): ParsedPrUrl;
104
- declare function postReviewComment(opts: {
105
- prUrl: string;
106
- reviewText: string;
107
- model?: string | null;
108
- metricsFooter?: string | null;
109
- headSha?: string | null;
110
- }): Promise<PostCommentResult>;
111
120
  declare function reviewPr(opts: {
112
121
  prUrl?: string;
113
122
  model?: string;
package/dist/index.js CHANGED
@@ -11,10 +11,10 @@ import {
11
11
  pushMetrics,
12
12
  reviewPr,
13
13
  validateReviewOutput
14
- } from "./chunk-LQDQ7CHL.js";
14
+ } from "./chunk-ZUYM6HY3.js";
15
15
  import {
16
16
  renderMarkdown
17
- } from "./chunk-DVJVQTVW.js";
17
+ } from "./chunk-LO2QJD45.js";
18
18
  import "./chunk-AMUK6GDX.js";
19
19
  export {
20
20
  buildPrReviewPrompt,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.6.2",
6
+ "version": "0.6.3-rc.2",
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",
@@ -16,6 +16,8 @@
16
16
  "dev": "tsx src/cli.ts",
17
17
  "test": "vitest run",
18
18
  "test:watch": "vitest",
19
+ "eval": "tsx scripts/run-evals.ts",
20
+ "eval:list": "tsx scripts/run-evals.ts --list",
19
21
  "typecheck": "tsc --noEmit",
20
22
  "lint": "tsc --noEmit"
21
23
  },
@@ -44,8 +46,8 @@
44
46
  },
45
47
  "dependencies": {
46
48
  "@anthropic-ai/sdk": "^0.95.2",
47
- "@earendil-works/pi-ai": "^0.79.10",
48
- "@earendil-works/pi-coding-agent": "^0.79.10",
49
+ "@earendil-works/pi-ai": "^0.80.7",
50
+ "@earendil-works/pi-coding-agent": "^0.80.7",
49
51
  "@sinclair/typebox": "^0.34.49",
50
52
  "chalk": "^5.6.2",
51
53
  "commander": "^14.0.3",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/exec.ts","../src/utils/logger.ts","../src/render.ts","../src/gitlab.ts"],"sourcesContent":["import { execFile, spawn } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n}\n\nexport interface ExecOptions {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n input?: string;\n}\n\nexport async function exec(\n cmd: string,\n args: string[],\n opts?: ExecOptions,\n): Promise<ExecResult> {\n if (typeof opts?.input === \"string\") {\n return new Promise<ExecResult>((resolve, reject) => {\n const child = spawn(cmd, args, {\n cwd: opts.cwd,\n env: opts.env ?? process.env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout.on(\"data\", (chunk: Buffer | string) => {\n stdout += chunk.toString();\n });\n child.stderr.on(\"data\", (chunk: Buffer | string) => {\n stderr += chunk.toString();\n });\n child.on(\"error\", (error) => {\n reject(error);\n });\n child.on(\"close\", (code, signal) => {\n if (code === 0) {\n resolve({ stdout, stderr });\n return;\n }\n\n const parts: string[] = [`Command failed: ${cmd} ${args.join(\" \")}`];\n if (stderr.trim()) {\n parts.push(`stderr:\\n${stderr.trim()}`);\n }\n if (stdout.trim()) {\n parts.push(`stdout:\\n${stdout.trim()}`);\n }\n if (signal) {\n parts.push(`signal: ${signal}`);\n }\n const error = new Error(parts.join(\"\\n\"));\n reject(error);\n });\n\n child.stdin.write(opts.input);\n child.stdin.end();\n });\n }\n\n const { stdout, stderr } = await execFileAsync(cmd, args, {\n cwd: opts?.cwd,\n env: opts?.env ?? process.env,\n maxBuffer: 50 * 1024 * 1024, // 50MB\n });\n return { stdout, stderr };\n}\n\nexport async function execJson<T = Record<string, unknown>>(\n cmd: string,\n args: string[],\n opts?: ExecOptions,\n): Promise<T> {\n const { stdout } = await exec(cmd, args, opts);\n return JSON.parse(stdout.trim()) as T;\n}\n","import chalk from \"chalk\";\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nlet currentLevel: LogLevel = \"warn\";\n\nconst LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nexport function setLogLevel(level: LogLevel): void {\n currentLevel = level;\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVELS[level] >= LEVELS[currentLevel];\n}\n\nfunction timestamp(): string {\n return new Date().toISOString();\n}\n\nexport const logger = {\n debug(msg: string): void {\n if (shouldLog(\"debug\")) {\n process.stderr.write(`${chalk.gray(timestamp())} ${chalk.gray(\"DEBUG\")} ${msg}\\n`);\n }\n },\n info(msg: string): void {\n if (shouldLog(\"info\")) {\n process.stderr.write(`${chalk.gray(timestamp())} ${chalk.blue(\"INFO\")} ${msg}\\n`);\n }\n },\n warn(msg: string): void {\n if (shouldLog(\"warn\")) {\n process.stderr.write(`${chalk.gray(timestamp())} ${chalk.yellow(\"WARN\")} ${msg}\\n`);\n }\n },\n error(msg: string): void {\n if (shouldLog(\"error\")) {\n process.stderr.write(`${chalk.gray(timestamp())} ${chalk.red(\"ERROR\")} ${msg}\\n`);\n }\n },\n};\n","/**\n * Render structured review output into clean markdown for PR/MR comments.\n */\n\nimport type { ReviewFinding, ReviewOutput } from \"./types.js\";\n\nexport const HODOR_REVIEW_MARKER = \"<!-- hodor-review -->\";\n\n/**\n * Render a ReviewOutput into clean markdown for posting as a PR/MR comment.\n */\nexport function renderMarkdown(review: ReviewOutput): string {\n const lines: string[] = [HODOR_REVIEW_MARKER];\n\n // Group findings by priority\n const critical: ReviewFinding[] = []; // P0, P1\n const important: ReviewFinding[] = []; // P2\n const minor: ReviewFinding[] = []; // P3\n\n for (const f of review.findings) {\n const p = f.priority;\n if (p <= 1) critical.push(f);\n else if (p === 2) important.push(f);\n else minor.push(f);\n }\n\n lines.push(\"### Issues Found\");\n lines.push(\"\");\n\n if (review.findings.length === 0) {\n lines.push(\"No issues found.\");\n lines.push(\"\");\n }\n\n if (critical.length > 0) {\n lines.push(\"**Critical (P0/P1)**\");\n for (const f of critical) {\n lines.push(formatFinding(f));\n }\n lines.push(\"\");\n }\n\n if (important.length > 0) {\n lines.push(\"**Important (P2)**\");\n for (const f of important) {\n lines.push(formatFinding(f));\n }\n lines.push(\"\");\n }\n\n if (minor.length > 0) {\n lines.push(\"**Minor (P3)**\");\n for (const f of minor) {\n lines.push(formatFinding(f));\n }\n lines.push(\"\");\n }\n\n // Summary\n lines.push(\"### Summary\");\n lines.push(\n `Total issues: ${critical.length} critical, ${important.length} important, ${minor.length} minor.`,\n );\n lines.push(\"\");\n\n // Overall verdict\n lines.push(\"### Overall Verdict\");\n const isCorrect = review.overall_correctness === \"patch is correct\";\n lines.push(\n `**Status**: ${isCorrect ? \"Patch is correct\" : \"Patch has blocking issues\"}`,\n );\n lines.push(\"\");\n if (review.overall_explanation) {\n lines.push(`**Explanation**: ${review.overall_explanation}`);\n }\n\n return lines.join(\"\\n\").trimEnd() + \"\\n\";\n}\n\nexport function renderSummaryMarkdown(review: ReviewOutput): string {\n const lines: string[] = [HODOR_REVIEW_MARKER];\n\n const counts = { critical: 0, important: 0, minor: 0 };\n for (const f of review.findings) {\n if (f.priority <= 1) counts.critical++;\n else if (f.priority === 2) counts.important++;\n else counts.minor++;\n }\n\n lines.push(\"\");\n lines.push(\"| Category | Count |\");\n lines.push(\"| --- | ---: |\");\n lines.push(`| Critical (P0/P1) | ${counts.critical} |`);\n lines.push(`| Important (P2) | ${counts.important} |`);\n lines.push(`| Minor (P3) | ${counts.minor} |`);\n\n const isCorrect = review.overall_correctness === \"patch is correct\";\n lines.push(\"\");\n lines.push(`**Overall verdict**: ${isCorrect ? \"Patch is correct\" : \"Patch has blocking issues\"}`);\n lines.push(\"\");\n lines.push(`**Explanation**: ${review.overall_explanation}`);\n\n if (review.findings.length > 0) {\n lines.push(\"\");\n lines.push(\"| Finding | Location | Priority |\");\n lines.push(\"| --- | --- | --- |\");\n for (const f of review.findings) {\n const loc = formatLocation(f.code_location);\n const safeTitle = f.title.replace(/\\|/g, \"\\\\|\");\n lines.push(`| ${safeTitle} | \\`${loc}\\` | P${f.priority} |`);\n }\n }\n\n return lines.join(\"\\n\").trimEnd() + \"\\n\";\n}\n\nfunction formatFinding(f: ReviewFinding): string {\n const loc = ` (\\`${formatLocation(f.code_location)}\\`)`;\n const title = `- **${f.title}**${loc}`;\n const body = ` - ${f.body}`;\n return `${title}\\n${body}`;\n}\n\nfunction formatLocation(loc: {\n absolute_file_path: string;\n line_range: { start: number; end: number };\n}): string {\n // Strip common workspace prefixes to get a clean relative path\n let filePath = loc.absolute_file_path;\n\n // GitLab CI: /builds/owner/repo/src/file.ts → src/file.ts\n const buildsMatch = filePath.match(/\\/builds\\/[^/]+\\/[^/]+\\/(.+)/);\n if (buildsMatch) {\n filePath = buildsMatch[1];\n }\n // GitHub Actions / generic workspace\n else if (filePath.includes(\"/workspace/\")) {\n filePath = filePath.slice(filePath.indexOf(\"/workspace/\") + \"/workspace/\".length);\n }\n // Temp review dirs: /tmp/hodor-review-<id>/src/file.ts → src/file.ts\n else {\n filePath = filePath.replace(/^.*\\/hodor-review-[^/]+\\//, \"\");\n }\n\n const { start, end } = loc.line_range;\n return start === end ? `${filePath}:${start}` : `${filePath}:${start}-${end}`;\n}\n","import { exec, execJson } from \"./utils/exec.js\";\nimport { logger } from \"./utils/logger.js\";\nimport type { MrMetadata, NoteEntry } from \"./types.js\";\nimport { HODOR_REVIEW_MARKER } from \"./render.js\";\n\nexport { HODOR_REVIEW_MARKER };\n\nexport interface DiffRefs {\n base_sha: string;\n head_sha: string;\n start_sha: string;\n}\n\nconst DEFAULT_GITLAB_HOST = \"gitlab.com\";\n\n/**\n * Match notes Hodor itself created. The body must begin with a hodor-owned HTML\n * comment (either `<!-- hodor-review -->` or a sibling like `<!-- hodor:sha:... -->`\n * that hodor prepends to summary comments). Anchoring at the start avoids deleting\n * human notes that quote the marker incidentally (e.g., a code block discussing hodor).\n */\nconst HODOR_NOTE_PREFIX_RE = /^\\s*<!--\\s*hodor[-:]/;\n\nfunction isHodorNote(body: unknown, marker = HODOR_REVIEW_MARKER): boolean {\n if (typeof body !== \"string\") return false;\n // Default fast-path: body starts with the canonical marker (allowing leading whitespace).\n if (body.trimStart().startsWith(marker)) return true;\n // Accept hodor's own SHA prefix, e.g. `<!-- hodor:sha:abc -->\\n<!-- hodor-review -->\\n...`\n if (marker === HODOR_REVIEW_MARKER && HODOR_NOTE_PREFIX_RE.test(body)) {\n // Require the canonical marker to appear somewhere in the body so we don't\n // resolve unrelated `<!-- hodor:foo -->` notes that aren't review summaries.\n return body.includes(HODOR_REVIEW_MARKER);\n }\n return false;\n}\n\n/**\n * Parse concatenated JSON arrays from `glab api --paginate`.\n * glab outputs `[...][...][...]` — one array per page, no delimiter.\n * We track bracket depth (respecting strings/escapes) to find each\n * top-level array, parse them individually, and merge with flat().\n */\nexport function parseGlabPaginatedJson(raw: string): Array<Record<string, unknown>> {\n const trimmed = raw.trim();\n if (!trimmed) return [];\n\n const chunks: string[] = [];\n let depth = 0;\n let inString = false;\n let escaped = false;\n let start = -1;\n\n for (let i = 0; i < trimmed.length; i++) {\n const ch = trimmed[i];\n if (escaped) {\n escaped = false;\n continue;\n }\n if (ch === \"\\\\\" && inString) {\n escaped = true;\n continue;\n }\n if (ch === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (ch === \"[\") {\n if (depth === 0) start = i;\n depth++;\n } else if (ch === \"]\") {\n depth--;\n if (depth === 0 && start >= 0) {\n chunks.push(trimmed.slice(start, i + 1));\n start = -1;\n }\n }\n }\n\n const results: Array<Record<string, unknown>> = [];\n for (const chunk of chunks) {\n try {\n const parsed = JSON.parse(chunk) as Array<Record<string, unknown>>;\n if (Array.isArray(parsed)) results.push(...parsed);\n } catch (err) {\n logger.warn(\n `Skipping malformed glab pagination chunk: ${err instanceof Error ? err.message : err}`,\n );\n }\n }\n return results;\n}\n\nexport class GitLabAPIError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"GitLabAPIError\";\n }\n}\n\nfunction normalizeBaseUrl(host?: string | null): string {\n const candidate =\n host ||\n process.env.GITLAB_HOST ||\n process.env.CI_SERVER_URL ||\n DEFAULT_GITLAB_HOST;\n const trimmed = candidate.trim() || DEFAULT_GITLAB_HOST;\n if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n return trimmed.replace(/\\/+$/, \"\");\n }\n return `https://${trimmed}`.replace(/\\/+$/, \"\");\n}\n\nfunction encodedProjectPath(owner: string, repo: string): string {\n const projectPath = [owner.replace(/^\\/+|\\/+$/g, \"\"), repo.replace(/^\\/+|\\/+$/g, \"\")]\n .filter(Boolean)\n .join(\"/\");\n return encodeURIComponent(projectPath);\n}\n\nfunction glabEnv(host?: string | null): NodeJS.ProcessEnv {\n const env = { ...process.env };\n // Ensure glab knows which host to talk to\n const baseUrl = normalizeBaseUrl(host);\n const hostname = baseUrl.replace(/^https?:\\/\\//, \"\");\n env.GITLAB_HOST = hostname;\n return env;\n}\n\n/**\n * Fetch merge request metadata using glab api.\n */\nexport async function fetchGitlabMrInfo(\n owner: string,\n repo: string,\n mrNumber: number | string,\n host?: string | null,\n options?: { includeComments?: boolean },\n): Promise<MrMetadata> {\n const encoded = encodedProjectPath(owner, repo);\n const env = glabEnv(host);\n\n let mrData: Record<string, unknown>;\n try {\n mrData = await execJson<Record<string, unknown>>(\n \"glab\",\n [\"api\", `projects/${encoded}/merge_requests/${mrNumber}`],\n { env },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitLabAPIError(`Failed to fetch MR !${mrNumber}: ${msg}`);\n }\n\n const metadata: MrMetadata = {\n title: mrData.title as string | undefined,\n description: (mrData.description as string) ?? \"\",\n source_branch: mrData.source_branch as string | undefined,\n target_branch: mrData.target_branch as string | undefined,\n changes_count: mrData.changes_count as number | undefined,\n labels: mrData.labels as string[] | undefined,\n author: mrData.author as { username?: string; name?: string } | undefined,\n pipeline: mrData.pipeline as { status?: string; web_url?: string } | undefined,\n state: mrData.state as string | undefined,\n };\n\n if (options?.includeComments) {\n try {\n // glab --paginate concatenates JSON arrays across pages (e.g., `[...][...]`).\n // Parse each top-level array separately and merge, avoiding regex on raw JSON\n // which could corrupt string values containing `][`.\n const { stdout: rawNotes } = await exec(\n \"glab\",\n [\"api\", `projects/${encoded}/merge_requests/${mrNumber}/notes`, \"--paginate\"],\n { env },\n );\n const notes = parseGlabPaginatedJson(rawNotes);\n metadata.Notes = notes.map((n) => ({\n body: (n.body as string) ?? \"\",\n author: n.author as { username?: string; name?: string } | undefined,\n created_at: n.created_at as string | undefined,\n system: n.system as boolean | undefined,\n }));\n } catch (err) {\n logger.warn(`Failed to fetch MR notes: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n return metadata;\n}\n\n/**\n * Post a comment on a GitLab merge request using glab api.\n */\nexport async function postGitlabMrComment(\n owner: string,\n repo: string,\n mrNumber: number | string,\n body: string,\n host?: string | null,\n): Promise<void> {\n const encoded = encodedProjectPath(owner, repo);\n const env = glabEnv(host);\n\n try {\n await exec(\n \"glab\",\n [\n \"api\",\n `projects/${encoded}/merge_requests/${mrNumber}/notes`,\n \"--method\",\n \"POST\",\n \"-H\",\n \"Content-Type: application/json\",\n \"--input\",\n \"-\",\n ],\n { env, input: JSON.stringify({ body }) },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitLabAPIError(`Failed to post comment to MR !${mrNumber}: ${msg}`);\n }\n}\n\n/**\n * Summarize GitLab notes into a human-readable bullet list.\n */\nexport function summarizeGitlabNotes(\n notes: NoteEntry[] | undefined | null,\n maxEntries = 5,\n): string {\n if (!notes || notes.length === 0) return \"\";\n\n const trivialPatterns = new Set([\n \"lgtm\",\n \"+1\",\n \"-1\",\n \"👍\",\n \"👎\",\n \"thanks\",\n \"thank you\",\n \"looks good\",\n \"approved\",\n \"🚀\",\n \"✅\",\n \"❌\",\n ]);\n\n const filtered: Array<{ username: string; body: string; createdAt: string }> = [];\n for (const note of notes) {\n const body = (note.body ?? \"\").trim();\n if (!body) continue;\n if (note.system) continue;\n if (body.length < 20) continue;\n\n const bodyLower = body.toLowerCase();\n let isTrivial = false;\n for (const pattern of trivialPatterns) {\n if (bodyLower.includes(pattern) && body.length < 50) {\n isTrivial = true;\n break;\n }\n }\n if (isTrivial) continue;\n\n const username =\n note.author?.username ?? note.author?.name ?? \"unknown\";\n filtered.push({ username, body, createdAt: note.created_at ?? \"\" });\n }\n\n // Sort oldest first\n filtered.sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n\n // Take most recent\n const recent = filtered.slice(-maxEntries);\n\n const lines: string[] = [];\n for (const { username, body, createdAt } of recent) {\n let timestampStr = \"\";\n if (createdAt) {\n try {\n const dt = new Date(createdAt);\n timestampStr = dt.toISOString().replace(\"T\", \" \").slice(0, 16);\n } catch {\n timestampStr = createdAt.slice(0, 10);\n }\n }\n\n const header = timestampStr\n ? `- ${timestampStr} @${username}:`\n : `- @${username}:`;\n const indentedBody = body.split(\"\\n\").join(\"\\n \");\n lines.push(`${header}\\n ${indentedBody}`);\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction isPositionErrorMessage(message: string): boolean {\n const lower = message.toLowerCase();\n const hasPositionHint =\n lower.includes(\"line_code\") ||\n lower.includes(\"position\") ||\n lower.includes(\"must be part of the diff\") ||\n lower.includes(\"part of the diff\");\n const hasStatusHint = lower.includes(\"400\") || lower.includes(\"422\");\n return hasPositionHint && hasStatusHint;\n}\n\nexport async function getGitlabMrDiffRefs(\n owner: string,\n repo: string,\n mrNumber: number | string,\n host?: string | null,\n): Promise<DiffRefs> {\n const encoded = encodedProjectPath(owner, repo);\n const env = glabEnv(host);\n\n let mrData: Record<string, unknown>;\n try {\n mrData = await execJson<Record<string, unknown>>(\n \"glab\",\n [\"api\", `projects/${encoded}/merge_requests/${mrNumber}`],\n { env },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitLabAPIError(`Failed to fetch diff refs for MR !${mrNumber}: ${msg}`);\n }\n\n const diffRefs = mrData.diff_refs as Record<string, unknown> | undefined;\n const base_sha = diffRefs?.base_sha;\n const head_sha = diffRefs?.head_sha;\n const start_sha = diffRefs?.start_sha;\n\n if (\n typeof base_sha !== \"string\" ||\n typeof head_sha !== \"string\" ||\n typeof start_sha !== \"string\" ||\n !base_sha ||\n !head_sha ||\n !start_sha\n ) {\n throw new GitLabAPIError(`MR !${mrNumber} has missing or incomplete diff_refs`);\n }\n\n return { base_sha, head_sha, start_sha };\n}\n\nexport async function postGitlabInlineComment(\n owner: string,\n repo: string,\n mrNumber: number | string,\n body: string,\n filePath: string,\n line: number,\n diffRefs: DiffRefs,\n host?: string | null,\n): Promise<Record<string, unknown> | null> {\n const encoded = encodedProjectPath(owner, repo);\n const env = glabEnv(host);\n const endpoint = `projects/${encoded}/merge_requests/${mrNumber}/discussions`;\n\n const payload: Record<string, unknown> = {\n body,\n position: {\n base_sha: diffRefs.base_sha,\n head_sha: diffRefs.head_sha,\n start_sha: diffRefs.start_sha,\n position_type: \"text\",\n old_path: filePath,\n new_path: filePath,\n new_line: line,\n },\n };\n\n try {\n return await execJson<Record<string, unknown>>(\n \"glab\",\n [\"api\", endpoint, \"--method\", \"POST\", \"-H\", \"Content-Type: application/json\", \"--input\", \"-\"],\n {\n env,\n input: JSON.stringify(payload),\n },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (isPositionErrorMessage(msg)) {\n logger.warn(`Skipping inline comment for ${filePath}:${line}: ${msg}`);\n return null;\n }\n throw new GitLabAPIError(`Failed to post inline comment to MR !${mrNumber}: ${msg}`);\n }\n}\n\nexport async function createGitlabDraftNote(\n owner: string,\n repo: string,\n mrNumber: number | string,\n body: string,\n host?: string | null,\n opts?: { filePath?: string; line?: number; diffRefs?: DiffRefs },\n): Promise<Record<string, unknown>> {\n const encoded = encodedProjectPath(owner, repo);\n const env = glabEnv(host);\n const endpoint = `projects/${encoded}/merge_requests/${mrNumber}/draft_notes`;\n\n const payload: Record<string, unknown> = {\n note: body,\n };\n\n if (opts?.filePath && typeof opts.line === \"number\" && opts.diffRefs) {\n payload.position = {\n base_sha: opts.diffRefs.base_sha,\n head_sha: opts.diffRefs.head_sha,\n start_sha: opts.diffRefs.start_sha,\n position_type: \"text\",\n old_path: opts.filePath,\n new_path: opts.filePath,\n new_line: opts.line,\n };\n }\n\n try {\n return await execJson<Record<string, unknown>>(\n \"glab\",\n [\"api\", endpoint, \"--method\", \"POST\", \"-H\", \"Content-Type: application/json\", \"--input\", \"-\"],\n {\n env,\n input: JSON.stringify(payload),\n },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitLabAPIError(`Failed to create draft note for MR !${mrNumber}: ${msg}`);\n }\n}\n\nexport async function bulkPublishGitlabDraftNotes(\n owner: string,\n repo: string,\n mrNumber: number | string,\n host?: string | null,\n): Promise<void> {\n const encoded = encodedProjectPath(owner, repo);\n const env = glabEnv(host);\n\n try {\n await exec(\n \"glab\",\n [\n \"api\",\n `projects/${encoded}/merge_requests/${mrNumber}/draft_notes/bulk_publish`,\n \"--method\",\n \"POST\",\n ],\n { env },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitLabAPIError(`Failed to bulk publish draft notes for MR !${mrNumber}: ${msg}`);\n }\n}\n\ntype GitlabCommitStatusState = \"pending\" | \"running\" | \"success\" | \"failed\" | \"canceled\";\n\nexport async function postGitlabCommitStatus(\n owner: string,\n repo: string,\n sha: string,\n state: GitlabCommitStatusState,\n host?: string | null,\n opts?: { name?: string; description?: string; targetUrl?: string },\n): Promise<void> {\n const allowedStates = new Set<GitlabCommitStatusState>([\n \"pending\",\n \"running\",\n \"success\",\n \"failed\",\n \"canceled\",\n ]);\n if (!allowedStates.has(state)) {\n throw new GitLabAPIError(`Invalid GitLab commit status state: ${state}`);\n }\n\n const encoded = encodedProjectPath(owner, repo);\n const env = glabEnv(host);\n const endpoint = `projects/${encoded}/statuses/${sha}`;\n const payload: Record<string, unknown> = {\n state,\n name: opts?.name ?? \"hodor\",\n };\n\n if (opts?.description) {\n payload.description = opts.description;\n }\n if (opts?.targetUrl) {\n payload.target_url = opts.targetUrl;\n }\n\n try {\n await exec(\n \"glab\",\n [\"api\", endpoint, \"--method\", \"POST\", \"-H\", \"Content-Type: application/json\", \"--input\", \"-\"],\n {\n env,\n input: JSON.stringify(payload),\n },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitLabAPIError(`Failed to post commit status for ${sha}: ${msg}`);\n }\n}\n\nexport async function cleanupHodorComments(\n owner: string,\n repo: string,\n mrNumber: number | string,\n host?: string | null,\n marker = HODOR_REVIEW_MARKER,\n): Promise<number> {\n const encoded = encodedProjectPath(owner, repo);\n const env = glabEnv(host);\n\n let notes: Array<Record<string, unknown>>;\n try {\n const { stdout: rawNotes } = await exec(\n \"glab\",\n [\n \"api\",\n `projects/${encoded}/merge_requests/${mrNumber}/notes?per_page=100`,\n \"--paginate\",\n ],\n { env },\n );\n notes = parseGlabPaginatedJson(rawNotes);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitLabAPIError(`Failed to list notes for MR !${mrNumber}: ${msg}`);\n }\n\n const matchedNotes = notes.filter((note) => isHodorNote(note.body, marker));\n\n let deletedCount = 0;\n let failedCount = 0;\n for (const note of matchedNotes) {\n const noteId = note.id;\n if (typeof noteId !== \"number\") continue;\n\n try {\n await exec(\n \"glab\",\n [\n \"api\",\n `projects/${encoded}/merge_requests/${mrNumber}/notes/${noteId}`,\n \"--method\",\n \"DELETE\",\n ],\n { env },\n );\n deletedCount += 1;\n logger.debug(`Deleted GitLab MR note ${noteId}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // Inline (discussion) notes can't be deleted via /notes/{id}; log and continue\n // so one stale note doesn't abort the whole cleanup.\n logger.warn(`Skipping note ${noteId} on MR !${mrNumber}: ${msg}`);\n failedCount += 1;\n }\n }\n\n if (failedCount > 0) {\n logger.warn(`Cleanup left ${failedCount} note(s) undeleted on MR !${mrNumber}`);\n }\n\n return deletedCount;\n}\n\nexport async function listHodorDiscussions(\n owner: string,\n repo: string,\n mrNumber: number | string,\n host?: string | null,\n marker = HODOR_REVIEW_MARKER,\n): Promise<\n Array<{\n discussionId: string;\n noteId: number;\n body: string;\n resolved: boolean;\n filePath?: string;\n line?: number;\n }>\n> {\n const encoded = encodedProjectPath(owner, repo);\n const env = glabEnv(host);\n\n let discussions: Array<Record<string, unknown>>;\n try {\n const { stdout: rawDiscussions } = await exec(\n \"glab\",\n [\n \"api\",\n `projects/${encoded}/merge_requests/${mrNumber}/discussions?per_page=100`,\n \"--paginate\",\n ],\n { env },\n );\n discussions = parseGlabPaginatedJson(rawDiscussions);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitLabAPIError(`Failed to list discussions for MR !${mrNumber}: ${msg}`);\n }\n\n const results: Array<{\n discussionId: string;\n noteId: number;\n body: string;\n resolved: boolean;\n filePath?: string;\n line?: number;\n }> = [];\n\n for (const discussion of discussions) {\n const discussionId = discussion.id;\n if (typeof discussionId !== \"string\") {\n continue;\n }\n\n const notes = discussion.notes;\n if (!Array.isArray(notes)) {\n continue;\n }\n\n for (const note of notes) {\n if (!note || typeof note !== \"object\") {\n continue;\n }\n const noteObj = note as Record<string, unknown>;\n const noteId = noteObj.id;\n const body = noteObj.body;\n if (typeof noteId !== \"number\" || typeof body !== \"string\" || !isHodorNote(body, marker)) {\n continue;\n }\n\n const position =\n noteObj.position && typeof noteObj.position === \"object\"\n ? (noteObj.position as Record<string, unknown>)\n : undefined;\n\n const filePath = typeof position?.new_path === \"string\" ? position.new_path : undefined;\n const line = typeof position?.new_line === \"number\" ? position.new_line : undefined;\n\n // Skip non-resolvable threads. GitLab wraps the summary-comment note in a\n // discussion envelope with `resolvable: false`; PUT resolved=true on those\n // returns 403, independent of the caller's project role. Only diff/review\n // threads (resolvable: true) belong in the resolver's queue.\n if (noteObj.resolvable !== true) {\n continue;\n }\n\n results.push({\n discussionId,\n noteId,\n body,\n resolved: Boolean(noteObj.resolved),\n filePath,\n line,\n });\n }\n }\n\n return results;\n}\n\nexport async function resolveGitlabDiscussions(\n owner: string,\n repo: string,\n mrNumber: number | string,\n discussionIds: string[],\n host?: string | null,\n): Promise<number> {\n const encoded = encodedProjectPath(owner, repo);\n const env = glabEnv(host);\n\n let resolvedCount = 0;\n\n for (const discussionId of discussionIds) {\n try {\n await exec(\n \"glab\",\n [\n \"api\",\n `projects/${encoded}/merge_requests/${mrNumber}/discussions/${discussionId}`,\n \"--method\",\n \"PUT\",\n \"-H\",\n \"Content-Type: application/json\",\n \"--input\",\n \"-\",\n ],\n {\n env,\n input: JSON.stringify({ resolved: true }),\n },\n );\n resolvedCount += 1;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn(`Failed to resolve discussion ${discussionId} on MR !${mrNumber}: ${msg}`);\n }\n }\n\n return resolvedCount;\n}\n"],"mappings":"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","names":["stdout","stderr"]}