@mrkaran/hodor 0.7.1 → 0.7.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.
@@ -502,6 +502,25 @@ async function bulkPublishGitlabDraftNotes(owner, repo, mrNumber, host) {
502
502
  throw new GitLabAPIError(`Failed to bulk publish draft notes for MR !${mrNumber}: ${msg}`);
503
503
  }
504
504
  }
505
+ async function publishGitlabDraftNote(owner, repo, mrNumber, draftNoteId, host) {
506
+ const encoded = encodedProjectPath(owner, repo);
507
+ const env = glabEnv(host);
508
+ try {
509
+ await exec(
510
+ "glab",
511
+ [
512
+ "api",
513
+ `projects/${encoded}/merge_requests/${mrNumber}/draft_notes/${draftNoteId}/publish`,
514
+ "--method",
515
+ "PUT"
516
+ ],
517
+ { env }
518
+ );
519
+ } catch (err) {
520
+ const msg = err instanceof Error ? err.message : String(err);
521
+ throw new GitLabAPIError(`Failed to publish draft note ${draftNoteId} for MR !${mrNumber}: ${msg}`);
522
+ }
523
+ }
505
524
  async function postGitlabCommitStatus(owner, repo, sha, state, host, opts) {
506
525
  const allowedStates = /* @__PURE__ */ new Set([
507
526
  "pending",
@@ -647,8 +666,9 @@ export {
647
666
  getGitlabMrDiffRefs,
648
667
  createGitlabDraftNote,
649
668
  bulkPublishGitlabDraftNotes,
669
+ publishGitlabDraftNote,
650
670
  postGitlabCommitStatus,
651
671
  listHodorDiscussions,
652
672
  resolveGitlabDiscussions
653
673
  };
654
- //# sourceMappingURL=chunk-W7ZUJIFT.js.map
674
+ //# sourceMappingURL=chunk-DALI4QRT.js.map
@@ -1 +1 @@
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[-:]/;\nconst HODOR_CACHE_MARKER_RE = /<!--\\s*hodor:cache:v1:[A-Za-z0-9_-]+\\s*-->\\s*/g;\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\nexport function isHodorGeneratedNote(body: unknown): boolean {\n return isHodorNote(body);\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 return summarizeNotes(notes, maxEntries, (note) => !isHodorNote(note.body));\n}\n\nexport function summarizeHodorNotes(\n notes: NoteEntry[] | undefined | null,\n maxEntries = 5,\n): string {\n return summarizeNotes(notes, maxEntries, (note) => isHodorNote(note.body));\n}\n\nfunction summarizeNotes(\n notes: NoteEntry[] | undefined | null,\n maxEntries: number,\n include: (note: NoteEntry) => boolean,\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 if (!include(note)) continue;\n // Cache payloads are machine-only and can be large. Never feed their\n // compressed representation back into reviewer context.\n const body = (note.body ?? \"\").replace(HODOR_CACHE_MARKER_RE, \"\").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 boundedBody = body.length > 2_000 ? `${body.slice(0, 1_999).trimEnd()}…` : body;\n const indentedBody = boundedBody.split(\"\\n\").join(\"\\n \");\n lines.push(`${header}\\n ${indentedBody}`);\n }\n\n return lines.join(\"\\n\");\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 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 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"]}
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[-:]/;\nconst HODOR_CACHE_MARKER_RE = /<!--\\s*hodor:cache:v1:[A-Za-z0-9_-]+\\s*-->\\s*/g;\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\nexport function isHodorGeneratedNote(body: unknown): boolean {\n return isHodorNote(body);\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 return summarizeNotes(notes, maxEntries, (note) => !isHodorNote(note.body));\n}\n\nexport function summarizeHodorNotes(\n notes: NoteEntry[] | undefined | null,\n maxEntries = 5,\n): string {\n return summarizeNotes(notes, maxEntries, (note) => isHodorNote(note.body));\n}\n\nfunction summarizeNotes(\n notes: NoteEntry[] | undefined | null,\n maxEntries: number,\n include: (note: NoteEntry) => boolean,\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 if (!include(note)) continue;\n // Cache payloads are machine-only and can be large. Never feed their\n // compressed representation back into reviewer context.\n const body = (note.body ?? \"\").replace(HODOR_CACHE_MARKER_RE, \"\").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 boundedBody = body.length > 2_000 ? `${body.slice(0, 1_999).trimEnd()}…` : body;\n const indentedBody = boundedBody.split(\"\\n\").join(\"\\n \");\n lines.push(`${header}\\n ${indentedBody}`);\n }\n\n return lines.join(\"\\n\");\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 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\nexport async function publishGitlabDraftNote(\n owner: string,\n repo: string,\n mrNumber: number | string,\n draftNoteId: 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/${draftNoteId}/publish`,\n \"--method\",\n \"PUT\",\n ],\n { env },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new GitLabAPIError(`Failed to publish draft note ${draftNoteId} 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 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"]}
@@ -10,12 +10,13 @@ import {
10
10
  logger,
11
11
  postGitlabCommitStatus,
12
12
  postGitlabMrComment,
13
+ publishGitlabDraftNote,
13
14
  renderMarkdown,
14
15
  renderSummaryMarkdown,
15
16
  resolveGitlabDiscussions,
16
17
  summarizeGitlabNotes,
17
18
  summarizeHodorNotes
18
- } from "./chunk-W7ZUJIFT.js";
19
+ } from "./chunk-DALI4QRT.js";
19
20
  import {
20
21
  relativizeWorkspacePath
21
22
  } from "./chunk-AMUK6GDX.js";
@@ -1144,6 +1145,7 @@ async function postReviewStructured(opts) {
1144
1145
  let inlineCreated = 0;
1145
1146
  let inlineFailed = 0;
1146
1147
  let inlineDeduplicated = 0;
1148
+ const draftNoteIds = [];
1147
1149
  for (const finding of review.findings) {
1148
1150
  const fingerprint = getFindingFingerprint(finding, workspacePath);
1149
1151
  if (existingByFingerprint.has(fingerprint)) {
@@ -1170,7 +1172,7 @@ ${finding.suggestion}
1170
1172
  \`\`\``;
1171
1173
  }
1172
1174
  try {
1173
- await createGitlabDraftNote(
1175
+ const draftNote = await createGitlabDraftNote(
1174
1176
  parsed.owner,
1175
1177
  parsed.repo,
1176
1178
  parsed.prNumber,
@@ -1182,6 +1184,9 @@ ${finding.suggestion}
1182
1184
  diffRefs
1183
1185
  }
1184
1186
  );
1187
+ if (typeof draftNote.id === "number" || typeof draftNote.id === "string") {
1188
+ draftNoteIds.push(draftNote.id);
1189
+ }
1185
1190
  inlineCreated++;
1186
1191
  } catch (error) {
1187
1192
  const message = error instanceof Error ? error.message : String(error);
@@ -1205,8 +1210,32 @@ ${finding.suggestion}
1205
1210
  draftsPublished = true;
1206
1211
  } catch (error) {
1207
1212
  const message = error instanceof Error ? error.message : String(error);
1208
- errors.push(`draft publish: ${message}`);
1209
1213
  logger.warn(`Failed to bulk publish draft notes: ${message}`);
1214
+ if (draftNoteIds.length === inlineCreated) {
1215
+ let individuallyPublished = 0;
1216
+ for (const draftNoteId of draftNoteIds) {
1217
+ try {
1218
+ await publishGitlabDraftNote(
1219
+ parsed.owner,
1220
+ parsed.repo,
1221
+ parsed.prNumber,
1222
+ draftNoteId,
1223
+ parsed.host
1224
+ );
1225
+ individuallyPublished++;
1226
+ } catch (publishError) {
1227
+ const publishMessage = publishError instanceof Error ? publishError.message : String(publishError);
1228
+ errors.push(`draft publish: ${publishMessage}`);
1229
+ logger.warn(`Failed to publish draft note ${draftNoteId}: ${publishMessage}`);
1230
+ }
1231
+ }
1232
+ draftsPublished = individuallyPublished === inlineCreated;
1233
+ if (draftsPublished) {
1234
+ logger.info(`Published ${individuallyPublished} draft note(s) individually`);
1235
+ }
1236
+ } else {
1237
+ errors.push(`draft publish: ${message}`);
1238
+ }
1210
1239
  }
1211
1240
  }
1212
1241
  let summaryPosted = false;
@@ -2796,4 +2825,4 @@ export {
2796
2825
  postReviewStructured,
2797
2826
  reviewPr
2798
2827
  };
2799
- //# sourceMappingURL=chunk-VERO4XYJ.js.map
2828
+ //# sourceMappingURL=chunk-YKB3BRJM.js.map