@mrkaran/hodor 0.4.1 → 0.6.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/README.md +177 -53
- package/dist/chunk-AMUK6GDX.js +23 -0
- package/dist/chunk-AMUK6GDX.js.map +1 -0
- package/dist/chunk-DVJVQTVW.js +731 -0
- package/dist/chunk-DVJVQTVW.js.map +1 -0
- package/dist/{chunk-QGUJENIG.js → chunk-JSLG7EOX.js} +916 -471
- package/dist/chunk-JSLG7EOX.js.map +1 -0
- package/dist/cli.js +111 -22
- package/dist/cli.js.map +1 -1
- package/dist/codequality-DTJK2LGF.js +42 -0
- package/dist/codequality-DTJK2LGF.js.map +1 -0
- package/dist/gitlab-5TWHYHEP.js +33 -0
- package/dist/gitlab-5TWHYHEP.js.map +1 -0
- package/dist/index.d.ts +21 -13
- package/dist/index.js +5 -2
- package/package.json +13 -13
- package/templates/tool-review.md +39 -5
- package/dist/chunk-QGUJENIG.js.map +0 -1
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, postReviewComment, reviewPr } from \"./agent.js\";\nimport type { AgentProgressEvent } from \"./agent.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, 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.4.1\")\n .argument(\"[pr-url]\", \"URL of the GitHub PR or GitLab MR to review (optional with --local)\")\n .option(\n \"--model <model>\",\n \"LLM model to use (e.g., anthropic/claude-sonnet-4-5-20250929, openai/gpt-5)\",\n \"anthropic/claude-sonnet-4-5-20250929\",\n )\n .option(\n \"--reasoning-effort <level>\",\n \"Reasoning effort level: 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 \"--ultrathink\",\n \"Enable maximum reasoning effort with extended thinking budget\",\n false,\n )\n .option(\n \"--bedrock-tags <json>\",\n \"JSON object of cost allocation tags for Bedrock requests (e.g., '{\\\"team\\\":\\\"platform\\\"}')\",\n )\n .option(\n \"--prometheus-push <url>\",\n \"Push review metrics to a Prometheus Pushgateway URL\",\n )\n .option(\n \"--local\",\n \"Review local changes in the current directory (no PR URL required)\",\n false,\n )\n .option(\n \"--diff-against <ref>\",\n \"Git ref to diff against in local mode (e.g., origin/main, HEAD~1)\",\n \"origin/main\",\n )\n .action(async (prUrl: string | undefined, cmdOpts: Record<string, unknown>) => {\n const verbose = cmdOpts.verbose as boolean;\n const post = cmdOpts.post as boolean;\n const model = cmdOpts.model as string;\n let reasoningEffort = cmdOpts.reasoningEffort as string | undefined;\n const prompt = cmdOpts.prompt as string | undefined;\n const promptFile = cmdOpts.promptFile as string | undefined;\n const workspace = cmdOpts.workspace as string | undefined;\n const ultrathink = cmdOpts.ultrathink as boolean;\n const bedrockTagsRaw = cmdOpts.bedrockTags as string | undefined;\n const prometheusPush = cmdOpts.prometheusPush as string | undefined;\n const localMode = cmdOpts.local as boolean;\n const diffAgainst = cmdOpts.diffAgainst as string;\n\n if (!localMode && !prUrl) {\n console.error(chalk.red(\"Error: pr-url is required unless --local is specified\"));\n process.exit(1);\n }\n if (localMode && post) {\n console.error(chalk.red(\"Error: --post is not supported in --local mode (no remote to post to)\"));\n process.exit(1);\n }\n\n // Auto-detect CI environment\n const isCI = !!(process.env.CI || process.env.GITLAB_CI || process.env.GITHUB_ACTIONS);\n\n if (verbose) setLogLevel(\"debug\");\n else if (isCI) setLogLevel(\"info\");\n\n // Handle ultrathink\n if (ultrathink) {\n reasoningEffort = \"high\";\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 if (!localMode && prUrl) {\n platform = detectPlatform(prUrl);\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 }\n }\n\n log(`\\n${chalk.bold.cyan(\"Hodor - AI Code Review Agent\")}`);\n if (localMode) {\n log(chalk.dim(`Mode: Local diff review`));\n log(chalk.dim(`Diff against: ${diffAgainst}`));\n log(chalk.dim(`Workspace: ${workspace ?? process.cwd()}`));\n } else {\n log(chalk.dim(`Platform: ${platform.toUpperCase()}`));\n log(chalk.dim(`PR URL: ${prUrl}`));\n }\n log(chalk.dim(`Model: ${model}`));\n if (reasoningEffort) {\n log(chalk.dim(`Reasoning Effort: ${reasoningEffort}`));\n }\n log();\n\n streamLog(chalk.dim(\"▶ Setting up workspace...\"));\n const { review, metricsFooter, headSha, metrics } = await reviewPr({\n prUrl: localMode ? undefined : prUrl,\n model,\n reasoningEffort,\n customPrompt: prompt,\n promptFile,\n cleanup: !workspace,\n workspaceDir: workspace,\n includeMetricsFooter: post && !localMode,\n onEvent: handleEvent,\n bedrockTags,\n localMode,\n diffAgainst,\n });\n const reviewText = renderMarkdown(review);\n\n streamLog(chalk.green(\"✔ Review complete!\"));\n\n if (post && prUrl) {\n log(chalk.cyan(\"\\nPosting review to PR/MR...\"));\n\n const result = await postReviewComment({\n prUrl,\n reviewText,\n model,\n metricsFooter,\n headSha,\n });\n\n if (result.success) {\n log(chalk.bold.green(\"Review posted successfully!\"));\n log(chalk.dim(` ${platform === \"github\" ? \"PR\" : \"MR\"}: ${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 await pushMetrics({\n pushgatewayUrl: prometheusPush,\n metrics,\n labels: {\n platform,\n model,\n verdict: review.overall_correctness === \"patch is correct\" ? \"correct\" : \"incorrect\",\n },\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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport \"dotenv/config\";\n\nimport { detectPlatform, parsePrUrl, postGitlabReviewCommitStatus, postReviewComment, postReviewStructured, reviewPr } from \"./agent.js\";\nimport type { AgentProgressEvent } from \"./agent.js\";\nimport type { PostCommentResult } from \"./types.js\";\nimport { renderMarkdown } from \"./render.js\";\nimport { pushMetrics } from \"./metrics.js\";\nimport { setLogLevel } from \"./utils/logger.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"hodor\")\n .description(\n \"AI-powered code review agent for GitHub PRs, GitLab MRs, Gitea/Forgejo PRs, and local diffs.\\n\\n\" +\n \"Hodor uses an AI agent that clones the repository, checks out the PR branch,\\n\" +\n \"and analyzes the code using tools (gh, git, glab) for metadata fetching and comment posting.\\n\\n\" +\n \"For local reviews, use --local with --diff-against to review changes in your current git repository.\",\n )\n .version(\"0.6.1\")\n .argument(\"[pr-url]\", \"URL of the GitHub PR, GitLab MR, or Gitea/Forgejo PR to review (optional with --local)\")\n .option(\n \"--model <model>\",\n \"LLM model to use as provider/model-id (e.g., anthropic/claude-sonnet-4-5-20250929, openrouter/moonshotai/kimi-k2.6)\",\n \"anthropic/claude-sonnet-4-5-20250929\",\n )\n .option(\n \"--reasoning-effort <level>\",\n \"Reasoning effort level: minimal, low, medium, high, xhigh\",\n )\n .option(\"-v, --verbose\", \"Enable verbose logging\", false)\n .option(\n \"--post\",\n \"Post the review directly to the PR/MR as a comment\",\n false,\n )\n .option(\"--prompt <text>\", \"Custom inline prompt text\")\n .option(\n \"--prompt-file <path>\",\n \"Path to file containing custom prompt instructions\",\n )\n .option(\n \"--workspace <dir>\",\n \"Workspace directory (creates temp dir if not specified)\",\n )\n .option(\n \"--review-style <style>\",\n \"How to post reviews on GitLab: summary (single comment), inline (diff comments), hybrid (both). Default: hybrid.\",\n \"hybrid\",\n )\n .option(\n \"--code-quality <path>\",\n \"Write a gl-code-quality-report.json CodeClimate artifact to this path\",\n )\n .option(\n \"--commit-status\",\n \"Post a pass/fail commit status to the MR head SHA\",\n false,\n )\n .option(\n \"--ultrathink\",\n \"Enable maximum reasoning effort with extended thinking budget\",\n false,\n )\n .option(\n \"--bedrock-tags <json>\",\n \"JSON object of cost allocation tags for Bedrock requests (e.g., '{\\\"team\\\":\\\"platform\\\"}')\",\n )\n .option(\n \"--prometheus-push <url>\",\n \"Push review metrics to a Prometheus Pushgateway URL\",\n )\n .option(\n \"--local\",\n \"Review local changes in the current directory (no PR URL required)\",\n false,\n )\n .option(\n \"--diff-against <ref>\",\n \"Git ref to diff against in local mode (e.g., origin/main, HEAD~1)\",\n \"origin/main\",\n )\n .action(async (prUrl: string | undefined, cmdOpts: Record<string, unknown>) => {\n const verbose = cmdOpts.verbose as boolean;\n const post = cmdOpts.post as boolean;\n const model = cmdOpts.model as string;\n let reasoningEffort = cmdOpts.reasoningEffort as string | undefined;\n const prompt = cmdOpts.prompt as string | undefined;\n const promptFile = cmdOpts.promptFile as string | undefined;\n const workspace = cmdOpts.workspace as string | undefined;\n const reviewStyle = cmdOpts.reviewStyle as \"summary\" | \"inline\" | \"hybrid\" | undefined;\n const codeQuality = cmdOpts.codeQuality as string | undefined;\n const commitStatus = cmdOpts.commitStatus as boolean;\n const ultrathink = cmdOpts.ultrathink as boolean;\n const bedrockTagsRaw = cmdOpts.bedrockTags as string | undefined;\n const prometheusPush = cmdOpts.prometheusPush as string | undefined;\n const localMode = cmdOpts.local as boolean;\n const diffAgainst = cmdOpts.diffAgainst as string;\n\n if (!localMode && !prUrl) {\n console.error(chalk.red(\"Error: pr-url is required unless --local is specified\"));\n process.exit(1);\n }\n if (localMode && post) {\n console.error(chalk.red(\"Error: --post is not supported in --local mode (no remote to post to)\"));\n process.exit(1);\n }\n if (![\"summary\", \"inline\", \"hybrid\"].includes(reviewStyle ?? \"hybrid\")) {\n console.error(chalk.red(\"Error: --review-style must be one of: summary, inline, hybrid\"));\n process.exit(1);\n }\n\n // Auto-detect CI environment\n const isCI = !!(process.env.CI || process.env.GITLAB_CI || process.env.GITHUB_ACTIONS || process.env.GITEA_ACTIONS || process.env.FORGEJO_ACTIONS);\n\n if (verbose) setLogLevel(\"debug\");\n else if (isCI) setLogLevel(\"info\");\n\n // Handle ultrathink\n if (ultrathink) {\n reasoningEffort = \"xhigh\";\n }\n\n // Parse Bedrock cost allocation tags\n let bedrockTags: Record<string, string> | null = null;\n if (bedrockTagsRaw) {\n try {\n bedrockTags = JSON.parse(bedrockTagsRaw) as Record<string, string>;\n } catch {\n console.error(chalk.red(\"Error: --bedrock-tags must be valid JSON\"));\n process.exit(1);\n }\n }\n\n const log = console.log;\n const logStream = process.stdout;\n\n const toolIcons: Record<string, string> = {\n bash: \"$\",\n read: \"cat\",\n grep: \"grep\",\n find: \"find\",\n ls: \"ls\",\n };\n\n /** Write a line to the log stream */\n function streamLog(msg: string): void {\n logStream.write(`${msg}\\n`);\n }\n\n /** Write inline text (no newline) for streaming deltas */\n function streamWrite(text: string): void {\n process.stderr.write(text);\n }\n\n function handleEvent(event: AgentProgressEvent): void {\n switch (event.type) {\n case \"agent_start\":\n streamLog(chalk.dim(\"▶ Agent started\"));\n break;\n case \"turn_start\":\n streamLog(chalk.dim(`\\n── Turn ${event.turnIndex ?? \"?\"} ──`));\n break;\n case \"tool_start\": {\n const icon = toolIcons[event.toolName ?? \"\"] ?? event.toolName;\n const preview = event.toolArgs ? ` ${event.toolArgs}` : \"\";\n const maxLen = 160;\n const truncated = preview.length > maxLen ? preview.slice(0, maxLen) + \"…\" : preview;\n streamLog(chalk.green(` ${icon}${truncated}`));\n break;\n }\n case \"tool_end\": {\n if (event.isError) {\n streamLog(chalk.red(` ✗ error`));\n }\n if (event.result) {\n const lines = event.result.split(\"\\n\");\n const maxLines = verbose ? 15 : 6;\n const maxChars = verbose ? 400 : 200;\n let chars = 0;\n for (let i = 0; i < Math.min(lines.length, maxLines); i++) {\n const line = lines[i];\n if (chars + line.length > maxChars) {\n streamLog(chalk.dim(` …(${lines.length - i} more lines)`));\n break;\n }\n streamLog(chalk.dim(` ${line}`));\n chars += line.length;\n }\n }\n break;\n }\n case \"text_delta\":\n if (verbose && event.delta) {\n streamWrite(event.delta);\n }\n break;\n case \"thinking_delta\":\n // Only show reasoning in verbose mode\n if (verbose && event.delta) {\n streamWrite(chalk.dim(event.delta));\n }\n break;\n case \"agent_end\":\n streamLog(chalk.dim(\"\\n▶ Extracting review...\"));\n break;\n }\n }\n\n try {\n // Detect platform and warn about missing tokens\n let platform: string = \"local\";\n let metricsProject: string | undefined;\n let metricsMrIid: string | undefined;\n if (!localMode && prUrl) {\n platform = detectPlatform(prUrl);\n const parsedPr = parsePrUrl(prUrl);\n metricsProject = `${parsedPr.owner}/${parsedPr.repo}`;\n metricsMrIid = String(parsedPr.prNumber);\n const githubToken = process.env.GITHUB_TOKEN;\n const gitlabToken =\n process.env.GITLAB_TOKEN ??\n process.env.GITLAB_PRIVATE_TOKEN ??\n process.env.CI_JOB_TOKEN;\n\n if (platform === \"github\" && !githubToken) {\n console.error(chalk.yellow(\"Warning: GITHUB_TOKEN not set. You may encounter rate limits.\"));\n console.error(chalk.dim(\" Set GITHUB_TOKEN or run: gh auth login\\n\"));\n } else if (platform === \"gitlab\" && !gitlabToken) {\n console.error(chalk.yellow(\"Warning: No GitLab token detected. Set GITLAB_TOKEN (api scope).\"));\n console.error(chalk.dim(\" Export GITLAB_TOKEN and optionally GITLAB_HOST.\\n\"));\n } else if (platform === \"gitea\") {\n const giteaToken = process.env.GITEA_TOKEN ?? process.env.FORGEJO_TOKEN;\n if (!giteaToken) {\n console.error(chalk.yellow(\"Warning: No Gitea/Forgejo token detected. Set GITEA_TOKEN for authentication.\"));\n console.error(chalk.dim(\" Export GITEA_TOKEN (or FORGEJO_TOKEN) for API access.\\n\"));\n }\n }\n }\n\n log(`\\n${chalk.bold.cyan(\"Hodor - AI Code Review Agent\")}`);\n if (localMode) {\n log(chalk.dim(`Mode: Local diff review`));\n log(chalk.dim(`Diff against: ${diffAgainst}`));\n log(chalk.dim(`Workspace: ${workspace ?? process.cwd()}`));\n } else {\n log(chalk.dim(`Platform: ${platform.toUpperCase()}`));\n log(chalk.dim(`PR URL: ${prUrl}`));\n }\n log(chalk.dim(`Model: ${model}`));\n if (reasoningEffort) {\n log(chalk.dim(`Reasoning Effort: ${reasoningEffort}`));\n }\n log();\n\n streamLog(chalk.dim(\"▶ Setting up workspace...\"));\n const { review, metricsFooter, headSha, metrics, workspacePath } = await reviewPr({\n prUrl: localMode ? undefined : prUrl,\n model,\n reasoningEffort,\n customPrompt: prompt,\n promptFile,\n cleanup: !workspace,\n workspaceDir: workspace,\n includeMetricsFooter: post && !localMode,\n onEvent: handleEvent,\n bedrockTags,\n localMode,\n diffAgainst,\n });\n const reviewText = renderMarkdown(review);\n\n streamLog(chalk.green(\"✔ Review complete!\"));\n\n if (codeQuality) {\n try {\n const { formatCodeQualityReport } = await import(\"./codequality.js\");\n const { writeFileSync } = await import(\"node:fs\");\n writeFileSync(\n codeQuality,\n formatCodeQualityReport(review, process.env.CI_PROJECT_DIR ?? workspacePath),\n \"utf-8\",\n );\n log(chalk.dim(`Wrote code quality report to ${codeQuality}`));\n } catch (err) {\n log(chalk.yellow(`Failed to write code quality report: ${err}`));\n }\n }\n\n if (post && prUrl) {\n log(chalk.cyan(\"\\nPosting review to PR/MR...\"));\n\n const platform = detectPlatform(prUrl);\n const useStructured = platform === \"gitlab\" && reviewStyle !== \"summary\";\n\n let result: PostCommentResult;\n if (useStructured) {\n result = await postReviewStructured({\n prUrl,\n review,\n model,\n metricsFooter,\n reviewStyle: reviewStyle ?? \"hybrid\",\n commitStatus,\n headSha,\n workspacePath,\n });\n } else {\n result = await postReviewComment({\n prUrl,\n reviewText,\n model,\n metricsFooter,\n headSha,\n });\n\n if (platform === \"gitlab\" && commitStatus) {\n try {\n const { getGitlabMrDiffRefs } = await import(\"./gitlab.js\");\n const parsed = parsePrUrl(prUrl);\n const diffRefs = await getGitlabMrDiffRefs(\n parsed.owner,\n parsed.repo,\n parsed.prNumber,\n parsed.host,\n );\n await postGitlabReviewCommitStatus(parsed, review, diffRefs);\n } catch (err) {\n result = {\n success: false,\n platform: \"gitlab\",\n mrNumber: parsePrUrl(prUrl).prNumber,\n error: `Failed to post commit status: ${err instanceof Error ? err.message : err}`,\n };\n }\n }\n }\n\n if (result.success) {\n log(chalk.bold.green(\"Review posted successfully!\"));\n log(chalk.dim(` ${platform === \"gitlab\" ? \"MR\" : \"PR\"}: ${prUrl}`));\n } else {\n log(chalk.bold.red(`Failed to post review: ${result.error}`));\n log(chalk.yellow(\"\\nReview output:\\n\"));\n console.log(reviewText);\n }\n } else {\n log(chalk.bold.green(\"Review Complete\\n\"));\n console.log(reviewText);\n if (!localMode) {\n log(chalk.dim(\"\\nTip: Use --post to automatically post this review to the PR/MR\"));\n }\n }\n\n // Push metrics to Prometheus Pushgateway (best-effort, never fails the run)\n if (prometheusPush) {\n const labels: Record<string, string> = {\n platform,\n model,\n verdict: review.overall_correctness === \"patch is correct\" ? \"correct\" : \"incorrect\",\n };\n if (metricsProject) labels.project = metricsProject;\n if (metricsMrIid) labels.mr_iid = metricsMrIid;\n\n await pushMetrics({\n pushgatewayUrl: prometheusPush,\n metrics,\n findings: review.findings,\n labels,\n });\n }\n } catch (err) {\n streamLog(chalk.red(\"✗ Review failed\"));\n console.error(\n chalk.bold.red(`\\nError: ${err instanceof Error ? err.message : err}`),\n );\n if (verbose && err instanceof Error && err.stack) {\n console.error(chalk.dim(err.stack));\n }\n process.exit(1);\n }\n });\n\nprogram.parse();\n"],"mappings":"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","names":["platform"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
relativizeWorkspacePath
|
|
3
|
+
} from "./chunk-AMUK6GDX.js";
|
|
4
|
+
|
|
5
|
+
// src/codequality.ts
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
var PRIORITY_TO_SEVERITY = {
|
|
8
|
+
0: "critical",
|
|
9
|
+
1: "major",
|
|
10
|
+
2: "minor",
|
|
11
|
+
3: "info"
|
|
12
|
+
};
|
|
13
|
+
function fingerprint(finding, relativePath) {
|
|
14
|
+
const input = `${finding.title}:${relativePath}:${finding.code_location.line_range.start}`;
|
|
15
|
+
return createHash("md5").update(input).digest("hex");
|
|
16
|
+
}
|
|
17
|
+
function formatCodeQualityReport(review, workspacePrefix) {
|
|
18
|
+
const issues = review.findings.map((finding) => {
|
|
19
|
+
const relPath = relativizeWorkspacePath(finding.code_location.absolute_file_path, workspacePrefix);
|
|
20
|
+
return {
|
|
21
|
+
type: "issue",
|
|
22
|
+
check_name: `hodor/P${finding.priority}`,
|
|
23
|
+
description: finding.title,
|
|
24
|
+
content: { body: finding.body },
|
|
25
|
+
categories: ["Bug Risk"],
|
|
26
|
+
severity: PRIORITY_TO_SEVERITY[finding.priority] ?? "info",
|
|
27
|
+
location: {
|
|
28
|
+
path: relPath,
|
|
29
|
+
lines: {
|
|
30
|
+
begin: finding.code_location.line_range.start,
|
|
31
|
+
end: finding.code_location.line_range.end
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
fingerprint: fingerprint(finding, relPath)
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
return JSON.stringify(issues, null, 2);
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
formatCodeQualityReport
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=codequality-DTJK2LGF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/codequality.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { ReviewOutput, ReviewFinding, ReviewPriority } from \"./types.js\";\nimport { relativizeWorkspacePath } from \"./utils/path.js\";\n\nconst PRIORITY_TO_SEVERITY: Record<ReviewPriority, string> = {\n 0: \"critical\",\n 1: \"major\",\n 2: \"minor\",\n 3: \"info\",\n};\n\nfunction fingerprint(finding: ReviewFinding, relativePath: string): string {\n const input = `${finding.title}:${relativePath}:${finding.code_location.line_range.start}`;\n return createHash(\"md5\").update(input).digest(\"hex\");\n}\n\nexport function formatCodeQualityReport(\n review: ReviewOutput,\n workspacePrefix?: string,\n): string {\n const issues = review.findings.map((finding) => {\n const relPath = relativizeWorkspacePath(finding.code_location.absolute_file_path, workspacePrefix);\n return {\n type: \"issue\",\n check_name: `hodor/P${finding.priority}`,\n description: finding.title,\n content: { body: finding.body },\n categories: [\"Bug Risk\"],\n severity: PRIORITY_TO_SEVERITY[finding.priority] ?? \"info\",\n location: {\n path: relPath,\n lines: {\n begin: finding.code_location.line_range.start,\n end: finding.code_location.line_range.end,\n },\n },\n fingerprint: fingerprint(finding, relPath),\n };\n });\n\n return JSON.stringify(issues, null, 2);\n}\n"],"mappings":";;;;;AAAA,SAAS,kBAAkB;AAI3B,IAAM,uBAAuD;AAAA,EAC3D,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEA,SAAS,YAAY,SAAwB,cAA8B;AACzE,QAAM,QAAQ,GAAG,QAAQ,KAAK,IAAI,YAAY,IAAI,QAAQ,cAAc,WAAW,KAAK;AACxF,SAAO,WAAW,KAAK,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AACrD;AAEO,SAAS,wBACd,QACA,iBACQ;AACR,QAAM,SAAS,OAAO,SAAS,IAAI,CAAC,YAAY;AAC9C,UAAM,UAAU,wBAAwB,QAAQ,cAAc,oBAAoB,eAAe;AACjG,WAAO;AAAA,MACL,MAAM;AAAA,MACN,YAAY,UAAU,QAAQ,QAAQ;AAAA,MACtC,aAAa,QAAQ;AAAA,MACrB,SAAS,EAAE,MAAM,QAAQ,KAAK;AAAA,MAC9B,YAAY,CAAC,UAAU;AAAA,MACvB,UAAU,qBAAqB,QAAQ,QAAQ,KAAK;AAAA,MACpD,UAAU;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,UACL,OAAO,QAAQ,cAAc,WAAW;AAAA,UACxC,KAAK,QAAQ,cAAc,WAAW;AAAA,QACxC;AAAA,MACF;AAAA,MACA,aAAa,YAAY,SAAS,OAAO;AAAA,IAC3C;AAAA,EACF,CAAC;AAED,SAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;AACvC;","names":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GitLabAPIError,
|
|
3
|
+
HODOR_REVIEW_MARKER,
|
|
4
|
+
bulkPublishGitlabDraftNotes,
|
|
5
|
+
cleanupHodorComments,
|
|
6
|
+
createGitlabDraftNote,
|
|
7
|
+
fetchGitlabMrInfo,
|
|
8
|
+
getGitlabMrDiffRefs,
|
|
9
|
+
listHodorDiscussions,
|
|
10
|
+
parseGlabPaginatedJson,
|
|
11
|
+
postGitlabCommitStatus,
|
|
12
|
+
postGitlabInlineComment,
|
|
13
|
+
postGitlabMrComment,
|
|
14
|
+
resolveGitlabDiscussions,
|
|
15
|
+
summarizeGitlabNotes
|
|
16
|
+
} from "./chunk-DVJVQTVW.js";
|
|
17
|
+
export {
|
|
18
|
+
GitLabAPIError,
|
|
19
|
+
HODOR_REVIEW_MARKER,
|
|
20
|
+
bulkPublishGitlabDraftNotes,
|
|
21
|
+
cleanupHodorComments,
|
|
22
|
+
createGitlabDraftNote,
|
|
23
|
+
fetchGitlabMrInfo,
|
|
24
|
+
getGitlabMrDiffRefs,
|
|
25
|
+
listHodorDiscussions,
|
|
26
|
+
parseGlabPaginatedJson,
|
|
27
|
+
postGitlabCommitStatus,
|
|
28
|
+
postGitlabInlineComment,
|
|
29
|
+
postGitlabMrComment,
|
|
30
|
+
resolveGitlabDiscussions,
|
|
31
|
+
summarizeGitlabNotes
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=gitlab-5TWHYHEP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { ThinkingLevel } from '@earendil-works/pi-ai';
|
|
2
|
+
|
|
3
|
+
type Platform = "github" | "gitlab" | "gitea";
|
|
2
4
|
interface ParsedPrUrl {
|
|
3
5
|
owner: string;
|
|
4
6
|
repo: string;
|
|
@@ -61,6 +63,7 @@ interface ReviewFinding {
|
|
|
61
63
|
end: number;
|
|
62
64
|
};
|
|
63
65
|
};
|
|
66
|
+
suggestion?: string;
|
|
64
67
|
}
|
|
65
68
|
interface ReviewOutput {
|
|
66
69
|
findings: ReviewFinding[];
|
|
@@ -75,6 +78,15 @@ interface PostCommentResult {
|
|
|
75
78
|
error?: string;
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Render structured review output into clean markdown for PR/MR comments.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Render a ReviewOutput into clean markdown for posting as a PR/MR comment.
|
|
87
|
+
*/
|
|
88
|
+
declare function renderMarkdown(review: ReviewOutput): string;
|
|
89
|
+
|
|
78
90
|
interface AgentProgressEvent {
|
|
79
91
|
type: "tool_start" | "tool_end" | "thinking" | "turn_start" | "turn_end" | "agent_start" | "agent_end" | "text_delta" | "thinking_delta" | "tool_result";
|
|
80
92
|
toolName?: string;
|
|
@@ -111,6 +123,7 @@ declare function reviewPr(opts: {
|
|
|
111
123
|
metricsFooter: string | null;
|
|
112
124
|
headSha: string | null;
|
|
113
125
|
metrics: ReviewMetrics;
|
|
126
|
+
workspacePath: string;
|
|
114
127
|
}>;
|
|
115
128
|
|
|
116
129
|
declare function buildPrReviewPrompt(opts: {
|
|
@@ -139,19 +152,22 @@ declare function parseModelString(model: string): ParsedModel;
|
|
|
139
152
|
* Map reasoning effort strings to pi-ai thinking levels.
|
|
140
153
|
* Returns undefined for no reasoning.
|
|
141
154
|
*/
|
|
142
|
-
declare function mapReasoningEffort(effort: string | undefined):
|
|
155
|
+
declare function mapReasoningEffort(effort: string | undefined): ThinkingLevel | undefined;
|
|
143
156
|
/**
|
|
144
157
|
* Get API key with provider-aware selection.
|
|
145
158
|
*
|
|
146
159
|
* Priority:
|
|
147
160
|
* 1. LLM_API_KEY (universal override)
|
|
148
|
-
* 2. Provider-specific key (ANTHROPIC_API_KEY, OPENAI_API_KEY
|
|
149
|
-
*
|
|
161
|
+
* 2. Provider-specific key known by pi-ai (ANTHROPIC_API_KEY, OPENAI_API_KEY,
|
|
162
|
+
* OPENROUTER_API_KEY, etc.)
|
|
150
163
|
*
|
|
151
164
|
* Returns null for bedrock (uses AWS credentials).
|
|
152
165
|
*/
|
|
153
166
|
declare function getApiKey(model?: string): string | null;
|
|
154
167
|
|
|
168
|
+
type FindingPriority = {
|
|
169
|
+
priority: number;
|
|
170
|
+
};
|
|
155
171
|
declare function formatMetricsMarkdown(metrics: ReviewMetrics): string;
|
|
156
172
|
declare function printMetrics(metrics: ReviewMetrics, stream?: NodeJS.WritableStream): void;
|
|
157
173
|
/**
|
|
@@ -161,18 +177,10 @@ declare function printMetrics(metrics: ReviewMetrics, stream?: NodeJS.WritableSt
|
|
|
161
177
|
declare function pushMetrics(opts: {
|
|
162
178
|
pushgatewayUrl: string;
|
|
163
179
|
metrics: ReviewMetrics;
|
|
180
|
+
findings?: FindingPriority[];
|
|
164
181
|
labels?: Record<string, string>;
|
|
165
182
|
}): Promise<void>;
|
|
166
183
|
|
|
167
184
|
declare function validateReviewOutput(review: ReviewOutput): ReviewOutput;
|
|
168
185
|
|
|
169
|
-
/**
|
|
170
|
-
* Render structured review output into clean markdown for PR/MR comments.
|
|
171
|
-
*/
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Render a ReviewOutput into clean markdown for posting as a PR/MR comment.
|
|
175
|
-
*/
|
|
176
|
-
declare function renderMarkdown(review: ReviewOutput): string;
|
|
177
|
-
|
|
178
186
|
export { type AgentProgressEvent, type MrMetadata, type NoteEntry, type ParsedPrUrl, type Platform, type PostCommentResult, type ReviewCorrectness, type ReviewFinding, type ReviewMetrics, type ReviewOutput, type ReviewPriority, buildPrReviewPrompt, detectPlatform, formatMetricsMarkdown, getApiKey, mapReasoningEffort, parseModelString, parsePrUrl, postReviewComment, printMetrics, pushMetrics, renderMarkdown, reviewPr, validateReviewOutput };
|
package/dist/index.js
CHANGED
|
@@ -9,10 +9,13 @@ import {
|
|
|
9
9
|
postReviewComment,
|
|
10
10
|
printMetrics,
|
|
11
11
|
pushMetrics,
|
|
12
|
-
renderMarkdown,
|
|
13
12
|
reviewPr,
|
|
14
13
|
validateReviewOutput
|
|
15
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-JSLG7EOX.js";
|
|
15
|
+
import {
|
|
16
|
+
renderMarkdown
|
|
17
|
+
} from "./chunk-DVJVQTVW.js";
|
|
18
|
+
import "./chunk-AMUK6GDX.js";
|
|
16
19
|
export {
|
|
17
20
|
buildPrReviewPrompt,
|
|
18
21
|
detectPlatform,
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.6.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",
|
|
@@ -43,19 +43,19 @@
|
|
|
43
43
|
"node": ">=22"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@sinclair/typebox": "^0.34.
|
|
47
|
-
"@anthropic-ai/sdk": "^0.
|
|
48
|
-
"@
|
|
49
|
-
"@
|
|
50
|
-
"chalk": "^5.
|
|
51
|
-
"commander": "^
|
|
52
|
-
"dotenv": "^
|
|
46
|
+
"@sinclair/typebox": "^0.34.49",
|
|
47
|
+
"@anthropic-ai/sdk": "^0.95.2",
|
|
48
|
+
"@earendil-works/pi-ai": "^0.74.0",
|
|
49
|
+
"@earendil-works/pi-coding-agent": "^0.74.0",
|
|
50
|
+
"chalk": "^5.6.2",
|
|
51
|
+
"commander": "^14.0.3",
|
|
52
|
+
"dotenv": "^17.4.2"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@types/node": "^
|
|
56
|
-
"tsup": "^8.
|
|
57
|
-
"tsx": "^4.
|
|
58
|
-
"typescript": "^
|
|
59
|
-
"vitest": "^
|
|
55
|
+
"@types/node": "^25.7.0",
|
|
56
|
+
"tsup": "^8.5.1",
|
|
57
|
+
"tsx": "^4.21.0",
|
|
58
|
+
"typescript": "^6.0.3",
|
|
59
|
+
"vitest": "^4.1.6"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/templates/tool-review.md
CHANGED
|
@@ -72,6 +72,16 @@ Always include the matching numeric priority field in the `submit_review` payloa
|
|
|
72
72
|
|
|
73
73
|
Output all findings that the original author would fix if they knew about it. If there is no finding that a person would definitely love to see and fix, prefer outputting no findings. Do not stop at the first qualifying finding. Continue until you've listed every qualifying finding.
|
|
74
74
|
|
|
75
|
+
### Contract Trace Checklist
|
|
76
|
+
|
|
77
|
+
For changes that introduce or modify routes, handlers, API parameters, auth/session/token logic, database schema or queries, cache keys, config contracts, or public interfaces:
|
|
78
|
+
|
|
79
|
+
1. Trace each externally supplied value through the layers it crosses: route/query/body/header → handler extraction and parsing → service method signature → DB/query/cache key → tests or mocks.
|
|
80
|
+
2. Compare semantic identity, not just variable names. Examples: public `user_id` string vs internal integer primary key, client/account ID vs database UID, token key vs full token value, app ID vs API key, enum label vs stored code, timestamp units/timezones, paise vs rupees.
|
|
81
|
+
3. Read the minimal adjacent convention needed when a changed file depends on it: nearby routes for the same resource, model/schema definitions, query files, auth middleware, or changed tests.
|
|
82
|
+
4. Treat a semantic mismatch as a concrete bug when production input will pass one kind of value but the changed code stores, queries, authorizes, or tests a different kind.
|
|
83
|
+
5. Keep this scoped to the diff. Do not browse unrelated code unless it defines a contract that the changed lines directly depend on.
|
|
84
|
+
|
|
75
85
|
### Additional Guidelines
|
|
76
86
|
|
|
77
87
|
- Ignore trivial style unless it obscures meaning or violates documented standards.
|
|
@@ -82,10 +92,30 @@ Output all findings that the original author would fix if they knew about it. If
|
|
|
82
92
|
|
|
83
93
|
{review_process_section}
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
### Conditional Review Lenses
|
|
96
|
+
|
|
97
|
+
After identifying the changed files and diff hunks, classify which lenses apply. Apply only the relevant lenses below; do not run a broad style review just because a lens exists.
|
|
98
|
+
|
|
99
|
+
- **Silent failure / error handling lens**: If the diff changes `try`/`catch`, rescue/recover blocks, fallback logic, retries, default values after failures, logging, metrics, or error returns, verify that failures are not swallowed, fallbacks are explicit and safe, and operators/users get enough context to debug the failing operation. Flag only concrete silent failures or misleading success paths.
|
|
100
|
+
- **Critical test gap lens**: If the diff adds behavior, changes edge-case handling, alters async/concurrent behavior, or fixes a bug, compare changed behavior against changed or existing tests. Flag missing tests only when the gap is likely to let a meaningful regression through; do not require line coverage or tests for trivial code.
|
|
101
|
+
- **Comment/documentation accuracy lens**: If the diff adds or modifies comments, docstrings, README snippets, API docs, or examples, verify the prose matches the changed code and commands. Flag comments or docs that are factually wrong, misleading, or likely to cause misuse; ignore harmless wording preferences.
|
|
102
|
+
- **Type/API invariant lens**: If the diff adds or changes types, schemas, DTOs, request/response shapes, config objects, database models, enums, or public interfaces, check whether invalid states became representable, required validation moved out of the boundary, or consumers can now pass semantically wrong values. Prefer concrete invariant or compatibility bugs over abstract design advice.
|
|
103
|
+
- **Simplification lens**: If the diff introduces complex branching, duplicated logic, clever one-liners, or unnecessary abstraction, mention it only when the complexity creates a plausible bug, hides an important invariant, or makes future fixes risky. Do not suggest cosmetic refactors.
|
|
104
|
+
|
|
105
|
+
### Attack Surface Analysis
|
|
106
|
+
|
|
107
|
+
When the diff touches the areas below, investigate them specifically — do not skip a category unless it is provably inapplicable to the changed code:
|
|
108
|
+
|
|
109
|
+
- **Race conditions / TOCTOU**: Shared mutable state, non-atomic check-then-act sequences, missing locks or transactions
|
|
110
|
+
- **Off-by-one / boundary errors**: Loop bounds, slice indices, size comparisons (`<` vs `<=`), pagination math, overflow
|
|
111
|
+
- **Schema / contract drift**: Renamed fields, changed types, enum values, units (paise vs rupees, ms vs s), encoding assumptions, wire format changes
|
|
112
|
+
- **Auth / permission gaps**: New routes or handlers missing auth middleware, privilege escalation paths, token or session misuse, missing ownership checks
|
|
113
|
+
- **Rollback safety**: Migrations that cannot be rolled back cleanly, stateful side effects committed before DB transactions complete, partial-write failure modes
|
|
114
|
+
- **Data loss / silent discard**: Early returns or swallowed errors that drop user data, leave state inconsistent, or suppress failures silently
|
|
115
|
+
- **Observability gaps**: New error paths unreachable from logs or metrics, silent failures, missing structured context on errors
|
|
116
|
+
- **Input validation**: Unvalidated user-supplied values crossing trust boundaries — SQL injection, shell injection, path traversal, template injection, deserialization
|
|
117
|
+
|
|
118
|
+
Default to skepticism: a change that looks mechanical (rename, refactor, constant tweak) may still introduce one of these. If a category is provably inapplicable, skip it — but verify, don't assume.
|
|
89
119
|
|
|
90
120
|
## Final Submission
|
|
91
121
|
|
|
@@ -103,7 +133,8 @@ When you are done, call `submit_review` exactly once with the final structured r
|
|
|
103
133
|
"code_location": {
|
|
104
134
|
"absolute_file_path": "<absolute file path>",
|
|
105
135
|
"line_range": {"start": <int>, "end": <int>}
|
|
106
|
-
}
|
|
136
|
+
},
|
|
137
|
+
"suggestion": "<optional: exact replacement code for the flagged line range>"
|
|
107
138
|
}
|
|
108
139
|
],
|
|
109
140
|
"overall_correctness": "patch is correct" | "patch is incorrect",
|
|
@@ -117,9 +148,12 @@ When you are done, call `submit_review` exactly once with the final structured r
|
|
|
117
148
|
* Do not print the review as normal assistant text.
|
|
118
149
|
* Do not wrap the payload in markdown fences when calling the tool.
|
|
119
150
|
* If there are no findings, submit `"findings": []`.
|
|
151
|
+
* If `findings` is non-empty, `overall_correctness` must be `"patch is incorrect"`.
|
|
152
|
+
* If `findings` is empty, `overall_correctness` must be `"patch is correct"`.
|
|
120
153
|
* Every finding must include `title`, `body`, `priority`, and `code_location`.
|
|
121
154
|
* Use absolute file paths (for example, `/workspace/path/to/file.py`) not relative paths.
|
|
122
155
|
* The title must start with a priority tag: `[P0]`, `[P1]`, `[P2]`, or `[P3]`.
|
|
123
156
|
* `overall_correctness` must be exactly `"patch is correct"` or `"patch is incorrect"`.
|
|
157
|
+
* If you can suggest a specific code fix for a finding, include the replacement code in the `suggestion` field. This should be the exact code to replace the lines in `line_range`, without markdown fences or extra context. Omit `suggestion` if no specific fix is available.
|
|
124
158
|
|
|
125
159
|
{start_instruction}
|