@weareikko/code-review 0.8.3 → 0.8.5

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.
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli-CICuhytH.js","names":[],"sources":["../src/errors.ts","../src/github.ts","../src/fingerprints.ts","../src/product.ts","../src/posting.ts","../src/types.ts","../src/config.ts","../src/diagnostics.ts","../src/git.ts","../src/logger.ts","../node_modules/jsonrepair/lib/esm/utils/JSONRepairError.js","../node_modules/jsonrepair/lib/esm/utils/stringUtils.js","../node_modules/jsonrepair/lib/esm/regular/jsonrepair.js","../src/verify.ts","../src/parser.ts","../src/prior-threads.ts","../src/skills.ts","../src/skipped-retrieval.ts","../src/triage.ts","../src/gitlab-review.ts","../src/otel.ts","../src/diff-lines.ts","../src/payloads.ts","../src/platforms/github.ts","../src/gitlab.ts","../src/platforms/gitlab.ts","../src/platform.ts","../src/summary-carryover.ts","../src/cli.ts"],"sourcesContent":["export type ErrorCode =\n | 'CONFIG_ERROR'\n | 'GITLAB_API_ERROR'\n | 'GITHUB_API_ERROR'\n | 'GIT_ERROR'\n | 'REVIEWER_ERROR'\n | 'PARSE_ERROR'\n | 'RUNTIME_ERROR';\n\nexport interface GitlabReviewErrorOptions extends ErrorOptions {\n code: ErrorCode;\n hint?: string;\n /**\n * Marks a deadline-exceeded failure. The CLI's `code` taxonomy stays coarse\n * (one code per subsystem), so timeouts are flagged here rather than via a\n * separate code; the OTel bridge reads it to label runs `status=timeout`.\n */\n timeout?: boolean;\n /**\n * Marks a provider credit/quota-exhaustion failure (e.g. HTTP 402). The review\n * could not run for reasons outside the MR's control, so the CLI treats it as\n * a non-fatal skip (warn, exit 0) rather than failing the pipeline.\n */\n quotaExceeded?: boolean;\n}\n\nexport class GitlabReviewError extends Error {\n readonly code: ErrorCode;\n readonly hint?: string;\n readonly timeout: boolean;\n readonly quotaExceeded: boolean;\n\n constructor(message: string, options: GitlabReviewErrorOptions) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.code = options.code;\n this.hint = options.hint;\n this.timeout = options.timeout ?? false;\n this.quotaExceeded = options.quotaExceeded ?? false;\n }\n}\n\n/**\n * Patterns that identify a provider credit/quota-exhaustion error across\n * providers (Anthropic, OpenAI, Cloudflare AI Gateway, …). Matched against the\n * provider's error message. Deliberately excludes transient rate limits (429),\n * which are retryable rather than a billing dead-end.\n */\nconst QUOTA_EXCEEDED_PATTERNS: readonly RegExp[] = [\n /payment required/i,\n /insufficient[^.]*credit/i,\n /out of credit/i,\n /credit balance is too low/i,\n /insufficient_quota/i,\n /exceeded your current quota/i,\n /billing (?:hard )?limit/i,\n];\n\n/** True when `message` looks like a provider credit/quota-exhaustion error. */\nexport function isQuotaExceededMessage(message: string | undefined): boolean {\n if (!message) return false;\n return QUOTA_EXCEEDED_PATTERNS.some((pattern) => pattern.test(message));\n}\n\n/** True when `error` is (or reports) a provider credit/quota-exhaustion failure. */\nexport function isQuotaExceededError(error: unknown): boolean {\n if (error instanceof GitlabReviewError && error.quotaExceeded) return true;\n if (error instanceof Error) return isQuotaExceededMessage(error.message);\n return false;\n}\n\nexport class ConfigError extends GitlabReviewError {\n constructor(message: string, options: Omit<GitlabReviewErrorOptions, 'code'> = {}) {\n super(message, { ...options, code: 'CONFIG_ERROR' });\n }\n}\n\nexport class GitLabApiError extends GitlabReviewError {\n readonly method: string;\n readonly path: string;\n readonly status?: number;\n readonly responseBody?: string;\n\n constructor(\n message: string,\n options: Omit<GitlabReviewErrorOptions, 'code'> & {\n method: string;\n path: string;\n status?: number;\n responseBody?: string;\n },\n ) {\n super(message, { ...options, code: 'GITLAB_API_ERROR' });\n this.method = options.method;\n this.path = options.path;\n this.status = options.status;\n this.responseBody = options.responseBody;\n }\n}\n\nexport class GitHubApiError extends GitlabReviewError {\n readonly method: string;\n readonly path: string;\n readonly status?: number;\n readonly responseBody?: string;\n\n constructor(\n message: string,\n options: Omit<GitlabReviewErrorOptions, 'code'> & {\n method: string;\n path: string;\n status?: number;\n responseBody?: string;\n },\n ) {\n super(message, { ...options, code: 'GITHUB_API_ERROR' });\n this.method = options.method;\n this.path = options.path;\n this.status = options.status;\n this.responseBody = options.responseBody;\n }\n}\n\nexport class GitError extends GitlabReviewError {\n constructor(message: string, options: Omit<GitlabReviewErrorOptions, 'code'> = {}) {\n super(message, { ...options, code: 'GIT_ERROR' });\n }\n}\n\nexport class ReviewerError extends GitlabReviewError {\n constructor(message: string, options: Omit<GitlabReviewErrorOptions, 'code'> = {}) {\n super(message, { ...options, code: 'REVIEWER_ERROR' });\n }\n}\n\nexport class ParseError extends GitlabReviewError {\n constructor(message: string, options: Omit<GitlabReviewErrorOptions, 'code'> = {}) {\n super(message, { ...options, code: 'PARSE_ERROR' });\n }\n}\n\nexport class RuntimeError extends GitlabReviewError {\n constructor(message: string, options: Omit<GitlabReviewErrorOptions, 'code'> = {}) {\n super(message, { ...options, code: 'RUNTIME_ERROR' });\n }\n}\n\nexport function formatError(error: unknown): string {\n if (error instanceof GitlabReviewError) {\n const lines = [`[${error.code}] ${error.message}`];\n if (error.hint) lines.push(`Hint: ${error.hint}`);\n if (\n (error instanceof GitLabApiError || error instanceof GitHubApiError) &&\n error.responseBody\n ) {\n lines.push(`Response: ${error.responseBody}`);\n }\n return lines.join('\\n');\n }\n\n return error instanceof Error ? error.message : String(error);\n}\n","import { GitHubApiError } from './errors.js';\n\n/**\n * Metadata about a single completed HTTP request, reported to `onResponse`.\n * Deliberately telemetry-agnostic: the OTel bridge maps these onto HTTP\n * semantic-convention span attributes, but the client itself has no OTel\n * dependency. Carries no secrets — the token lives in a request header, not\n * the URL.\n */\nexport interface GitHubResponseInfo {\n method: string;\n path: string;\n url: string;\n status: number;\n /** Parsed Content-Length header in bytes, when the response provided one. */\n responseContentLength?: number;\n}\n\nexport interface GitHubClientOptions {\n /**\n * REST API base, honoring `GITHUB_API_URL` (default `https://api.github.com`;\n * GitHub Enterprise sets it to e.g. `https://ghe.example.com/api/v3`). Paths\n * are appended directly, so the base must already include any `/api/v3` prefix.\n */\n apiUrl?: string;\n token: string;\n fetchImpl?: typeof fetch;\n requestTimeout?: number;\n /**\n * Optional instrumentation callback invoked once per completed HTTP response\n * (success or error status), before any error is thrown. Used to surface HTTP\n * metadata to diagnostics/OTel without coupling the client to those layers.\n */\n onResponse?: (info: GitHubResponseInfo) => void;\n}\n\nexport const DEFAULT_GITHUB_API_URL = 'https://api.github.com';\nconst DEFAULT_REQUEST_TIMEOUT_MS = 60_000;\nconst GITHUB_ACCEPT = 'application/vnd.github+json';\nconst GITHUB_API_VERSION = '2022-11-28';\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError';\n}\n\n/**\n * Extract the `rel=\"next\"` URL from a GitHub `Link` response header, or `null`\n * when there is no next page. GitHub paginates with absolute URLs in this header\n * rather than the page-number headers GitLab uses.\n */\nexport function parseNextLink(header: string | null | undefined): string | null {\n if (!header) return null;\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"next\"/);\n if (match) return match[1];\n }\n return null;\n}\n\nexport interface PullRequestRef {\n ref: string;\n sha: string;\n}\n\nexport interface PullRequest {\n head: PullRequestRef;\n base: PullRequestRef;\n /** PR title — the one-line declared intent of the change. May be empty. */\n title?: string;\n /** PR body — the author's full reasoning / decision log. May be empty or null. */\n body?: string | null;\n}\n\nexport interface GitHubUser {\n id: number;\n login: string;\n}\n\n/** An inline pull-request review comment (positioned against the diff). */\nexport interface PullRequestReviewComment {\n id: number;\n body?: string | null;\n path?: string;\n /** Line in the diff's new file, when the comment still anchors to it. */\n line?: number | null;\n original_line?: number | null;\n side?: string | null;\n in_reply_to_id?: number;\n user?: GitHubUser | null;\n}\n\n/** A non-positional issue/PR-level comment (the summary note lives here). */\nexport interface IssueComment {\n id: number;\n body?: string | null;\n user?: GitHubUser | null;\n}\n\n/** A single inline comment in a batched review payload. */\nexport interface ReviewCommentInput {\n path: string;\n body: string;\n line?: number;\n side?: string;\n start_line?: number;\n start_side?: string;\n}\n\n/**\n * Payload for a batched pull-request review. Posted as one atomic review\n * (`event: 'COMMENT'`) to avoid per-comment secondary rate limits.\n */\nexport interface CreateReviewInput {\n commit_id: string;\n event?: string;\n body?: string;\n comments?: ReviewCommentInput[];\n}\n\nexport interface Review {\n id: number;\n}\n\n/** Minimal shape of the `reviewThreads` GraphQL query response we consume. */\ninterface ReviewThreadsResponse {\n repository?: {\n pullRequest?: {\n reviewThreads?: {\n pageInfo?: { hasNextPage?: boolean; endCursor?: string | null };\n nodes?: {\n isResolved?: boolean;\n comments?: { nodes?: { databaseId?: number | null }[] };\n }[];\n };\n };\n } | null;\n}\n\nconst REVIEW_THREADS_QUERY = `\n query ($owner: String!, $repo: String!, $pull: Int!, $cursor: String) {\n repository(owner: $owner, name: $repo) {\n pullRequest(number: $pull) {\n reviewThreads(first: 100, after: $cursor) {\n pageInfo { hasNextPage endCursor }\n nodes {\n isResolved\n comments(first: 100) { nodes { databaseId } }\n }\n }\n }\n }\n }`;\n\nexport class GitHubClient {\n private readonly base: string;\n private readonly token: string;\n private readonly fetchImpl: typeof fetch;\n private readonly requestTimeout: number;\n private readonly onResponse?: (info: GitHubResponseInfo) => void;\n\n constructor(options: GitHubClientOptions) {\n this.base = (options.apiUrl ?? DEFAULT_GITHUB_API_URL).replace(/\\/$/, '');\n this.token = options.token;\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.requestTimeout = options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;\n this.onResponse = options.onResponse;\n }\n\n private reportResponse(method: string, path: string, url: string, response: Response): void {\n if (!this.onResponse) return;\n const header = response.headers.get('content-length');\n // Treat a present-but-blank header as absent: Number('') / Number(' ') are 0\n // (finite), which would otherwise be reported as a real body size of 0.\n const length = header !== null && header.trim() !== '' ? Number(header) : NaN;\n this.onResponse({\n method,\n path,\n url,\n status: response.status,\n responseContentLength: Number.isFinite(length) ? length : undefined,\n });\n }\n\n url(path: string, query: Record<string, string | number | boolean | undefined> = {}): string {\n const url = new URL(`${this.base}${path}`);\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n return url.toString();\n }\n\n private headers(headers?: Record<string, string>): Record<string, string> {\n return {\n Authorization: `Bearer ${this.token}`,\n Accept: GITHUB_ACCEPT,\n 'X-GitHub-Api-Version': GITHUB_API_VERSION,\n ...headers,\n };\n }\n\n private async fetchWithTimeout(\n url: string,\n init: RequestInit,\n method: string,\n path: string,\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.requestTimeout);\n try {\n const response = await this.fetchImpl(url, { ...init, signal: controller.signal });\n this.reportResponse(method, path, url, response);\n return response;\n } catch (error) {\n if (isAbortError(error)) {\n throw new GitHubApiError(\n `GitHub API ${method} ${path} timed out after ${this.requestTimeout}ms`,\n {\n method,\n path,\n timeout: true,\n hint: 'Check GitHub API availability or increase requestTimeout.',\n },\n );\n }\n throw error;\n } finally {\n clearTimeout(timer);\n }\n }\n\n private failure(method: string, path: string, response: Response, responseBody: string): never {\n throw new GitHubApiError(\n `GitHub API ${method} ${path} failed: ${response.status} ${response.statusText}`,\n {\n method,\n path,\n status: response.status,\n responseBody,\n hint: 'Check the GitHub API URL, token permissions, repository (owner/repo), and pull request number.',\n },\n );\n }\n\n async request<T>(\n path: string,\n init: RequestInit = {},\n query: Record<string, string | number | boolean | undefined> = {},\n ): Promise<T> {\n const method = init.method ?? 'GET';\n const response = await this.fetchWithTimeout(\n this.url(path, query),\n {\n ...init,\n headers: this.headers({\n ...(init.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...(init.headers as Record<string, string> | undefined),\n }),\n },\n method,\n path,\n );\n\n if (!response.ok) {\n this.failure(method, path, response, await response.text());\n }\n\n if (response.status === 204) return undefined as T;\n const text = await response.text();\n if (!text) return undefined as T;\n return JSON.parse(text) as T;\n }\n\n /** Follow GitHub `Link` `rel=\"next\"` headers, accumulating every page. */\n async paginate<T>(\n path: string,\n query: Record<string, string | number | boolean | undefined> = {},\n ): Promise<T[]> {\n const items: T[] = [];\n let url: string | null = this.url(path, { ...query, per_page: 100 });\n\n while (url) {\n const response = await this.fetchWithTimeout(url, { headers: this.headers() }, 'GET', path);\n\n if (!response.ok) {\n this.failure('GET', path, response, await response.text());\n }\n\n const body = (await response.json()) as unknown;\n if (!Array.isArray(body)) {\n throw new GitHubApiError(`GitHub API GET ${path} returned a non-array paginated response`, {\n method: 'GET',\n path,\n hint: 'The GitHub API response shape was unexpected.',\n });\n }\n items.push(...(body as T[]));\n\n url = parseNextLink(response.headers.get('link'));\n }\n\n return items;\n }\n\n getPullRequest(owner: string, repo: string, pull: number): Promise<PullRequest> {\n return this.request(\n `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${pull}`,\n );\n }\n\n listReviewComments(\n owner: string,\n repo: string,\n pull: number,\n ): Promise<PullRequestReviewComment[]> {\n return this.paginate(\n `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${pull}/comments`,\n );\n }\n\n listIssueComments(owner: string, repo: string, pull: number): Promise<IssueComment[]> {\n return this.paginate(\n `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${pull}/comments`,\n );\n }\n\n createReview(\n owner: string,\n repo: string,\n pull: number,\n payload: CreateReviewInput,\n ): Promise<Review> {\n return this.request(\n `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${pull}/reviews`,\n { method: 'POST', body: JSON.stringify(payload) },\n );\n }\n\n createIssueComment(\n owner: string,\n repo: string,\n pull: number,\n body: string,\n ): Promise<IssueComment> {\n return this.request(\n `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${pull}/comments`,\n { method: 'POST', body: JSON.stringify({ body }) },\n );\n }\n\n updateIssueComment(\n owner: string,\n repo: string,\n commentId: number,\n body: string,\n ): Promise<IssueComment> {\n return this.request(\n `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/comments/${commentId}`,\n { method: 'PATCH', body: JSON.stringify({ body }) },\n );\n }\n\n getCurrentUser(): Promise<GitHubUser> {\n return this.request('/user');\n }\n\n /**\n * Derive the GraphQL endpoint from the REST base. github.com exposes GraphQL\n * at `<origin>/graphql`, while GitHub Enterprise Server exposes it at\n * `<origin>/api/graphql` (its REST base is `<origin>/api/v3`).\n */\n private graphqlEndpoint(): string {\n const suffix = '/api/v3';\n if (this.base.endsWith(suffix)) return `${this.base.slice(0, -suffix.length)}/api/graphql`;\n return `${this.base}/graphql`;\n }\n\n private async graphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {\n const url = this.graphqlEndpoint();\n const response = await this.fetchWithTimeout(\n url,\n {\n method: 'POST',\n headers: this.headers({ 'Content-Type': 'application/json' }),\n body: JSON.stringify({ query, variables }),\n },\n 'POST',\n '/graphql',\n );\n if (!response.ok) this.failure('POST', '/graphql', response, await response.text());\n const text = await response.text();\n const parsed = JSON.parse(text) as { data?: T; errors?: { message?: string }[] };\n if (parsed.errors && parsed.errors.length > 0) {\n throw new GitHubApiError(\n `GitHub API POST /graphql failed: ${parsed.errors.map((e) => e.message ?? '').join('; ')}`,\n {\n method: 'POST',\n path: '/graphql',\n responseBody: text,\n hint: 'Ensure the token can read pull-request review threads (pull-requests: read / repo scope).',\n },\n );\n }\n return parsed.data as T;\n }\n\n /**\n * Return the database IDs of review comments that belong to a **resolved**\n * review thread. GitHub's REST comment endpoints omit thread-resolution state;\n * it is only exposed via GraphQL `reviewThreads.isResolved`. Callers use this\n * set to mark normalized notes resolved so resolved threads are excluded from\n * summary carry-over and prior-thread context. Paginates over threads.\n */\n async listResolvedReviewCommentIds(\n owner: string,\n repo: string,\n pull: number,\n ): Promise<Set<number>> {\n const resolved = new Set<number>();\n let cursor: string | null = null;\n let hasNext = true;\n while (hasNext) {\n const data: ReviewThreadsResponse = await this.graphql(REVIEW_THREADS_QUERY, {\n owner,\n repo,\n pull,\n cursor,\n });\n const threads = data.repository?.pullRequest?.reviewThreads;\n if (!threads) break;\n for (const thread of threads.nodes ?? []) {\n if (!thread.isResolved) continue;\n for (const comment of thread.comments?.nodes ?? []) {\n if (typeof comment.databaseId === 'number') resolved.add(comment.databaseId);\n }\n }\n hasNext = threads.pageInfo?.hasNextPage ?? false;\n cursor = threads.pageInfo?.endCursor ?? null;\n if (!cursor) hasNext = false;\n }\n return resolved;\n }\n}\n","import { createHash } from 'node:crypto';\nimport type { Discussion } from './gitlab.js';\nimport type { Fingerprints, ReviewComment, Side } from './types.js';\n\n/**\n * Pattern source for the hidden fingerprint marker, capturing the hash group.\n * Shared with the parser and prior-thread detection so the marker format is\n * defined in one place; each call site builds its own RegExp with the flags it\n * needs (the capture group is harmless when only stripping or testing). This is\n * a stable wire contract — see CLAUDE.md before changing it.\n *\n * Reads match BOTH the current `code-review:` prefix and the legacy\n * `gitlab-review:` prefix so findings posted under the old product identity are\n * still recognised and deduplicated after the rename. Writes emit the current\n * prefix (see {@link appendFingerprintMarkers}).\n */\nexport const FINGERPRINT_MARKER_PATTERN = String.raw`<!--\\s*(?:code-review|gitlab-review):fingerprint-(?:primary|secondary):([a-f0-9]+)\\s*-->`;\n\nconst FINGERPRINT_MARKER_RE = new RegExp(FINGERPRINT_MARKER_PATTERN, 'gi');\n\nexport function sha256(input: string): string {\n return createHash('sha256').update(input).digest('hex');\n}\n\nexport function normalizeBody(body: string): string {\n return body.replace(FINGERPRINT_MARKER_RE, '').replace(/\\s+/g, ' ').trim();\n}\n\ninterface FileState {\n oldPath: string;\n newPath: string;\n}\n\nfunction matchesFile(state: FileState, file: string): boolean {\n return state.oldPath === file || state.newPath === file;\n}\n\nfunction parseHunkHeader(line: string): { oldLine: number; newLine: number } | null {\n const match = line.match(/^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/);\n if (!match) return null;\n return { oldLine: Number(match[1]), newLine: Number(match[2]) };\n}\n\nfunction hunkContainsLine(\n hunkLines: string[],\n targetLine: number,\n side: Side,\n startOld: number,\n startNew: number,\n): boolean {\n let oldLine = startOld;\n let newLine = startNew;\n\n for (const text of hunkLines.slice(1)) {\n const prefix = text[0] ?? ' ';\n if (side === 'RIGHT' && prefix !== '-' && newLine === targetLine) return true;\n if (side === 'LEFT' && prefix !== '+' && oldLine === targetLine) return true;\n if (prefix !== '+') oldLine += 1;\n if (prefix !== '-') newLine += 1;\n }\n\n return false;\n}\n\nexport function extractDiffHunkContext(\n diff: string,\n file: string,\n line: number,\n side: Side,\n): string {\n const lines = diff.split('\\n');\n const state: FileState = { oldPath: '', newPath: '' };\n\n for (let i = 0; i < lines.length; i += 1) {\n const text = lines[i];\n if (text.startsWith('diff --git ')) {\n state.oldPath = '';\n state.newPath = '';\n continue;\n }\n\n const oldMatch = text.match(/^--- (?:a\\/(.*)|\\/dev\\/null)$/);\n if (oldMatch) state.oldPath = oldMatch[1] ?? '/dev/null';\n const newMatch = text.match(/^\\+\\+\\+ (?:b\\/(.*)|\\/dev\\/null)$/);\n if (newMatch) state.newPath = newMatch[1] ?? '/dev/null';\n\n if (!text.startsWith('@@') || !matchesFile(state, file)) continue;\n const header = parseHunkHeader(text);\n if (!header) continue;\n\n let end = i + 1;\n while (\n end < lines.length &&\n !lines[end].startsWith('@@') &&\n !lines[end].startsWith('diff --git ')\n ) {\n end += 1;\n }\n\n const hunkLines = lines.slice(i, end);\n if (hunkContainsLine(hunkLines, line, side, header.oldLine, header.newLine)) {\n return hunkLines.join('\\n');\n }\n }\n\n return `${file}:${side}:${line}`;\n}\n\nexport function fingerprints(comment: ReviewComment, hunkContext: string): Fingerprints {\n const bodyHash = sha256(normalizeBody(comment.body));\n const hunkHash = sha256(hunkContext);\n return {\n // Primary: exact match — same file/side/line, same body, same surrounding\n // hunk. It shifts when the author edits the hunk, which is why the secondary\n // exists as the edit-stable fallback.\n primary: sha256([comment.file, comment.side, comment.line, bodyHash, hunkHash].join('|')),\n // Secondary: edit-stable. Deliberately excludes both the line number and the\n // hunk context, so the same finding on the same file/side stays deduplicated\n // when the author edits nearby lines (which grow/shift the hunk). Folding the\n // hunk in here defeated that fallback and re-posted findings on every nearby\n // edit (#91).\n secondary: sha256([comment.file, comment.side, bodyHash].join('|')),\n };\n}\n\nexport function appendFingerprintMarkers(body: string, fp: Fingerprints): string {\n return `${body.trim()}\\n\\n<!-- code-review:fingerprint-primary:${fp.primary} -->\\n<!-- code-review:fingerprint-secondary:${fp.secondary} -->`;\n}\n\nexport function extractExistingFingerprints(discussions: Discussion[]): Set<string> {\n const set = new Set<string>();\n for (const discussion of discussions) {\n for (const note of discussion.notes ?? []) {\n for (const match of String(note.body ?? '').matchAll(FINGERPRINT_MARKER_RE)) {\n set.add(match[1]);\n }\n }\n }\n return set;\n}\n","/**\n * The published package name, shown in review footers. Independent of the review\n * platform: the same tool posts to GitLab and GitHub, so the footer identifies\n * the tool, not the backend. Kept as a single source of truth so the inline and\n * summary footers can never drift apart.\n */\nexport const PRODUCT_NAME = '@weareikko/code-review';\n\n/** Canonical project URL used in the footer's markdown link. */\nexport const PRODUCT_URL = 'https://github.com/weareikko/code-review';\n\n/**\n * The `[name](url)` markdown link used verbatim in both the inline comment footer\n * ({@link buildCommentBody}) and the reviewed-commit summary footer\n * ({@link buildReviewedCommitFooter}). Changing this changes the reviewed-commit\n * footer format, which is guarded by a migration test.\n */\nexport const PRODUCT_LINK = `[${PRODUCT_NAME}](${PRODUCT_URL})`;\n","import { extractExistingFingerprints } from './fingerprints.js';\nimport type { Discussion, GitLabClient } from './gitlab.js';\nimport { PRODUCT_LINK } from './product.js';\nimport type { Fingerprints, GeneratedComment, SizeSkippedFile } from './types.js';\n\nexport const SUMMARY_MARKER = '<!-- code-review:summary -->';\nexport const SUMMARY_HISTORY_START = '<!-- code-review:summary-history:start -->';\nexport const SUMMARY_HISTORY_END = '<!-- code-review:summary-history:end -->';\nexport const SUMMARY_HISTORY_ENTRY_START = '<!-- code-review:summary-history-entry:start -->';\nexport const SUMMARY_HISTORY_ENTRY_END = '<!-- code-review:summary-history-entry:end -->';\nexport const SUMMARY_HISTORY_LIMIT = 10;\n\n/**\n * Legacy marker set emitted by the tool under its former `gitlab-review`\n * identity. Writers only ever emit the current `code-review` markers above, but\n * readers must still match these so the first post-rename run finds (and\n * upserts, not duplicates) a summary note it wrote under the old identity.\n */\nconst LEGACY_SUMMARY_MARKER = '<!-- gitlab-review:summary -->';\nconst LEGACY_SUMMARY_HISTORY_START = '<!-- gitlab-review:summary-history:start -->';\nconst LEGACY_SUMMARY_HISTORY_END = '<!-- gitlab-review:summary-history:end -->';\n\n/**\n * Matches the reviewed-commit footer on read. Both the scope and the repo URL\n * host path accept the current `weareikko` org and the legacy `ikko-dev` org,\n * and both the name segment and the repo name accept the current `code-review`\n * and the legacy `gitlab-review`. The GitHub repo was renamed\n * `weareikko/gitlab-review` → `weareikko/code-review`, so writers now emit the\n * `code-review` URL; matching both keeps the reviewed-commit skip working on\n * MRs/PRs already reviewed under any prior identity.\n */\nexport const REVIEWED_COMMIT_FOOTER_PATTERN =\n /Reviewed by \\[@(?:ikko-dev|weareikko)\\/(?:code|gitlab)-review\\]\\(https:\\/\\/github\\.com\\/(?:ikko-dev|weareikko)\\/(?:gitlab-review|code-review)\\)(?: v\\S+)? for commit ([a-f0-9]{40})\\./i;\n\ndeclare const __PKG_VERSION__: string;\n\nexport type SummaryAction = 'created' | 'updated';\n\nexport interface SummaryResult {\n action: SummaryAction;\n noteId?: number;\n}\n\nexport interface SummaryNote {\n id: number;\n body: string;\n}\n\n/**\n * MR-level \"this change is too big\" signal. Surfaced as a prominent callout at\n * the top of the summary so a reviewer cannot miss that part of the change went\n * unreviewed or that the MR is past a human's reviewability threshold.\n */\nexport interface SizeNotice {\n /** Files dropped for exceeding the char budget. */\n sizeSkippedFiles: SizeSkippedFile[];\n /** Set when the reviewed diff's changed-line count crossed the configured threshold. */\n decomposeHint?: { lines: number; threshold: number };\n /** Diff coverage when files were dropped: reviewed vs total changed lines. */\n coverage?: { reviewedLines: number; totalLines: number };\n}\n\nexport interface SummaryBodyOptions {\n historyEntries?: string[];\n reviewedCommitSha?: string;\n skillsFooter?: string;\n runId?: string;\n /** Prominent size/decompose callout rendered above the reviewer's summary. */\n sizeNotice?: SizeNotice;\n}\n\nexport interface UpsertSummaryOptions extends SummaryBodyOptions {\n archivedAt?: Date;\n costFooter?: string;\n}\n\nfunction formatChars(chars: number): string {\n if (chars >= 1000) return `${Math.round(chars / 1000)}k chars`;\n return `${chars} chars`;\n}\n\n/**\n * Render the prominent size/decompose callout that sits directly under the\n * \"### Code Review\" heading. Returns an empty string when there is nothing to\n * surface, so the summary body is byte-for-byte unchanged in the common case.\n */\nexport function buildSizeNoticeBlock(notice?: SizeNotice): string {\n if (!notice) return '';\n const sizeSkippedFiles = notice.sizeSkippedFiles;\n const blocks: string[] = [];\n\n if (sizeSkippedFiles.length > 0) {\n const fileList = sizeSkippedFiles\n .map((file) => `- \\`${file.path}\\` (${formatChars(file.chars)})`)\n .join('\\n');\n const cov = notice.coverage;\n const coverageLine =\n cov && cov.totalLines > 0\n ? `> **Partial review — ~${Math.round((cov.reviewedLines / cov.totalLines) * 100)}% of changed lines reviewed** (${cov.reviewedLines} of ${cov.totalLines}). The files below were NOT reviewed; their absence from the findings is not a clean bill of health.`\n : `> **${sizeSkippedFiles.length} file(s) were not reviewed** — the diff exceeded the size budget, so these files were dropped from the review:`;\n blocks.push(\n [\n `> [!WARNING]`,\n coverageLine,\n `>`,\n ...fileList.split('\\n').map((line) => `> ${line}`),\n `>`,\n `> This MR is past the reviewability threshold. **Split this MR into smaller, atomic changes** so the whole change can be reviewed.`,\n ].join('\\n'),\n );\n }\n\n if (notice.decomposeHint) {\n const { lines, threshold } = notice.decomposeHint;\n blocks.push(\n [\n `> [!NOTE]`,\n `> This MR changes **${lines} lines**, above the configured threshold of **${threshold}**. Consider decomposing this MR into atomic changes — smaller MRs get more thorough review and merge faster.`,\n ].join('\\n'),\n );\n }\n\n return blocks.join('\\n\\n');\n}\n\nexport function buildSummaryBody(\n summary: string,\n costFooter?: string,\n options: SummaryBodyOptions = {},\n): string {\n const sizeNotice = buildSizeNoticeBlock(options.sizeNotice);\n const header = sizeNotice ? `${sizeNotice}\\n\\n${summary.trim()}` : summary.trim();\n const body = `${SUMMARY_MARKER}\\n\\n### Code Review\\n\\n${header}`;\n const footerLines = [\n costFooter?.trim(),\n options.skillsFooter?.trim(),\n options.reviewedCommitSha ? buildReviewedCommitFooter(options.reviewedCommitSha) : undefined,\n options.runId ? `<sub>Run ID: \\`${options.runId}\\`</sub>` : undefined,\n ].filter((line): line is string => Boolean(line));\n const withFooter =\n footerLines.length > 0 ? `${body}\\n\\n---\\n\\n${footerLines.join('\\n\\n')}` : body;\n const historyEntries = options.historyEntries?.filter((entry) => entry.trim().length > 0) ?? [];\n if (historyEntries.length === 0) return withFooter;\n return `${withFooter}\\n\\n${buildSummaryHistoryBlock(historyEntries)}`;\n}\n\nexport function buildReviewedCommitFooter(commitSha: string): string {\n return `Reviewed by ${PRODUCT_LINK} v${__PKG_VERSION__} for commit ${commitSha}.`;\n}\n\nexport function extractReviewedCommitSha(body: string): string | null {\n return REVIEWED_COMMIT_FOOTER_PATTERN.exec(body)?.[1] ?? null;\n}\n\nexport function findExistingReviewedCommitSha(discussions: Discussion[]): string | null {\n const body = findExistingSummaryNote(discussions)?.body;\n return body ? extractReviewedCommitSha(stripSummaryHistory(body)) : null;\n}\n\nexport function findExistingSummaryNote(discussions: Discussion[]): SummaryNote | null {\n for (const discussion of discussions) {\n for (const note of discussion.notes ?? []) {\n const id = note.id;\n if (typeof id !== 'number') continue;\n const body = note.body;\n if (\n typeof body === 'string' &&\n (body.includes(SUMMARY_MARKER) || body.includes(LEGACY_SUMMARY_MARKER))\n ) {\n return { id, body };\n }\n }\n }\n return null;\n}\n\nexport function findExistingSummaryNoteId(discussions: Discussion[]): number | null {\n return findExistingSummaryNote(discussions)?.id ?? null;\n}\n\nexport function buildArchivedSummaryEntry(body: string, archivedAt = new Date()): string {\n const trimmed = body.trim();\n return [\n SUMMARY_HISTORY_ENTRY_START,\n `### Previous run archived ${formatSummaryArchiveDate(archivedAt)}`,\n '',\n trimmed,\n SUMMARY_HISTORY_ENTRY_END,\n ].join('\\n');\n}\n\nexport function extractSummaryHistoryEntries(body: string): string[] {\n const entries: string[] = [];\n // Match both the current `code-review` entry markers and the legacy\n // `gitlab-review` ones so history written under the old identity carries\n // forward; re-emitted entries always use the current markers.\n const entryPattern = new RegExp(\n `${bothPrefixMarkerPattern(SUMMARY_HISTORY_ENTRY_START)}\\\\s*([\\\\s\\\\S]*?)\\\\s*${bothPrefixMarkerPattern(SUMMARY_HISTORY_ENTRY_END)}`,\n 'g',\n );\n for (const match of body.matchAll(entryPattern)) {\n const entry = match[1]?.trim();\n if (entry)\n entries.push(`${SUMMARY_HISTORY_ENTRY_START}\\n${entry}\\n${SUMMARY_HISTORY_ENTRY_END}`);\n }\n return entries;\n}\n\nexport function stripSummaryHistory(body: string): string {\n // Try the current marker pair first, then the legacy pair, so a summary note\n // written under either identity has its history block removed correctly.\n for (const [startMarker, endMarker] of [\n [SUMMARY_HISTORY_START, SUMMARY_HISTORY_END],\n [LEGACY_SUMMARY_HISTORY_START, LEGACY_SUMMARY_HISTORY_END],\n ] as const) {\n const start = body.indexOf(startMarker);\n if (start === -1) continue;\n\n const detailsStart = body.lastIndexOf('<details>', start);\n const blockStart = detailsStart === -1 ? start : detailsStart;\n const endMarkerStart = body.indexOf(endMarker, start);\n const endMarkerEnd =\n endMarkerStart === -1 ? start + startMarker.length : endMarkerStart + endMarker.length;\n const detailsEnd = body.indexOf('</details>', endMarkerEnd);\n const blockEnd = detailsEnd === -1 ? endMarkerEnd : detailsEnd + '</details>'.length;\n\n return `${body.slice(0, blockStart)}${body.slice(blockEnd)}`.trim();\n }\n return body.trim();\n}\n\nexport function stripSummaryMarker(body: string): string {\n return body.replace(SUMMARY_MARKER, '').replace(LEGACY_SUMMARY_MARKER, '').trim();\n}\n\nexport function buildSummaryHistoryEntries(\n existingBody: string,\n archivedAt = new Date(),\n): string[] {\n const latestPrevious = stripSummaryMarker(stripSummaryHistory(existingBody));\n const previousEntries = extractSummaryHistoryEntries(existingBody);\n const nextEntries = latestPrevious\n ? [buildArchivedSummaryEntry(latestPrevious, archivedAt), ...previousEntries]\n : previousEntries;\n return nextEntries.slice(0, SUMMARY_HISTORY_LIMIT);\n}\n\n/**\n * Resolve the summary upsert into the note body to write plus the existing note\n * to update (or `null` to create). Platform-agnostic — it operates on the\n * normalized {@link Discussion}[] and returns strings/ids — so both the GitLab\n * and GitHub platforms share the exact same body-building and history-carryover\n * logic and only differ in which API call posts the result.\n */\nexport function buildUpsertSummary(\n summary: string,\n discussions: Discussion[],\n options: UpsertSummaryOptions,\n): { body: string; existing: SummaryNote | null } {\n const existing = findExistingSummaryNote(discussions);\n const historyEntries = existing\n ? buildSummaryHistoryEntries(existing.body, options.archivedAt)\n : (options.historyEntries ?? []);\n const body = buildSummaryBody(summary, options.costFooter, {\n historyEntries,\n reviewedCommitSha: options.reviewedCommitSha,\n skillsFooter: options.skillsFooter,\n sizeNotice: options.sizeNotice,\n });\n return { body, existing };\n}\n\nexport async function upsertSummaryNote(\n gitlab: GitLabClient,\n project: string,\n mr: string,\n summary: string,\n discussions: Discussion[],\n costFooterOrOptions?: string | UpsertSummaryOptions,\n): Promise<SummaryResult> {\n const options =\n typeof costFooterOrOptions === 'string'\n ? { costFooter: costFooterOrOptions }\n : (costFooterOrOptions ?? {});\n const { body, existing } = buildUpsertSummary(summary, discussions, options);\n if (existing) {\n await gitlab.updateMergeRequestNote(project, mr, existing.id, body);\n return { action: 'updated', noteId: existing.id };\n }\n const created = await gitlab.createMergeRequestNote(project, mr, body);\n return { action: 'created', noteId: created.id };\n}\n\nfunction buildSummaryHistoryBlock(entries: string[]): string {\n return [\n '<details>',\n '<summary>Previous review runs</summary>',\n '',\n SUMMARY_HISTORY_START,\n '',\n entries.join('\\n\\n'),\n '',\n SUMMARY_HISTORY_END,\n '',\n '</details>',\n ].join('\\n');\n}\n\nfunction formatSummaryArchiveDate(date: Date): string {\n return date.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Turn a current (`code-review`) marker string into a regex source that also\n * matches its legacy (`gitlab-review`) equivalent, so readers accept both\n * product identities. The two markers differ only by that prefix.\n */\nfunction bothPrefixMarkerPattern(marker: string): string {\n return escapeRegExp(marker).replace('code-review', '(?:code-review|gitlab-review)');\n}\n\nexport type PostingMode = 'direct' | 'draft';\n\nexport const POSTING_MODES: readonly PostingMode[] = ['direct', 'draft'];\n\nexport const DRAFT_CONCURRENCY = 10;\n\nexport interface PostResult {\n posted: number;\n drafts?: {\n abandoned: number;\n created: number;\n deletedPrePublish: number;\n published: number;\n /**\n * Drafts that survived the pre-publish race check but could not be\n * published, counted only on the per-draft fallback path (a `bulk_publish`\n * 500 forced individual publishes and some still failed). 0 on the normal\n * path. A non-zero value means inline comments were silently dropped.\n */\n publishFailed: number;\n };\n}\n\nexport async function postGeneratedComments(\n gitlab: GitLabClient,\n project: string,\n mr: string,\n generated: GeneratedComment[],\n mode: PostingMode = 'direct',\n): Promise<PostResult> {\n const fresh = generated.filter((item) => !item.duplicate);\n if (mode === 'draft') return postViaDrafts(gitlab, project, mr, fresh);\n if (fresh.length === 0) return { posted: 0 };\n return postDirectly(gitlab, project, mr, fresh);\n}\n\nasync function postDirectly(\n gitlab: GitLabClient,\n project: string,\n mr: string,\n fresh: GeneratedComment[],\n): Promise<PostResult> {\n let posted = 0;\n for (const item of fresh) {\n await gitlab.postDiscussion(project, mr, item.payload);\n posted += 1;\n }\n return { posted };\n}\n\ninterface DraftRecord {\n id: number;\n fingerprints: Fingerprints;\n}\n\nasync function postViaDrafts(\n gitlab: GitLabClient,\n project: string,\n mr: string,\n fresh: GeneratedComment[],\n): Promise<PostResult> {\n const abandoned = await cleanupOrphanDrafts(gitlab, project, mr);\n\n if (fresh.length === 0) {\n return {\n posted: 0,\n drafts: { abandoned, created: 0, deletedPrePublish: 0, published: 0, publishFailed: 0 },\n };\n }\n\n let drafts: DraftRecord[];\n try {\n drafts = await createDraftsConcurrently(gitlab, project, mr, fresh);\n } catch (error) {\n // A draft creation failed mid-flight. Some siblings may have succeeded\n // and now sit as unpublished drafts on the MR — sweep them before\n // re-throwing so the failure does not leak partial state.\n await cleanupOrphanDrafts(gitlab, project, mr).catch(() => undefined);\n throw error;\n }\n\n const survivors = await deleteRaceLosers(gitlab, project, mr, drafts);\n const deletedPrePublish = drafts.length - survivors.length;\n\n const { published, publishFailed } = await publishDrafts(gitlab, project, mr, survivors);\n\n return {\n posted: published,\n drafts: { abandoned, created: drafts.length, deletedPrePublish, published, publishFailed },\n };\n}\n\n/**\n * Publishes the surviving drafts. `bulk_publish` is one atomic batch server-side\n * — a single draft with an unresolvable diff position (e.g. a one-sided context\n * line, gitlab-org/gitlab#579609) makes it 500 and nothing publishes. When that\n * happens we fall back to publishing each draft individually, which GitLab\n * isolates per draft, so one bad draft can no longer sink the whole set (and\n * fail the CI job). The original error is re-thrown only when every individual\n * publish also fails, so a genuinely broken run still surfaces loudly.\n */\nasync function publishDrafts(\n gitlab: GitLabClient,\n project: string,\n mr: string,\n drafts: DraftRecord[],\n): Promise<{ published: number; publishFailed: number }> {\n if (drafts.length === 0) return { published: 0, publishFailed: 0 };\n try {\n await gitlab.bulkPublishDraftNotes(project, mr);\n return { published: drafts.length, publishFailed: 0 };\n } catch (bulkError) {\n const results = await Promise.allSettled(\n drafts.map((draft) => gitlab.publishDraftNote(project, mr, draft.id)),\n );\n const published = results.filter((result) => result.status === 'fulfilled').length;\n if (published === 0) throw bulkError;\n return { published, publishFailed: drafts.length - published };\n }\n}\n\nasync function cleanupOrphanDrafts(\n gitlab: GitLabClient,\n project: string,\n mr: string,\n): Promise<number> {\n const me = await gitlab.getCurrentUser();\n const drafts = await gitlab.listDraftNotes(project, mr);\n const mine = drafts.filter((draft) => draft.author_id === me.id);\n if (mine.length === 0) return 0;\n await Promise.all(mine.map((draft) => gitlab.deleteDraftNote(project, mr, draft.id)));\n return mine.length;\n}\n\nasync function createDraftsConcurrently(\n gitlab: GitLabClient,\n project: string,\n mr: string,\n fresh: GeneratedComment[],\n): Promise<DraftRecord[]> {\n const records: DraftRecord[] = Array.from({ length: fresh.length });\n let next = 0;\n\n async function worker(): Promise<void> {\n while (true) {\n const index = next;\n next += 1;\n if (index >= fresh.length) return;\n const item = fresh[index];\n const draft = await gitlab.createDraftNote(project, mr, item.payload);\n records[index] = { id: draft.id, fingerprints: item.fingerprints };\n }\n }\n\n const workerCount = Math.min(DRAFT_CONCURRENCY, fresh.length);\n // allSettled (not all) so siblings finish their POSTs before we report\n // failure — that way the caller's cleanup can see every draft GitLab has\n // accepted, not just the ones that beat the rejection.\n const results = await Promise.allSettled(Array.from({ length: workerCount }, () => worker()));\n const failure = results.find((r): r is PromiseRejectedResult => r.status === 'rejected');\n if (failure) throw failure.reason;\n return records;\n}\n\n/**\n * Deletes drafts whose fingerprints now collide with already-published\n * discussions (a concurrent run won the race) and returns the surviving drafts\n * still safe to publish.\n */\nasync function deleteRaceLosers(\n gitlab: GitLabClient,\n project: string,\n mr: string,\n drafts: DraftRecord[],\n): Promise<DraftRecord[]> {\n const live = extractExistingFingerprints(await gitlab.getDiscussions(project, mr));\n const colliding: DraftRecord[] = [];\n const survivors: DraftRecord[] = [];\n for (const draft of drafts) {\n if (live.has(draft.fingerprints.primary) || live.has(draft.fingerprints.secondary)) {\n colliding.push(draft);\n } else {\n survivors.push(draft);\n }\n }\n if (colliding.length > 0) {\n await Promise.all(colliding.map((draft) => gitlab.deleteDraftNote(project, mr, draft.id)));\n }\n return survivors;\n}\n","export type Severity = 'info' | 'warn' | 'critical';\nexport type GitLabReviewSeverity = 'INFO' | 'WARN' | 'CRITICAL';\nexport type Confidence = 'high' | 'medium' | 'low';\nexport type Side = 'RIGHT' | 'LEFT';\nexport type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';\n\n/**\n * How many stages of the review pipeline run.\n * - `single`: one Find pass; the model's output is used verbatim (legacy behaviour).\n * - `verify`: Find → Verify → Synthesize; each severe finding is re-checked by a\n * separate adversarial agent before it survives into the posted review.\n * - `full`: multi-angle Find (several finders, each a different lens) → Triage\n * (dedup) → Verify → Synthesize.\n */\nexport type ReviewDepth = 'single' | 'verify' | 'full';\n\nexport const REVIEW_DEPTHS: readonly ReviewDepth[] = ['single', 'verify', 'full'];\n\nexport const THINKING_LEVELS: readonly ThinkingLevel[] = [\n 'off',\n 'minimal',\n 'low',\n 'medium',\n 'high',\n 'xhigh',\n];\n\nexport interface ReviewComment {\n file: string;\n line: number;\n side: Side;\n severity: Severity;\n /**\n * The reviewer's certainty that the finding is a real defect, separate from\n * its impact (encoded in `severity`). Defaults to 'high' when absent so\n * legacy reviewer outputs continue to parse unchanged.\n */\n confidence: Confidence;\n body: string;\n}\n\n/**\n * A file dropped from the reviewed diff because the cumulative diff exceeded the\n * char budget (distinct from quiet noise skips like lockfiles). `chars` is the\n * size of that file's diff section.\n */\nexport interface SizeSkippedFile {\n path: string;\n chars: number;\n /** Added/removed lines in this file's dropped diff — feeds the coverage ratio. */\n changedLines: number;\n}\n\nexport interface DiffRefs {\n base_sha: string;\n start_sha: string;\n head_sha: string;\n}\n\nexport interface Fingerprints {\n primary: string;\n secondary: string;\n}\n\n/**\n * A parsed reviewer finding paired with its dedup fingerprints and the\n * platform-specific posting payload. `payload` is generic (defaulting to\n * `unknown`) so the seam stays platform-agnostic: only the platform that built\n * a payload reads it back. GitLab builds a {@link GitLabDiscussionPayload}.\n */\nexport interface GeneratedComment<Payload = unknown> {\n comment: ReviewComment;\n fingerprints: Fingerprints;\n duplicate: boolean;\n payload: Payload;\n}\n\nexport interface GitLabDiscussionPayload {\n body: string;\n position: {\n position_type: 'text';\n base_sha: string;\n start_sha: string;\n head_sha: string;\n old_path: string;\n new_path: string;\n old_line?: number;\n new_line?: number;\n };\n}\n\nexport function toGitLabReviewSeverity(severity: Severity): GitLabReviewSeverity {\n return severity === 'critical' ? 'CRITICAL' : severity === 'warn' ? 'WARN' : 'INFO';\n}\n\nexport function normalizeSeverity(value: unknown): Severity {\n const normalized = String(value ?? '')\n .trim()\n .toLowerCase();\n if (normalized === 'critical' || normalized === 'error') return 'critical';\n if (normalized === 'warn' || normalized === 'warning') return 'warn';\n return 'info';\n}\n\n/**\n * Normalize a raw confidence value from reviewer JSON into the strict enum.\n * Defaults to 'high' for absent / unrecognised values: a missing field is\n * assumed to come from a pre-confidence reviewer output, and the reviewer\n * historically only emitted findings it considered provable, which maps to\n * high confidence.\n */\nexport function normalizeConfidence(value: unknown): Confidence {\n const normalized = String(value ?? '')\n .trim()\n .toLowerCase();\n if (normalized === 'low') return 'low';\n if (normalized === 'medium' || normalized === 'med') return 'medium';\n return 'high';\n}\n\n/**\n * Split a `\"provider/modelId\"` model string on the FIRST slash. Multi-slash\n * model IDs (e.g. `openrouter/anthropic/claude-3`) keep everything after the\n * first slash as the model ID. When the string has no slash, `provider` is\n * `undefined` and `modelId` is the whole string (or `undefined` when empty).\n */\nexport function splitModel(model: string): {\n provider: string | undefined;\n modelId: string | undefined;\n} {\n const idx = model.indexOf('/');\n if (idx < 0) return { provider: undefined, modelId: model || undefined };\n return { provider: model.slice(0, idx), modelId: model.slice(idx + 1) };\n}\n","import { readFileSync } from 'node:fs';\nimport { getEnvApiKey } from '@earendil-works/pi-ai';\nimport { ConfigError } from './errors.js';\nimport { DEFAULT_GITHUB_API_URL } from './github.js';\nimport { POSTING_MODES, type PostingMode } from './posting.js';\nimport {\n REVIEW_DEPTHS,\n splitModel,\n THINKING_LEVELS,\n type ReviewDepth,\n type Severity,\n type ThinkingLevel,\n} from './types.js';\n\nexport type GitLabAuthHeader = 'PRIVATE-TOKEN' | 'JOB-TOKEN';\n\n/** Source-control backends the reviewer can target. */\nexport const PLATFORMS = ['gitlab', 'github'] as const;\nexport type Platform = (typeof PLATFORMS)[number];\n\n/** Default GitHub server (web) URL, honoring `GITHUB_SERVER_URL` on Enterprise. */\nexport const DEFAULT_GITHUB_SERVER_URL = 'https://github.com';\n\n/**\n * The single source of truth for the tool's own `CODE_REVIEW_*` settings.\n *\n * Each entry is the suffix after the `CODE_REVIEW_` prefix (e.g. `MODEL` for\n * `CODE_REVIEW_MODEL`). These are read directly across `config.ts`/`otel.ts`\n * and must NEVER be de-prefixed by {@link applyCodeReviewEnvPrefix}.\n *\n * Note: `API_KEY` is reserved. `CODE_REVIEW_API_KEY` was intentionally retired\n * as the AI provider key and must not be revived as one.\n */\nexport const RESERVED_ENV_SUFFIXES = [\n 'API_KEY',\n 'BASE_URL',\n 'DECOMPOSE_HINT_LINES',\n 'MAX_DIFF_CHARS',\n 'MAX_TOKENS',\n 'MIN_SEVERITY',\n 'MODEL',\n 'MODEL_POOL',\n 'PLATFORM',\n 'OTEL',\n 'OTEL_CAPTURE_CONTENT',\n 'POSTING_MODE',\n 'POST_SUMMARY',\n 'FORCE_REVIEW',\n 'VERBOSE',\n 'SKILLS',\n 'REFRESH_SKILLS',\n 'THINKING_LEVEL',\n] as const;\n\nconst CODE_REVIEW_PREFIX = 'CODE_REVIEW_';\nconst RESERVED_ENV_SUFFIX_SET = new Set<string>(RESERVED_ENV_SUFFIXES);\n\n/**\n * Optional namespacing shim for provider/infra environment variables.\n *\n * For each `CODE_REVIEW_<NAME>` variable whose `<NAME>` is not a reserved tool\n * setting (see {@link RESERVED_ENV_SUFFIXES}), this exposes `<NAME>` in the same\n * env object. The prefixed value wins when both `CODE_REVIEW_<NAME>` and a\n * plain `<NAME>` are set — the tool's scoped value should override an unrelated\n * CI-wide variable of the same name.\n *\n * This lets credentials and infra vars that `@earendil-works/pi-ai` reads\n * (`ANTHROPIC_API_KEY`, `CLOUDFLARE_API_KEY`, `CLOUDFLARE_ACCOUNT_ID`,\n * `OLLAMA_HOST`, ambient AWS/Vertex creds, …) — and the GitLab tokens — be\n * scoped under `CODE_REVIEW_` in shared CI without enumerating pi-ai's\n * provider list.\n *\n * Must run once at startup BEFORE config/key resolution: `getEnvApiKey` and\n * pi-ai's request-time reads both read `process.env` directly, so mutating it\n * in-process is what makes those reads pick up the de-prefixed values.\n *\n * Empty prefixed values are ignored (treated as unset). Double-prefixed names\n * (e.g. `CODE_REVIEW_CODE_REVIEW_MODEL`) are also skipped so a de-prefixed\n * suffix can never clobber the tool's own reserved `CODE_REVIEW_*` settings.\n *\n * @returns the same `env` object it was given, mutated in place.\n */\nexport function applyCodeReviewEnvPrefix(env = process.env): NodeJS.ProcessEnv {\n for (const key of Object.keys(env)) {\n if (!key.startsWith(CODE_REVIEW_PREFIX)) continue;\n const suffix = key.slice(CODE_REVIEW_PREFIX.length);\n if (!suffix || RESERVED_ENV_SUFFIX_SET.has(suffix) || suffix.startsWith(CODE_REVIEW_PREFIX))\n continue;\n const value = env[key];\n if (typeof value !== 'string' || value.length === 0) continue;\n env[suffix] = value;\n }\n return env;\n}\n\n/**\n * Default pi-ai's prompt-cache retention to `long` when the caller has not set\n * it. pi-ai reads `PI_CACHE_RETENTION` from `process.env` at request time; `long`\n * asks providers that support it (e.g. OpenAI's `openai-responses` API, including\n * via the Cloudflare AI Gateway) to keep the cached system-prompt prefix for up\n * to 24h so reviews spaced hours apart still reuse it. It is a safe no-op for\n * providers/models without long-retention support (e.g. Anthropic), where it\n * behaves exactly like the default `short`.\n *\n * Overridable: an explicit `PI_CACHE_RETENTION` (or `CODE_REVIEW_PI_CACHE_RETENTION`,\n * mapped by {@link applyCodeReviewEnvPrefix} first) is left untouched.\n */\nexport function applyDefaultCacheRetention(env = process.env): NodeJS.ProcessEnv {\n if (!env.PI_CACHE_RETENTION) {\n env.PI_CACHE_RETENTION = 'long';\n }\n return env;\n}\n\nexport interface Config {\n /**\n * The source-control backend to review against. Auto-detected from the\n * environment by default (see {@link detectPlatform}); `--platform` /\n * `CODE_REVIEW_PLATFORM` is an explicit override that always wins.\n */\n platform: Platform;\n project: string;\n mr: string;\n gitlabUrl: string;\n gitlabToken: string;\n gitlabAuthHeader: GitLabAuthHeader;\n /** `owner/repo` slug of the GitHub repository (`GITHUB_REPOSITORY`). */\n githubRepository: string;\n /** Pull-request number as a string, mirroring {@link Config.mr}. */\n githubPr: string;\n /** Token used for the GitHub REST API (`GITHUB_TOKEN` / `--github-token`). */\n githubToken: string;\n /** GitHub REST API base; defaults to {@link DEFAULT_GITHUB_API_URL}. */\n githubApiUrl: string;\n /** GitHub server (web) URL; defaults to {@link DEFAULT_GITHUB_SERVER_URL}. */\n githubServerUrl: string;\n model: string;\n /**\n * Optional pool of `provider/modelId` models for heterogeneous `full`-depth\n * review. When non-empty, multi-angle Find maps each angle to a pool member and\n * the adversarial verifier prefers a member other than the one that authored a\n * finding. Empty (the default) means the effective pool is just `[model]`, which\n * reproduces single-model behaviour byte-for-byte. Sourced from `--model-pool`\n * or `CODE_REVIEW_MODEL_POOL` (comma-separated).\n */\n modelPool: string[];\n minSeverity: Severity;\n thinkingLevel: ThinkingLevel;\n /**\n * How many stages of the review pipeline run. `single` keeps the legacy\n * single-pass behaviour; `verify` adds an adversarial Verify + Synthesize pass.\n */\n reviewDepth: ReviewDepth;\n /**\n * Optional `provider/modelId` for the Verify stage (`verify`/`full` depth). When\n * set, every adversarial verifier runs on this model instead of the pool's\n * cross-family pick — pairing a cheap, high-recall Find model with a strong,\n * high-precision verifier. Empty (default) keeps the pool-based selection.\n * Sourced from `--verify-model` or `CODE_REVIEW_VERIFY_MODEL`.\n */\n verifyModel: string;\n postingMode: PostingMode;\n apiKey: string;\n /** Custom base URL for the AI provider API (e.g. Ollama or other OpenAI-compatible endpoints). */\n baseUrl: string;\n /** Maximum output tokens to request from the model. 0 uses the model's default. */\n maxTokens: number;\n /**\n * Maximum cumulative diff characters sent to the reviewer. Files past this\n * budget are dropped and surfaced as a size-skip callout. Defaults to 100_000.\n */\n maxDiffChars: number;\n /**\n * When > 0, an MR whose reviewed diff changes more lines than this threshold\n * gets a \"consider decomposing this MR\" hint in the summary. 0 = off (default).\n */\n decomposeHintLines: number;\n /**\n * Lines of surrounding context per diff hunk (`git diff --unified`). More\n * context helps the model reason about each change but inflates tokens and\n * fits fewer files in the char budget; less context fits more files. 0 uses\n * the built-in default. Sourced from `--diff-context` / `CODE_REVIEW_DIFF_CONTEXT`.\n */\n diffContext: number;\n /**\n * When true, diffs for files dropped by the char budget are staged on disk so\n * the reviewer can read them on demand instead of losing them (retrieval mode).\n * Default false. Sourced from `--retrieve-skipped` / `CODE_REVIEW_RETRIEVE_SKIPPED`.\n */\n retrieveSkipped: boolean;\n reviewFile: string;\n output: string;\n dryRun: boolean;\n noPost: boolean;\n postSummary: boolean;\n forceReview: boolean;\n verbose: boolean;\n cwd: string;\n skills: string[];\n /** Re-clone `git:` / `git+ssh:` skills, bypassing the on-disk clone cache. */\n refreshGitSkills: boolean;\n}\n\nexport type ParsedArgs = Record<string, string | boolean | string[]>;\n\nconst BOOLEAN_FLAGS = new Set([\n 'dry-run',\n 'no-post',\n 'no-summary',\n 'force-review',\n 'retrieve-skipped',\n 'verbose',\n 'help',\n 'version',\n]);\n\nconst MULTI_FLAGS = new Set(['skill']);\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const args: ParsedArgs = {};\n\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === '-h') {\n args.help = true;\n continue;\n }\n if (arg === '-v') {\n args.version = true;\n continue;\n }\n if (!arg.startsWith('--')) continue;\n\n const [rawKey, inlineValue] = arg.slice(2).split('=', 2);\n if (!rawKey) continue;\n const key = rawKey.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n\n let value: string | boolean;\n if (inlineValue !== undefined) {\n value = inlineValue;\n } else if (BOOLEAN_FLAGS.has(rawKey)) {\n value = true;\n } else {\n const next = argv[i + 1];\n if (!next || next.startsWith('--')) {\n throw new ConfigError(`Missing value for --${rawKey}`, {\n hint: `Pass a value after --${rawKey} or use --${rawKey}=<value>.`,\n });\n }\n value = next;\n i += 1;\n }\n\n if (MULTI_FLAGS.has(rawKey)) {\n const existing = args[key];\n args[key] = Array.isArray(existing) ? [...existing, value as string] : [value as string];\n } else {\n args[key] = value;\n }\n }\n\n return args;\n}\n\nfunction first(...values: Array<string | undefined>): string | undefined {\n return values.find((value) => typeof value === 'string' && value.length > 0);\n}\n\nfunction toBoolean(value: unknown): boolean {\n return value === true || value === 'true' || value === '1';\n}\n\nfunction resolvePostSummary(args: ParsedArgs, env: NodeJS.ProcessEnv): boolean {\n if (args.noSummary === true) return false;\n const raw = env.CODE_REVIEW_POST_SUMMARY;\n if (typeof raw === 'string') {\n const normalized = raw.trim().toLowerCase();\n if (['0', 'false', 'no', 'off'].includes(normalized)) return false;\n if (normalized.length > 0) return true;\n }\n return true;\n}\n\nfunction normalizeChoice(value: unknown): string {\n return String(value ?? '')\n .trim()\n .toLowerCase();\n}\n\n/**\n * Extract the provider name from a `\"provider/modelId\"` model string.\n * Returns an empty string when the model string contains no slash.\n */\nexport function parseModelProvider(model: string): string {\n return splitModel(model).provider ?? '';\n}\n\n/**\n * Resolve the base URL for the Ollama provider from the `OLLAMA_HOST` env var.\n * Returns `undefined` for non-Ollama models.\n *\n * The Ollama OpenAI-compatible endpoint lives at `<host>/v1`.\n */\nfunction resolveOllamaBaseUrl(model: string, env: NodeJS.ProcessEnv): string | undefined {\n if (parseModelProvider(model) !== 'ollama') return undefined;\n const host = env.OLLAMA_HOST ?? 'http://localhost:11434';\n return `${host.replace(/\\/$/, '')}/v1`;\n}\n\n/**\n * Resolve the API key for the given model's provider, delegating entirely to\n * `@earendil-works/pi-ai`'s `getEnvApiKey`. That helper reads the provider's\n * standard environment variable (e.g. `ANTHROPIC_API_KEY` / `ANTHROPIC_OAUTH_TOKEN`,\n * `OPENAI_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`, …) and resolves\n * ambient credentials (Amazon Bedrock, Google Vertex ADC). The key is therefore\n * always provider-specific — a key for provider X is never used for provider Y.\n *\n * Ollama is a local OpenAI-compatible endpoint that needs no key, so a\n * placeholder is returned. The `--api-key` flag takes precedence in\n * `resolveConfig`.\n *\n * NOTE: `getEnvApiKey` reads `process.env` directly (not a passed-in env), so\n * tests stub `process.env` or pass `--api-key` to control the resolved key.\n */\nexport function resolveProviderApiKey(model: string): string {\n const provider = parseModelProvider(model);\n if (!provider) return '';\n // Ollama is a local endpoint — no real key needed.\n if (provider === 'ollama') return 'ollama';\n return getEnvApiKey(provider) ?? '';\n}\n\nfunction resolveSkills(args: ParsedArgs, env: NodeJS.ProcessEnv): string[] {\n const argSkill = args.skill;\n if (Array.isArray(argSkill) && argSkill.length > 0) return argSkill;\n if (typeof argSkill === 'string' && argSkill.length > 0) return [argSkill];\n const envVal = env.CODE_REVIEW_SKILLS;\n if (envVal)\n return envVal\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n return [];\n}\n\n/**\n * Resolve the model pool from `--model-pool` (preferred) or\n * `CODE_REVIEW_MODEL_POOL`. Both are comma-separated `provider/modelId` lists.\n * Entries are trimmed and empty entries dropped. Returns `[]` when unset, which\n * downstream treats as \"use the single `config.model`\".\n */\nfunction resolveModelPool(args: ParsedArgs, env: NodeJS.ProcessEnv): string[] {\n const raw =\n (typeof args.modelPool === 'string' && args.modelPool.length > 0\n ? args.modelPool\n : env.CODE_REVIEW_MODEL_POOL) ?? '';\n return raw\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n\nfunction resolveGitLabToken(\n args: ParsedArgs,\n env: NodeJS.ProcessEnv,\n): { token: string; header: GitLabAuthHeader } {\n if (typeof args.gitlabToken === 'string' && args.gitlabToken.length > 0) {\n return { token: args.gitlabToken, header: 'PRIVATE-TOKEN' };\n }\n\n if (env.GITLAB_TOKEN) return { token: env.GITLAB_TOKEN, header: 'PRIVATE-TOKEN' };\n if (env.GLAB_CLI_TOKEN) return { token: env.GLAB_CLI_TOKEN, header: 'PRIVATE-TOKEN' };\n if (env.CI_JOB_TOKEN) return { token: env.CI_JOB_TOKEN, header: 'JOB-TOKEN' };\n if (env.GITLAB_PRIVATE_TOKEN) return { token: env.GITLAB_PRIVATE_TOKEN, header: 'PRIVATE-TOKEN' };\n\n return { token: '', header: 'PRIVATE-TOKEN' };\n}\n\n/**\n * Extract a pull-request number from a GitHub Actions event payload JSON string\n * (the file `GITHUB_EVENT_PATH` points at). Prefers `.pull_request.number`, then\n * the top-level `.number` (present on `issue_comment` events). Returns `''` when\n * the JSON is unparsable or carries no number.\n */\nexport function parsePrNumberFromEvent(json: string): string {\n try {\n const data = JSON.parse(json) as {\n pull_request?: { number?: unknown } | null;\n number?: unknown;\n };\n const value = data.pull_request?.number ?? data.number;\n if (typeof value === 'number' && Number.isInteger(value)) return String(value);\n if (typeof value === 'string' && /^\\d+$/.test(value.trim())) return value.trim();\n return '';\n } catch {\n return '';\n }\n}\n\n/**\n * Extract a pull-request number from a `GITHUB_REF` of the form\n * `refs/pull/<N>/merge` (or `/head`). Returns `''` for any other ref shape.\n */\nexport function parsePrNumberFromRef(ref: string | undefined): string {\n const match = /^refs\\/pull\\/(\\d+)\\/(?:merge|head)$/.exec(ref ?? '');\n return match ? match[1] : '';\n}\n\nfunction readEventFileSync(path: string): string | undefined {\n try {\n return readFileSync(path, 'utf8');\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolve the GitHub pull-request number, in priority order: `--pr` flag, then\n * the `GITHUB_EVENT_PATH` payload (`.pull_request.number` ?? `.number`), then a\n * `refs/pull/<N>/merge` `GITHUB_REF`. Returns `''` when none apply.\n */\nexport function resolveGitHubPr(\n args: ParsedArgs,\n env: NodeJS.ProcessEnv,\n readEventFile: (path: string) => string | undefined = readEventFileSync,\n): string {\n if (typeof args.pr === 'string' && args.pr.length > 0) return args.pr;\n const eventPath = env.GITHUB_EVENT_PATH;\n if (eventPath) {\n const json = readEventFile(eventPath);\n if (json) {\n const fromEvent = parsePrNumberFromEvent(json);\n if (fromEvent) return fromEvent;\n }\n }\n return parsePrNumberFromRef(env.GITHUB_REF);\n}\n\n/**\n * Determine the review platform for this run.\n *\n * Precedence:\n * 1. Explicit `--platform` / `CODE_REVIEW_PLATFORM` (throws on an unknown value).\n * 2. CI markers: `GITHUB_ACTIONS === 'true'` → github; `GITLAB_CI === 'true'`\n * or a present `CI_PROJECT_ID` / `CI_SERVER_URL` → gitlab.\n * 3. Inference from which platform's required identifiers are present\n * (`GITHUB_REPOSITORY` + a PR number vs. `CI_PROJECT_ID` + `CI_MERGE_REQUEST_IID`).\n *\n * Throws a {@link ConfigError} when both platforms' identifiers are present\n * (ambiguous) or neither is (undetectable), with a hint to set `--platform`.\n */\nexport function detectPlatform(\n args: ParsedArgs,\n env: NodeJS.ProcessEnv,\n readEventFile: (path: string) => string | undefined = readEventFileSync,\n): Platform {\n const explicit = normalizeChoice(args.platform ?? env.CODE_REVIEW_PLATFORM);\n if (explicit) {\n if (explicit === 'github' || explicit === 'gitlab') return explicit;\n throw new ConfigError(`Unknown platform \"${explicit}\".`, {\n hint: `--platform (or CODE_REVIEW_PLATFORM) must be one of: ${PLATFORMS.join(', ')}.`,\n });\n }\n\n if (env.GITHUB_ACTIONS === 'true') return 'github';\n if (env.GITLAB_CI === 'true' || env.CI_PROJECT_ID || env.CI_SERVER_URL) return 'gitlab';\n\n const hasGitHub = Boolean(\n (args.githubRepository ?? env.GITHUB_REPOSITORY) && resolveGitHubPr(args, env, readEventFile),\n );\n const hasGitLab = Boolean(\n (args.project ?? env.CI_PROJECT_ID) && (args.mr ?? env.CI_MERGE_REQUEST_IID),\n );\n if (hasGitHub && !hasGitLab) return 'github';\n if (hasGitLab && !hasGitHub) return 'gitlab';\n\n throw new ConfigError(\n hasGitHub && hasGitLab\n ? 'Ambiguous review platform: both GitHub and GitLab identifiers are present.'\n : 'Could not detect the review platform from the environment.',\n {\n hint: `Set --platform (or CODE_REVIEW_PLATFORM) to one of: ${PLATFORMS.join(', ')}.`,\n },\n );\n}\n\nexport function resolveConfig(argv = process.argv.slice(2), env = process.env): Config {\n const args = parseArgs(argv);\n const platform = detectPlatform(args, env);\n const gitlabUrl = String(\n args.gitlabUrl ??\n first(env.CI_SERVER_URL, env.CI_SERVER_HOST ? `https://${env.CI_SERVER_HOST}` : undefined) ??\n '',\n ).replace(/\\/$/, '');\n const token = resolveGitLabToken(args, env);\n\n const githubApiUrl = String(\n args.githubApiUrl ?? env.GITHUB_API_URL ?? DEFAULT_GITHUB_API_URL,\n ).replace(/\\/$/, '');\n const githubServerUrl = String(\n args.githubServerUrl ?? env.GITHUB_SERVER_URL ?? DEFAULT_GITHUB_SERVER_URL,\n ).replace(/\\/$/, '');\n\n // Model and API key are both required — there is no implicit default model.\n // The model is `provider/modelId`; supply it via --model or CODE_REVIEW_MODEL.\n const model = String(args.model ?? env.CODE_REVIEW_MODEL ?? '');\n\n // API key resolution:\n // 1. --api-key flag (explicit override)\n // 2. The model provider's standard env var / ambient credentials, via\n // pi-ai's getEnvApiKey — resolved provider-specifically so a key for one\n // provider is never sent to another. Ollama uses a placeholder.\n const apiKey = String(args.apiKey ?? resolveProviderApiKey(model) ?? '');\n\n // Base URL resolution priority:\n // 1. --base-url flag\n // 2. CODE_REVIEW_BASE_URL (universal override for any OpenAI-compatible endpoint)\n // 3. OLLAMA_HOST (automatic for ollama provider)\n const baseUrl = String(\n args.baseUrl ?? first(env.CODE_REVIEW_BASE_URL, resolveOllamaBaseUrl(model, env)) ?? '',\n );\n\n const maxTokens = Number(args.maxTokens ?? env.CODE_REVIEW_MAX_TOKENS ?? 0);\n\n const DEFAULT_MAX_DIFF_CHARS = 100_000;\n const rawMaxDiffChars = Number(args.maxDiffChars ?? env.CODE_REVIEW_MAX_DIFF_CHARS);\n const maxDiffChars =\n Number.isFinite(rawMaxDiffChars) && rawMaxDiffChars > 0\n ? rawMaxDiffChars\n : DEFAULT_MAX_DIFF_CHARS;\n\n const rawDecomposeHintLines = Number(\n args.decomposeHintLines ?? env.CODE_REVIEW_DECOMPOSE_HINT_LINES ?? 0,\n );\n const decomposeHintLines =\n Number.isFinite(rawDecomposeHintLines) && rawDecomposeHintLines > 0 ? rawDecomposeHintLines : 0;\n\n const rawDiffContext = Number(args.diffContext ?? env.CODE_REVIEW_DIFF_CONTEXT);\n const diffContext =\n Number.isFinite(rawDiffContext) && rawDiffContext >= 0 ? Math.floor(rawDiffContext) : 0;\n\n return {\n platform,\n project: String(args.project ?? env.CI_PROJECT_ID ?? ''),\n mr: String(args.mr ?? env.CI_MERGE_REQUEST_IID ?? ''),\n gitlabUrl,\n gitlabToken: token.token,\n gitlabAuthHeader: token.header,\n githubRepository: String(args.githubRepository ?? env.GITHUB_REPOSITORY ?? ''),\n githubPr: resolveGitHubPr(args, env),\n githubToken: String(args.githubToken ?? env.GITHUB_TOKEN ?? ''),\n githubApiUrl,\n githubServerUrl,\n model,\n modelPool: resolveModelPool(args, env),\n minSeverity: normalizeChoice(\n args.minSeverity ?? env.CODE_REVIEW_MIN_SEVERITY ?? 'info',\n ) as Severity,\n thinkingLevel: normalizeChoice(\n args.thinking ?? env.CODE_REVIEW_THINKING_LEVEL ?? 'off',\n ) as ThinkingLevel,\n reviewDepth: normalizeChoice(\n args.reviewDepth ?? env.CODE_REVIEW_DEPTH ?? 'single',\n ) as ReviewDepth,\n verifyModel: String(args.verifyModel ?? env.CODE_REVIEW_VERIFY_MODEL ?? ''),\n postingMode: normalizeChoice(\n args.postingMode ?? env.CODE_REVIEW_POSTING_MODE ?? 'direct',\n ) as PostingMode,\n apiKey,\n baseUrl,\n maxTokens,\n maxDiffChars,\n decomposeHintLines,\n diffContext,\n retrieveSkipped: toBoolean(args.retrieveSkipped) || toBoolean(env.CODE_REVIEW_RETRIEVE_SKIPPED),\n reviewFile: String(args.reviewFile ?? 'code-review.md'),\n output: String(args.output ?? 'review-comments.json'),\n dryRun: toBoolean(args.dryRun),\n noPost: toBoolean(args.noPost),\n postSummary: resolvePostSummary(args, env),\n forceReview: toBoolean(args.forceReview) || toBoolean(env.CODE_REVIEW_FORCE_REVIEW),\n verbose: toBoolean(args.verbose) || toBoolean(env.CODE_REVIEW_VERBOSE),\n cwd: String(args.cwd ?? process.cwd()),\n skills: resolveSkills(args, env),\n refreshGitSkills: toBoolean(env.CODE_REVIEW_REFRESH_SKILLS),\n };\n}\n\nexport function validateConfig(config: Config): void {\n // `api-key` is optional for providers that supply ambient credentials (e.g.\n // AWS Bedrock, Google Vertex) or that don't need a key at all (Ollama).\n // `resolveConfig` populates `apiKey` with a placeholder for these cases, so\n // the falsy check below already skips them — but we also skip when the\n // provider is `ollama` even if someone passes an empty string explicitly.\n const provider = parseModelProvider(config.model);\n const requiresApiKey = provider !== 'ollama';\n\n // Target identification and the write token are platform-specific; the model\n // and its API key are shared. GitHub's api-url has a built-in default, so it\n // is never listed as missing.\n const targetFields: Array<[string, string]> =\n config.platform === 'github'\n ? [\n ['github-repository', config.githubRepository],\n ['pr', config.githubPr],\n ['github-token', config.githubToken],\n ]\n : [\n ['project', config.project],\n ['mr', config.mr],\n ['gitlab-url', config.gitlabUrl],\n ['gitlab-token', config.gitlabToken],\n ];\n\n const missing = [\n ...targetFields,\n ['model', config.model],\n ...(requiresApiKey ? [['api-key', config.apiKey]] : []),\n ]\n .filter(([, value]) => !value)\n .map(([name]) => `--${name}`);\n\n if (missing.length > 0) {\n const ambientProviders = ['amazon-bedrock', 'google-vertex'];\n const isAmbientProvider = ambientProviders.includes(provider);\n const hints: string[] = [];\n if (missing.includes('--model')) {\n hints.push(\n 'Set --model (or CODE_REVIEW_MODEL) to a \"provider/modelId\" value, e.g. anthropic/claude-sonnet-4-5.',\n );\n }\n if (isAmbientProvider) {\n hints.push(\n `Provider \"${provider}\" requires ambient credentials. For Amazon Bedrock set AWS_ACCESS_KEY_ID / AWS_PROFILE; for Google Vertex run \\`gcloud auth application-default login\\` and set GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION.`,\n );\n } else if (missing.includes('--api-key')) {\n hints.push(\n \"Set the provider's standard API key env var (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY) or pass --api-key.\",\n );\n }\n if (config.platform === 'github') {\n if (missing.includes('--github-token')) {\n hints.push(\n 'Set GITHUB_TOKEN (or pass --github-token) with a token that can read the repo and write pull-request reviews; in GitHub Actions expose `${{ secrets.GITHUB_TOKEN }}` via env.',\n );\n }\n if (missing.includes('--github-repository') || missing.includes('--pr')) {\n hints.push(\n 'Set GITHUB_REPOSITORY (owner/repo) and the pull-request number — the latter comes from the pull_request event payload, GITHUB_REF (refs/pull/N/merge), or --pr.',\n );\n }\n } else if (hints.length === 0) {\n hints.push('Provide CLI flags or the corresponding GitLab CI environment variables.');\n }\n throw new ConfigError(`Missing required configuration: ${missing.join(', ')}.`, {\n hint: hints.join(' '),\n });\n }\n\n if (!['info', 'warn', 'critical'].includes(config.minSeverity)) {\n throw new ConfigError('--min-severity must be one of: info, warn, critical');\n }\n\n if (!THINKING_LEVELS.includes(config.thinkingLevel)) {\n throw new ConfigError(`--thinking must be one of: ${THINKING_LEVELS.join(', ')}`);\n }\n\n if (!REVIEW_DEPTHS.includes(config.reviewDepth)) {\n throw new ConfigError(`--review-depth must be one of: ${REVIEW_DEPTHS.join(', ')}`);\n }\n\n if (!POSTING_MODES.includes(config.postingMode)) {\n throw new ConfigError(`--posting-mode must be one of: ${POSTING_MODES.join(', ')}`);\n }\n}\n\nexport { type Severity, type ThinkingLevel };\n","import { randomUUID } from 'node:crypto';\nimport { tracingChannel, type TracingChannel } from 'node:diagnostics_channel';\nimport { performance } from 'node:perf_hooks';\nimport type { Config } from './config.js';\nimport type { Severity } from './types.js';\n\nexport type DiagnosticPhase =\n | 'run'\n | 'scm.get_merge_request'\n | 'scm.get_latest_version'\n | 'git.prepare_history'\n | 'git.get_merge_diff'\n | 'git.get_commit_log'\n | 'reviewer.run'\n | 'review.parse'\n | 'scm.get_discussions'\n | 'comments.build'\n | 'artifact.write_output'\n | 'scm.post_comments'\n | 'scm.upsert_summary';\n\nexport interface DiagnosticError {\n name?: string;\n message: string;\n code?: string;\n /** True when the failure was a deadline/abort, so the bridge can label it `timeout`. */\n timeout?: boolean;\n /**\n * HTTP status code when the failure came from a GitLab API response (e.g. a\n * 500 on `bulk_publish`). Propagates up the phase chain so the OTel bridge can\n * refine `error.type` and label the error counter with the status.\n */\n status?: number;\n}\n\nconst CENSOR = '[REDACTED]';\n/** Values shorter than this are not treated as secrets (avoids masking noise). */\nconst MIN_SECRET_SIZE = 6;\n\n/**\n * The encodings a secret value might appear under in a free-form error message,\n * mirroring the transforms in `@zapier/secret-scrubber` so a token is caught\n * whether it was logged raw, URL-encoded, form-encoded, JSON-escaped, or base64.\n */\nfunction secretVariants(value: string): string[] {\n return [\n value,\n encodeURIComponent(value),\n encodeURIComponent(value).replace(/%20/g, '+'),\n JSON.stringify(value).slice(1, -1), // JSON escaping, without the wrapping quotes\n Buffer.from(value, 'utf8').toString('base64'),\n ];\n}\n\n/**\n * Value-based secret redaction: removes the *known* secret values this run holds\n * (the GitLab token and the provider API key) from a string, in every encoding\n * they might appear under. Unlike pattern matching it cannot over-redact ordinary\n * text, cannot miss a token because of its format, and uses literal replacement\n * (no regex), so there is no catastrophic-backtracking risk on large messages.\n */\nexport function scrubSecrets(input: string, secretValues: readonly string[]): string {\n const variants = new Set<string>();\n for (const value of secretValues) {\n if (value.length < MIN_SECRET_SIZE) continue;\n for (const variant of secretVariants(value)) {\n if (variant.length >= MIN_SECRET_SIZE) variants.add(variant);\n }\n }\n // Replace longer variants first so a shorter one cannot pre-empt a longer match.\n const ordered = [...variants].toSorted((a, b) => b.length - a.length || (a < b ? 1 : -1));\n let result = input;\n for (const variant of ordered) result = result.split(variant).join(CENSOR);\n return result;\n}\n\n/** Collects the run's secret values (non-empty) for {@link scrubSecrets}. */\nexport function collectSecrets(config: Config): string[] {\n return [config.gitlabToken, config.apiKey].filter((value) => value.length > 0);\n}\n\nexport interface DiagnosticUsageBreakdown {\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n total: number;\n}\n\nexport interface DiagnosticUsage {\n model: string;\n tokens: DiagnosticUsageBreakdown;\n cost: DiagnosticUsageBreakdown;\n}\n\nexport interface DiagnosticContext {\n version: 1;\n runId: string;\n phase: DiagnosticPhase;\n project: string;\n mr: string;\n gitlabUrl: string;\n cwd: string;\n model: string;\n minSeverity: string;\n dryRun: boolean;\n noPost: boolean;\n startedAt: string;\n completedAt?: string;\n durationMs?: number;\n generated?: number;\n newComments?: number;\n duplicateComments?: number;\n posted?: number;\n /**\n * Breakdown of posted comments by severity, populated on the `run` context so\n * the OTel bridge can split `gitlab_review_comments_total` by severity. Counts\n * the non-duplicate (posted-intent) comments; absent on dry-run/skip paths.\n */\n postedBySeverity?: Partial<Record<Severity, number>>;\n warnings?: number;\n /** Why the reviewer JSON could not be parsed, on the `review.parse` phase when it fails. */\n malformedReason?: string;\n reviewFile?: string;\n output?: string;\n /** Number of files in the merge diff (set on the `git.get_merge_diff` phase). */\n diffFilesChanged?: number;\n /** Added content lines in the merge diff. */\n diffLinesAdded?: number;\n /** Removed content lines in the merge diff. */\n diffLinesRemoved?: number;\n /** HTTP method of the GitLab API call traced by this phase. */\n httpRequestMethod?: string;\n /** Full URL of the (last) GitLab API request in this phase; carries no secrets. */\n httpUrl?: string;\n /** HTTP status code of the (last) GitLab API response in this phase. */\n httpStatusCode?: number;\n /** Response Content-Length in bytes, when present. */\n httpResponseBodySize?: number;\n /** Host of the GitLab server the request targeted. */\n serverAddress?: string;\n draftsAbandoned?: number;\n draftsCreated?: number;\n draftsDeletedPrePublish?: number;\n draftsPublished?: number;\n /** Drafts that could not be published on the per-draft fallback path (silent comment drops). */\n draftsPublishFailed?: number;\n summaryAction?: 'created' | 'updated' | 'skipped';\n summaryNoteId?: number;\n usage?: DiagnosticUsage;\n errorInfo?: DiagnosticError;\n}\n\nexport const DIAGNOSTIC_CHANNEL_PREFIX = '@weareikko/code-review';\n\nexport const DIAGNOSTIC_CHANNEL_NAMES = {\n run: `${DIAGNOSTIC_CHANNEL_PREFIX}:run`,\n getMergeRequest: `${DIAGNOSTIC_CHANNEL_PREFIX}:scm.get_merge_request`,\n getLatestVersion: `${DIAGNOSTIC_CHANNEL_PREFIX}:scm.get_latest_version`,\n prepareGitHistory: `${DIAGNOSTIC_CHANNEL_PREFIX}:git.prepare_history`,\n getMergeDiff: `${DIAGNOSTIC_CHANNEL_PREFIX}:git.get_merge_diff`,\n getCommitLog: `${DIAGNOSTIC_CHANNEL_PREFIX}:git.get_commit_log`,\n runReviewer: `${DIAGNOSTIC_CHANNEL_PREFIX}:reviewer.run`,\n parseReview: `${DIAGNOSTIC_CHANNEL_PREFIX}:review.parse`,\n getDiscussions: `${DIAGNOSTIC_CHANNEL_PREFIX}:scm.get_discussions`,\n buildComments: `${DIAGNOSTIC_CHANNEL_PREFIX}:comments.build`,\n writeOutput: `${DIAGNOSTIC_CHANNEL_PREFIX}:artifact.write_output`,\n postComments: `${DIAGNOSTIC_CHANNEL_PREFIX}:scm.post_comments`,\n upsertSummary: `${DIAGNOSTIC_CHANNEL_PREFIX}:scm.upsert_summary`,\n} as const;\n\nexport const diagnosticChannels = Object.fromEntries(\n Object.entries(DIAGNOSTIC_CHANNEL_NAMES).map(([key, name]) => [\n key,\n tracingChannel<DiagnosticContext>(name),\n ]),\n) as Record<\n keyof typeof DIAGNOSTIC_CHANNEL_NAMES,\n ReturnType<typeof tracingChannel<DiagnosticContext>>\n>;\n\nexport function createDiagnosticRunId(): string {\n return randomUUID();\n}\n\nexport function createDiagnosticContext(\n phase: DiagnosticPhase,\n config: Config,\n runId: string,\n overrides: Partial<DiagnosticContext> = {},\n): DiagnosticContext {\n return {\n version: 1,\n runId,\n phase,\n project: config.project,\n mr: config.mr,\n gitlabUrl: config.gitlabUrl,\n cwd: config.cwd,\n model: config.model,\n minSeverity: config.minSeverity,\n dryRun: config.dryRun,\n noPost: config.noPost,\n reviewFile: config.reviewFile,\n output: config.output,\n startedAt: new Date().toISOString(),\n ...overrides,\n };\n}\n\nexport async function traceDiagnostic<T>(\n channel: TracingChannel<DiagnosticContext>,\n context: DiagnosticContext,\n operation: (context: DiagnosticContext) => Promise<T>,\n secretValues: readonly string[] = [],\n): Promise<T> {\n const started = performance.now();\n\n return channel.tracePromise(async () => {\n try {\n return await operation(context);\n } catch (error) {\n context.errorInfo = toDiagnosticError(error, secretValues);\n throw error;\n } finally {\n context.completedAt = new Date().toISOString();\n context.durationMs = Number((performance.now() - started).toFixed(3));\n }\n }, context);\n}\n\nexport function traceDiagnosticPhase<T>(\n phase: DiagnosticPhase,\n config: Config,\n runId: string,\n operation: (context: DiagnosticContext) => Promise<T>,\n overrides: Partial<DiagnosticContext> = {},\n): Promise<T> {\n const context = createDiagnosticContext(phase, config, runId, overrides);\n // The phase string is exactly the channel-name suffix, so the tracing channel\n // is derived directly rather than kept in a parallel phase→channel table. The\n // tracing sub-channels are process-wide singletons keyed by name, so this\n // publishes to the same channels `diagnosticChannels` exposes for subscription\n // (e.g. the OTel bridge).\n return traceDiagnostic(\n tracingChannel<DiagnosticContext>(`${DIAGNOSTIC_CHANNEL_PREFIX}:${phase}`),\n context,\n operation,\n collectSecrets(config),\n );\n}\n\nfunction toDiagnosticError(error: unknown, secretValues: readonly string[] = []): DiagnosticError {\n if (error instanceof Error) {\n const code = 'code' in error && typeof error.code === 'string' ? error.code : undefined;\n const timeout =\n 'timeout' in error && (error as { timeout?: unknown }).timeout === true ? true : undefined;\n const status =\n 'status' in error && typeof (error as { status?: unknown }).status === 'number'\n ? (error as { status: number }).status\n : undefined;\n return {\n name: error.name,\n message: scrubSecrets(error.message, secretValues),\n code,\n timeout,\n status,\n };\n }\n return { message: scrubSecrets(String(error), secretValues) };\n}\n","import { execFile, type ExecFileException } from 'node:child_process';\nimport { unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { GitError } from './errors.js';\n\nconst exec = promisify(execFile);\n\nexport interface GitOptions {\n cwd?: string;\n}\n\nexport interface PrepareGitHistoryOptions extends GitOptions {\n remote?: string;\n codeQualityArtifacts?: string[];\n}\n\nconst DEFAULT_DIFF_CONTEXT = 20;\n\nconst DEFAULT_CODEQUALITY_ARTIFACTS = [\n 'gl-code-quality-report.json',\n 'codequality.json',\n 'codeclimate.json',\n 'code-quality-report.json',\n];\n\nfunction gitErrorMessage(error: unknown): string {\n const err = error as ExecFileException & { stderr?: string; stdout?: string };\n return [err.message, err.stderr, err.stdout].filter(Boolean).join('\\n').trim();\n}\n\nexport async function git(args: string[], options: GitOptions = {}): Promise<string> {\n try {\n const { stdout } = await exec('git', args, {\n cwd: options.cwd,\n maxBuffer: 50 * 1024 * 1024,\n });\n return stdout;\n } catch (error) {\n throw new GitError(`git ${args.join(' ')} failed.`, {\n cause: error,\n hint: gitErrorMessage(error),\n });\n }\n}\n\nfunction remoteRef(remote: string, branch: string): string {\n return `refs/remotes/${remote}/${branch}`;\n}\n\nexport function getMergeDiffArguments(\n targetBranch: string,\n options: { remote?: string; context?: number } = {},\n): string[] {\n const remote = options.remote ?? 'origin';\n const context = options.context ?? DEFAULT_DIFF_CONTEXT;\n return [`${remoteRef(remote, targetBranch)}...HEAD`, `--unified=${context}`, '--'];\n}\n\n/**\n * Full git argv for the merge diff. `-c core.quotepath=false` keeps non-ASCII\n * paths literal instead of git's default octal-escaped + double-quoted form\n * (`\"a/caf\\303\\251.ts\"`), which the comment-position parser cannot match —\n * leaving such comments with invalid one-sided positions that 500 on\n * `bulk_publish`. Diffing literally also gives the reviewer readable paths.\n */\nexport function getMergeDiffCommand(\n targetBranch: string,\n options: { remote?: string; context?: number } = {},\n): string[] {\n return ['-c', 'core.quotepath=false', 'diff', ...getMergeDiffArguments(targetBranch, options)];\n}\n\nasync function fetchBranch(remote: string, branch: string, options: GitOptions): Promise<void> {\n await git(\n ['fetch', '--no-tags', remote, `+refs/heads/${branch}:${remoteRef(remote, branch)}`],\n options,\n );\n}\n\nasync function isTracked(path: string, options: GitOptions): Promise<boolean> {\n try {\n await git(['ls-files', '--error-unmatch', '--', path], options);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function removeGeneratedCodeQualityArtifacts(\n paths = DEFAULT_CODEQUALITY_ARTIFACTS,\n options: GitOptions = {},\n): Promise<string[]> {\n const removed: string[] = [];\n for (const path of paths) {\n if (await isTracked(path, options)) continue;\n try {\n await unlink(options.cwd ? join(options.cwd, path) : path);\n removed.push(path);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== 'ENOENT') throw error;\n }\n }\n return removed;\n}\n\nexport async function prepareGitHistory(\n sourceBranch: string,\n targetBranch: string,\n options: PrepareGitHistoryOptions = {},\n): Promise<void> {\n const remote = options.remote ?? 'origin';\n\n await removeGeneratedCodeQualityArtifacts(options.codeQualityArtifacts, options);\n\n // Unshallow first when possible. Git exits non-zero in full clones; that is not actionable.\n await git(['fetch', '--unshallow', '--no-tags', remote], options).catch(() => undefined);\n\n const fetchErrors: string[] = [];\n for (const branch of [targetBranch, sourceBranch]) {\n try {\n await fetchBranch(remote, branch, options);\n } catch (error) {\n fetchErrors.push(`${branch}: ${gitErrorMessage(error)}`);\n }\n }\n\n if (fetchErrors.length === 2) {\n throw new GitError(`Unable to fetch MR source/target branches from ${remote}.`, {\n hint: fetchErrors.join('\\n'),\n });\n }\n\n try {\n await git(['merge-base', remoteRef(remote, targetBranch), 'HEAD'], options);\n } catch (error) {\n const fetchDetail =\n fetchErrors.length > 0 ? `\\nFetch warnings:\\n${fetchErrors.join('\\n')}` : '';\n throw new GitError(\n `Unable to prepare Git history for MR review: merge-base ${remoteRef(remote, targetBranch)} HEAD failed.`,\n {\n cause: error,\n hint: `Set GIT_DEPTH: 0 or ensure ${remote}/${targetBranch} is fetchable.${fetchDetail}\\n${gitErrorMessage(error)}`,\n },\n );\n }\n}\n\nexport async function getMergeDiff(\n targetBranch: string,\n options: GitOptions & { remote?: string; context?: number } = {},\n): Promise<string> {\n return git(getMergeDiffCommand(targetBranch, options), options);\n}\n\nexport function getMergeCommitLogArguments(\n targetBranch: string,\n options: { remote?: string } = {},\n): string[] {\n const remote = options.remote ?? 'origin';\n return [\n `${remoteRef(remote, targetBranch)}...HEAD`,\n '--pretty=tformat:commit %h%nAuthor: %an%nDate: %as%n%n%s%n%n%b',\n '--reverse',\n '--no-merges',\n ];\n}\n\nexport async function getMergeCommitLog(\n targetBranch: string,\n options: GitOptions & { remote?: string } = {},\n): Promise<string> {\n return git(['log', ...getMergeCommitLogArguments(targetBranch, options)], options);\n}\n\nexport interface DiffSummary {\n filesChanged: number;\n linesAdded: number;\n linesRemoved: number;\n}\n\n/**\n * Summarize a unified diff into file/line counts for telemetry. Counts one file\n * per `diff --git` header and counts `+`/`-` lines only inside a hunk (after a\n * `@@` header), so the `--- a/file` / `+++ b/file` header lines are excluded and\n * a genuine content line whose text starts with `++`/`--` is still counted. Pure\n * and allocation-light so it can run on the full merge diff without an extra git\n * invocation.\n */\nexport function summarizeDiff(diff: string): DiffSummary {\n let filesChanged = 0;\n let linesAdded = 0;\n let linesRemoved = 0;\n let inHunk = false;\n for (const line of diff.split('\\n')) {\n if (line.startsWith('diff --git ')) {\n filesChanged += 1;\n inHunk = false;\n } else if (line.startsWith('@@ ')) {\n inHunk = true;\n } else if (inHunk && line.startsWith('+')) {\n linesAdded += 1;\n } else if (inHunk && line.startsWith('-')) {\n linesRemoved += 1;\n }\n }\n return { filesChanged, linesAdded, linesRemoved };\n}\n","export type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst LEVELS: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\n\nexport interface Logger {\n debug(message: string): void;\n info(message: string): void;\n warn(message: string): void;\n error(message: string): void;\n}\n\nexport function createLogger(minLevel: LogLevel = 'info'): Logger {\n const min = LEVELS[minLevel];\n function log(level: LogLevel, message: string): void {\n if (LEVELS[level] < min) return;\n process.stderr.write(`[code-review] ${message}\\n`);\n }\n return {\n debug: (message) => log('debug', message),\n info: (message) => log('info', message),\n warn: (message) => log('warn', message),\n error: (message) => log('error', message),\n };\n}\n\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n","export class JSONRepairError extends Error {\n constructor(message, position) {\n super(`${message} at position ${position}`);\n this.position = position;\n }\n}\n//# sourceMappingURL=JSONRepairError.js.map","const codeSpace = 0x20; // \" \"\nconst codeNewline = 0xa; // \"\\n\"\nconst codeTab = 0x9; // \"\\t\"\nconst codeReturn = 0xd; // \"\\r\"\n\n// unicode spaces: https://jkorpela.fi/chars/spaces.html\nconst codeNonBreakingSpace = 0x00a0;\nconst codeMongolianVowelSeparator = 0x180e;\nconst codeEnQuad = 0x2000;\nconst codeZeroWidthSpace = 0x200b;\nconst codeNarrowNoBreakSpace = 0x202f;\nconst codeMediumMathematicalSpace = 0x205f;\nconst codeIdeographicSpace = 0x3000;\nconst codeZeroWidthNoBreakSpace = 0xfeff;\nexport function isHex(char) {\n return /^[0-9A-Fa-f]$/.test(char);\n}\nexport function isDigit(char) {\n return char >= '0' && char <= '9';\n}\nexport function isValidStringCharacter(char) {\n // note that the valid range is between \\u{0020} and \\u{10ffff},\n // but in JavaScript it is not possible to create a code point larger than\n // \\u{10ffff}, so there is no need to test for that here.\n return char >= '\\u0020';\n}\nexport function isDelimiter(char) {\n return ',:[]/{}()\\n+'.includes(char);\n}\nexport function isFunctionNameCharStart(char) {\n return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char === '_' || char === '$';\n}\nexport function isFunctionNameChar(char) {\n return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char === '_' || char === '$' || char >= '0' && char <= '9';\n}\n\n// matches \"https://\" and other schemas\nexport const regexUrlStart = /^(http|https|ftp|mailto|file|data|irc):\\/\\/$/;\n\n// matches all valid URL characters EXCEPT \"[\", \"]\", and \",\", since that are important JSON delimiters\nexport const regexUrlChar = /^[A-Za-z0-9-._~:/?#@!$&'()*+;=]$/;\nexport function isUnquotedStringDelimiter(char) {\n return ',[]/{}\\n+'.includes(char);\n}\nexport function isStartOfValue(char) {\n return isQuote(char) || regexStartOfValue.test(char);\n}\n\n// alpha, number, minus, or opening bracket or brace\nconst regexStartOfValue = /^[[{\\w-]$/;\nexport function isControlCharacter(char) {\n return char === '\\n' || char === '\\r' || char === '\\t' || char === '\\b' || char === '\\f';\n}\n/**\n * Check if the given character is a whitespace character like space, tab, or\n * newline\n */\nexport function isWhitespace(text, index) {\n const code = text.charCodeAt(index);\n return code === codeSpace || code === codeNewline || code === codeTab || code === codeReturn;\n}\n\n/**\n * Check if the given character is a whitespace character like space or tab,\n * but NOT a newline\n */\nexport function isWhitespaceExceptNewline(text, index) {\n const code = text.charCodeAt(index);\n return code === codeSpace || code === codeTab || code === codeReturn;\n}\n\n/**\n * Check if the given character is a special whitespace character, some\n * unicode variant\n */\nexport function isSpecialWhitespace(text, index) {\n const code = text.charCodeAt(index);\n return code === codeNonBreakingSpace || code === codeMongolianVowelSeparator || code >= codeEnQuad && code <= codeZeroWidthSpace || code === codeNarrowNoBreakSpace || code === codeMediumMathematicalSpace || code === codeIdeographicSpace || code === codeZeroWidthNoBreakSpace;\n}\n\n/**\n * Test whether the given character is a quote or double quote character.\n * Also tests for special variants of quotes.\n */\nexport function isQuote(char) {\n // the first check double quotes, since that occurs most often\n return isDoubleQuoteLike(char) || isSingleQuoteLike(char);\n}\n\n/**\n * Test whether the given character is a double quote character.\n * Also tests for special variants of double quotes.\n */\nexport function isDoubleQuoteLike(char) {\n return char === '\"' || char === '\\u201c' || char === '\\u201d';\n}\n\n/**\n * Test whether the given character is a double quote character.\n * Does NOT test for special variants of double quotes.\n */\nexport function isDoubleQuote(char) {\n return char === '\"';\n}\n\n/**\n * Test whether the given character is a single quote character.\n * Also tests for special variants of single quotes.\n */\nexport function isSingleQuoteLike(char) {\n return char === \"'\" || char === '\\u2018' || char === '\\u2019' || char === '\\u0060' || char === '\\u00b4';\n}\n\n/**\n * Test whether the given character is a single quote character.\n * Does NOT test for special variants of single quotes.\n */\nexport function isSingleQuote(char) {\n return char === \"'\";\n}\n\n/**\n * Strip last occurrence of textToStrip from text\n */\nexport function stripLastOccurrence(text, textToStrip) {\n let stripRemainingText = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;\n const index = text.lastIndexOf(textToStrip);\n return index !== -1 ? text.substring(0, index) + (stripRemainingText ? '' : text.substring(index + 1)) : text;\n}\nexport function insertBeforeLastWhitespace(text, textToInsert) {\n let index = text.length;\n if (!isWhitespace(text, index - 1)) {\n // no trailing whitespaces\n return text + textToInsert;\n }\n while (isWhitespace(text, index - 1)) {\n index--;\n }\n return text.substring(0, index) + textToInsert + text.substring(index);\n}\nexport function removeAtIndex(text, start, count) {\n return text.substring(0, start) + text.substring(start + count);\n}\n\n/**\n * Test whether a string ends with a newline or comma character and optional whitespace\n */\nexport function endsWithCommaOrNewline(text) {\n return /[,\\n][ \\t\\r]*$/.test(text);\n}\n//# sourceMappingURL=stringUtils.js.map","import { JSONRepairError } from '../utils/JSONRepairError.js';\nimport { endsWithCommaOrNewline, insertBeforeLastWhitespace, isControlCharacter, isDelimiter, isDigit, isDoubleQuote, isDoubleQuoteLike, isFunctionNameChar, isFunctionNameCharStart, isHex, isQuote, isSingleQuote, isSingleQuoteLike, isSpecialWhitespace, isStartOfValue, isUnquotedStringDelimiter, isValidStringCharacter, isWhitespace, isWhitespaceExceptNewline, regexUrlChar, regexUrlStart, removeAtIndex, stripLastOccurrence } from '../utils/stringUtils.js';\nconst controlCharacters = {\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t'\n};\n\n// map with all escape characters\nconst escapeCharacters = {\n '\"': '\"',\n '\\\\': '\\\\',\n '/': '/',\n b: '\\b',\n f: '\\f',\n n: '\\n',\n r: '\\r',\n t: '\\t'\n // note that \\u is handled separately in parseString()\n};\n\n/**\n * Repair a string containing an invalid JSON document.\n * For example changes JavaScript notation into JSON notation.\n *\n * Example:\n *\n * try {\n * const json = \"{name: 'John'}\"\n * const repaired = jsonrepair(json)\n * console.log(repaired)\n * // '{\"name\": \"John\"}'\n * } catch (err) {\n * console.error(err)\n * }\n *\n */\nexport function jsonrepair(text) {\n let i = 0; // current index in text\n let output = ''; // generated output\n\n parseMarkdownCodeBlock(['```', '[```', '{```']);\n const processed = parseValue();\n if (!processed) {\n throwUnexpectedEnd();\n }\n parseMarkdownCodeBlock(['```', '```]', '```}']);\n const processedComma = parseCharacter(',');\n if (processedComma) {\n parseWhitespaceAndSkipComments();\n }\n if (isStartOfValue(text[i]) && endsWithCommaOrNewline(output)) {\n // start of a new value after end of the root level object: looks like\n // newline delimited JSON -> turn into a root level array\n if (!processedComma) {\n // repair missing comma\n output = insertBeforeLastWhitespace(output, ',');\n }\n parseNewlineDelimitedJSON();\n } else if (processedComma) {\n // repair: remove trailing comma\n output = stripLastOccurrence(output, ',');\n }\n\n // repair redundant end quotes\n while (text[i] === '}' || text[i] === ']') {\n i++;\n parseWhitespaceAndSkipComments();\n }\n if (i >= text.length) {\n // reached the end of the document properly\n return output;\n }\n throwUnexpectedCharacter();\n function parseValue() {\n parseWhitespaceAndSkipComments();\n const processed = parseObject() || parseArray() || parseString() || parseNumber() || parseKeywords() || parseUnquotedString(false) || parseRegex();\n parseWhitespaceAndSkipComments();\n return processed;\n }\n function parseWhitespaceAndSkipComments() {\n let skipNewline = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;\n const start = i;\n let changed = parseWhitespace(skipNewline);\n do {\n changed = parseComment();\n if (changed) {\n changed = parseWhitespace(skipNewline);\n }\n } while (changed);\n return i > start;\n }\n function parseWhitespace(skipNewline) {\n const _isWhiteSpace = skipNewline ? isWhitespace : isWhitespaceExceptNewline;\n let whitespace = '';\n while (true) {\n if (_isWhiteSpace(text, i)) {\n whitespace += text[i];\n i++;\n } else if (isSpecialWhitespace(text, i)) {\n // repair special whitespace\n whitespace += ' ';\n i++;\n } else {\n break;\n }\n }\n if (whitespace.length > 0) {\n output += whitespace;\n return true;\n }\n return false;\n }\n function parseComment() {\n // find a block comment '/* ... */'\n if (text[i] === '/' && text[i + 1] === '*') {\n // repair block comment by skipping it\n while (i < text.length && !atEndOfBlockComment(text, i)) {\n i++;\n }\n i += 2;\n return true;\n }\n\n // find a line comment '// ...'\n if (text[i] === '/' && text[i + 1] === '/') {\n // repair line comment by skipping it\n while (i < text.length && text[i] !== '\\n') {\n i++;\n }\n return true;\n }\n return false;\n }\n function parseMarkdownCodeBlock(blocks) {\n // find and skip over a Markdown fenced code block:\n // ``` ... ```\n // or\n // ```json ... ```\n if (skipMarkdownCodeBlock(blocks)) {\n if (isFunctionNameCharStart(text[i])) {\n // strip the optional language specifier like \"json\"\n while (i < text.length && isFunctionNameChar(text[i])) {\n i++;\n }\n }\n parseWhitespaceAndSkipComments();\n return true;\n }\n return false;\n }\n function skipMarkdownCodeBlock(blocks) {\n parseWhitespace(true);\n for (const block of blocks) {\n const end = i + block.length;\n if (text.slice(i, end) === block) {\n i = end;\n return true;\n }\n }\n return false;\n }\n function parseCharacter(char) {\n if (text[i] === char) {\n output += text[i];\n i++;\n return true;\n }\n return false;\n }\n function skipCharacter(char) {\n if (text[i] === char) {\n i++;\n return true;\n }\n return false;\n }\n function skipEscapeCharacter() {\n return skipCharacter('\\\\');\n }\n\n /**\n * Skip ellipsis like \"[1,2,3,...]\" or \"[1,2,3,...,9]\" or \"[...,7,8,9]\"\n * or a similar construct in objects.\n */\n function skipEllipsis() {\n parseWhitespaceAndSkipComments();\n if (text[i] === '.' && text[i + 1] === '.' && text[i + 2] === '.') {\n // repair: remove the ellipsis (three dots) and optionally a comma\n i += 3;\n parseWhitespaceAndSkipComments();\n skipCharacter(',');\n return true;\n }\n return false;\n }\n\n /**\n * Parse an object like '{\"key\": \"value\"}'\n */\n function parseObject() {\n if (text[i] === '{') {\n output += '{';\n i++;\n parseWhitespaceAndSkipComments();\n\n // repair: skip leading comma like in {, message: \"hi\"}\n if (skipCharacter(',')) {\n parseWhitespaceAndSkipComments();\n }\n let initial = true;\n while (i < text.length && text[i] !== '}') {\n let processedComma;\n if (!initial) {\n processedComma = parseCharacter(',');\n if (!processedComma) {\n // repair missing comma\n output = insertBeforeLastWhitespace(output, ',');\n }\n parseWhitespaceAndSkipComments();\n } else {\n processedComma = true;\n initial = false;\n }\n skipEllipsis();\n const processedKey = parseString() || parseUnquotedString(true);\n if (!processedKey) {\n if (text[i] === '}' || text[i] === '{' || text[i] === ']' || text[i] === '[' || text[i] === undefined) {\n // repair trailing comma\n output = stripLastOccurrence(output, ',');\n } else {\n throwObjectKeyExpected();\n }\n break;\n }\n parseWhitespaceAndSkipComments();\n const processedColon = parseCharacter(':');\n const truncatedText = i >= text.length;\n if (!processedColon) {\n if (isStartOfValue(text[i]) || truncatedText) {\n // repair missing colon\n output = insertBeforeLastWhitespace(output, ':');\n } else {\n throwColonExpected();\n }\n }\n const processedValue = parseValue();\n if (!processedValue) {\n if (processedColon || truncatedText) {\n // repair missing object value\n output += 'null';\n } else {\n throwColonExpected();\n }\n }\n }\n if (text[i] === '}') {\n output += '}';\n i++;\n } else {\n // repair missing end bracket\n output = insertBeforeLastWhitespace(output, '}');\n }\n return true;\n }\n return false;\n }\n\n /**\n * Parse an array like '[\"item1\", \"item2\", ...]'\n */\n function parseArray() {\n if (text[i] === '[') {\n output += '[';\n i++;\n parseWhitespaceAndSkipComments();\n\n // repair: skip leading comma like in [,1,2,3]\n if (skipCharacter(',')) {\n parseWhitespaceAndSkipComments();\n }\n let initial = true;\n while (i < text.length && text[i] !== ']') {\n if (!initial) {\n const processedComma = parseCharacter(',');\n if (!processedComma) {\n // repair missing comma\n output = insertBeforeLastWhitespace(output, ',');\n }\n } else {\n initial = false;\n }\n skipEllipsis();\n const processedValue = parseValue();\n if (!processedValue) {\n // repair trailing comma\n output = stripLastOccurrence(output, ',');\n break;\n }\n }\n if (text[i] === ']') {\n output += ']';\n i++;\n } else {\n // repair missing closing array bracket\n output = insertBeforeLastWhitespace(output, ']');\n }\n return true;\n }\n return false;\n }\n\n /**\n * Parse and repair Newline Delimited JSON (NDJSON):\n * multiple JSON objects separated by a newline character\n */\n function parseNewlineDelimitedJSON() {\n // repair NDJSON\n let initial = true;\n let processedValue = true;\n while (processedValue) {\n if (!initial) {\n // parse optional comma, insert when missing\n const processedComma = parseCharacter(',');\n if (!processedComma) {\n // repair: add missing comma\n output = insertBeforeLastWhitespace(output, ',');\n }\n } else {\n initial = false;\n }\n processedValue = parseValue();\n }\n if (!processedValue) {\n // repair: remove trailing comma\n output = stripLastOccurrence(output, ',');\n }\n\n // repair: wrap the output inside array brackets\n output = `[\\n${output}\\n]`;\n }\n\n /**\n * Parse a string enclosed by double quotes \"...\". Can contain escaped quotes\n * Repair strings enclosed in single quotes or special quotes\n * Repair an escaped string\n *\n * The function can run in two stages:\n * - First, it assumes the string has a valid end quote\n * - If it turns out that the string does not have a valid end quote followed\n * by a delimiter (which should be the case), the function runs again in a\n * more conservative way, stopping the string at the first next delimiter\n * and fixing the string by inserting a quote there, or stopping at a\n * stop index detected in the first iteration.\n */\n function parseString() {\n let stopAtDelimiter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n let stopAtIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;\n let skipEscapeChars = text[i] === '\\\\';\n if (skipEscapeChars) {\n // repair: remove the first escape character\n i++;\n skipEscapeChars = true;\n }\n if (isQuote(text[i])) {\n // double quotes are correct JSON,\n // single quotes come from JavaScript for example, we assume it will have a correct single end quote too\n // otherwise, we will match any double-quote-like start with a double-quote-like end,\n // or any single-quote-like start with a single-quote-like end\n const isEndQuote = isDoubleQuote(text[i]) ? isDoubleQuote : isSingleQuote(text[i]) ? isSingleQuote : isSingleQuoteLike(text[i]) ? isSingleQuoteLike : isDoubleQuoteLike;\n const iBefore = i;\n const oBefore = output.length;\n let str = '\"';\n i++;\n while (true) {\n if (i >= text.length) {\n // end of text, we are missing an end quote\n\n const iPrev = prevNonWhitespaceIndex(i - 1);\n if (!stopAtDelimiter && isDelimiter(text.charAt(iPrev))) {\n // if the text ends with a delimiter, like [\"hello],\n // so the missing end quote should be inserted before this delimiter\n // retry parsing the string, stopping at the first next delimiter\n i = iBefore;\n output = output.substring(0, oBefore);\n return parseString(true);\n }\n\n // repair missing quote\n str = insertBeforeLastWhitespace(str, '\"');\n output += str;\n return true;\n }\n if (i === stopAtIndex) {\n // use the stop index detected in the first iteration, and repair end quote\n str = insertBeforeLastWhitespace(str, '\"');\n output += str;\n return true;\n }\n if (isEndQuote(text[i])) {\n // end quote\n // let us check what is before and after the quote to verify whether this is a legit end quote\n const iQuote = i;\n const oQuote = str.length;\n str += '\"';\n i++;\n output += str;\n parseWhitespaceAndSkipComments(false);\n if (stopAtDelimiter || i >= text.length || isDelimiter(text[i]) || isQuote(text[i]) || isDigit(text[i])) {\n // The quote is followed by the end of the text, a delimiter,\n // or a next value. So the quote is indeed the end of the string.\n parseConcatenatedString();\n return true;\n }\n const iPrevChar = prevNonWhitespaceIndex(iQuote - 1);\n const prevChar = text.charAt(iPrevChar);\n if (prevChar === ',') {\n // A comma followed by a quote, like '{\"a\":\"b,c,\"d\":\"e\"}'.\n // We assume that the quote is a start quote, and that the end quote\n // should have been located right before the comma but is missing.\n i = iBefore;\n output = output.substring(0, oBefore);\n return parseString(false, iPrevChar);\n }\n if (isDelimiter(prevChar)) {\n // This is not the right end quote: it is preceded by a delimiter,\n // and NOT followed by a delimiter. So, there is an end quote missing\n // parse the string again and then stop at the first next delimiter\n i = iBefore;\n output = output.substring(0, oBefore);\n return parseString(true);\n }\n\n // revert to right after the quote but before any whitespace, and continue parsing the string\n output = output.substring(0, oBefore);\n i = iQuote + 1;\n\n // repair unescaped quote\n str = `${str.substring(0, oQuote)}\\\\${str.substring(oQuote)}`;\n } else if (stopAtDelimiter && isUnquotedStringDelimiter(text[i])) {\n // we're in the mode to stop the string at the first delimiter\n // because there is an end quote missing\n\n // test start of an url like \"https://...\" (this would be parsed as a comment)\n if (text[i - 1] === ':' && regexUrlStart.test(text.substring(iBefore + 1, i + 2))) {\n while (i < text.length && regexUrlChar.test(text[i])) {\n str += text[i];\n i++;\n }\n }\n\n // repair missing quote\n str = insertBeforeLastWhitespace(str, '\"');\n output += str;\n parseConcatenatedString();\n return true;\n } else if (text[i] === '\\\\') {\n // handle escaped content like \\n or \\u2605\n const char = text.charAt(i + 1);\n const escapeChar = escapeCharacters[char];\n if (escapeChar !== undefined) {\n str += text.slice(i, i + 2);\n i += 2;\n } else if (char === 'u') {\n let j = 2;\n while (j < 6 && isHex(text[i + j])) {\n j++;\n }\n if (j === 6) {\n str += text.slice(i, i + 6);\n i += 6;\n } else if (i + j >= text.length) {\n // repair invalid or truncated unicode char at the end of the text\n // by removing the unicode char and ending the string here\n i = text.length;\n } else {\n throwInvalidUnicodeCharacter();\n }\n } else if (char === '\\n') {\n // repair a backslash escaped newline (like in Bash scripts)\n str += '\\\\n';\n i += 2;\n } else {\n // repair invalid escape character: remove it\n str += char;\n i += 2;\n }\n } else {\n // handle regular characters\n const char = text.charAt(i);\n if (char === '\"' && text[i - 1] !== '\\\\') {\n // repair unescaped double quote\n str += `\\\\${char}`;\n i++;\n } else if (isControlCharacter(char)) {\n // unescaped control character\n str += controlCharacters[char];\n i++;\n } else {\n if (!isValidStringCharacter(char)) {\n throwInvalidCharacter(char);\n }\n str += char;\n i++;\n }\n }\n if (skipEscapeChars) {\n // repair: skipped escape character (nothing to do)\n skipEscapeCharacter();\n }\n }\n }\n return false;\n }\n\n /**\n * Repair concatenated strings like \"hello\" + \"world\", change this into \"helloworld\"\n */\n function parseConcatenatedString() {\n let processed = false;\n parseWhitespaceAndSkipComments();\n while (text[i] === '+') {\n processed = true;\n i++;\n parseWhitespaceAndSkipComments();\n\n // repair: remove the end quote of the first string\n output = stripLastOccurrence(output, '\"', true);\n const start = output.length;\n const parsedStr = parseString();\n if (parsedStr) {\n // repair: remove the start quote of the second string\n output = removeAtIndex(output, start, 1);\n } else {\n // repair: remove the + because it is not followed by a string\n output = insertBeforeLastWhitespace(output, '\"');\n }\n }\n return processed;\n }\n\n /**\n * Parse a number like 2.4 or 2.4e6\n */\n function parseNumber() {\n const start = i;\n if (text[i] === '-') {\n i++;\n if (atEndOfNumber()) {\n repairNumberEndingWithNumericSymbol(start);\n return true;\n }\n if (!isDigit(text[i])) {\n i = start;\n return false;\n }\n }\n\n // Note that in JSON leading zeros like \"00789\" are not allowed.\n // We will allow all leading zeros here though and at the end of parseNumber\n // check against trailing zeros and repair that if needed.\n // Leading zeros can have meaning, so we should not clear them.\n while (isDigit(text[i])) {\n i++;\n }\n if (text[i] === '.') {\n i++;\n if (atEndOfNumber()) {\n repairNumberEndingWithNumericSymbol(start);\n return true;\n }\n if (!isDigit(text[i])) {\n i = start;\n return false;\n }\n while (isDigit(text[i])) {\n i++;\n }\n }\n if (text[i] === 'e' || text[i] === 'E') {\n i++;\n if (text[i] === '-' || text[i] === '+') {\n i++;\n }\n if (atEndOfNumber()) {\n repairNumberEndingWithNumericSymbol(start);\n return true;\n }\n if (!isDigit(text[i])) {\n i = start;\n return false;\n }\n while (isDigit(text[i])) {\n i++;\n }\n }\n\n // if we're not at the end of the number by this point, allow this to be parsed as another type\n if (!atEndOfNumber()) {\n i = start;\n return false;\n }\n if (i > start) {\n // repair a number with leading zeros like \"00789\"\n const num = text.slice(start, i);\n const hasInvalidLeadingZero = /^0\\d/.test(num);\n output += hasInvalidLeadingZero ? `\"${num}\"` : num;\n return true;\n }\n return false;\n }\n\n /**\n * Parse keywords true, false, null\n * Repair Python keywords True, False, None\n */\n function parseKeywords() {\n return parseKeyword('true', 'true') || parseKeyword('false', 'false') || parseKeyword('null', 'null') ||\n // repair Python keywords True, False, None\n parseKeyword('True', 'true') || parseKeyword('False', 'false') || parseKeyword('None', 'null');\n }\n function parseKeyword(name, value) {\n if (text.slice(i, i + name.length) === name) {\n output += value;\n i += name.length;\n return true;\n }\n return false;\n }\n\n /**\n * Repair an unquoted string by adding quotes around it\n * Repair a MongoDB function call like NumberLong(\"2\")\n * Repair a JSONP function call like callback({...});\n */\n function parseUnquotedString(isKey) {\n // note that the symbol can end with whitespaces: we stop at the next delimiter\n // also, note that we allow strings to contain a slash / in order to support repairing regular expressions\n const start = i;\n if (isFunctionNameCharStart(text[i])) {\n while (i < text.length && isFunctionNameChar(text[i])) {\n i++;\n }\n let j = i;\n while (isWhitespace(text, j)) {\n j++;\n }\n if (text[j] === '(') {\n // repair a MongoDB function call like NumberLong(\"2\")\n // repair a JSONP function call like callback({...});\n i = j + 1;\n parseValue();\n if (text[i] === ')') {\n // repair: skip close bracket of function call\n i++;\n if (text[i] === ';') {\n // repair: skip semicolon after JSONP call\n i++;\n }\n }\n return true;\n }\n }\n while (i < text.length && !isUnquotedStringDelimiter(text[i]) && !isQuote(text[i]) && (!isKey || text[i] !== ':')) {\n i++;\n }\n\n // test start of an url like \"https://...\" (this would be parsed as a comment)\n if (text[i - 1] === ':' && regexUrlStart.test(text.substring(start, i + 2))) {\n while (i < text.length && regexUrlChar.test(text[i])) {\n i++;\n }\n }\n if (i > start) {\n // repair unquoted string\n // also, repair undefined into null\n\n // first, go back to prevent getting trailing whitespaces in the string\n while (isWhitespace(text, i - 1) && i > 0) {\n i--;\n }\n const symbol = text.slice(start, i);\n output += symbol === 'undefined' ? 'null' : JSON.stringify(symbol);\n if (text[i] === '\"') {\n // we had a missing start quote, but now we encountered the end quote, so we can skip that one\n i++;\n }\n return true;\n }\n }\n function parseRegex() {\n if (text[i] === '/') {\n const start = i;\n i++;\n while (i < text.length && (text[i] !== '/' || text[i - 1] === '\\\\')) {\n i++;\n }\n i++;\n output += JSON.stringify(text.substring(start, i));\n return true;\n }\n }\n function prevNonWhitespaceIndex(start) {\n let prev = start;\n while (prev > 0 && isWhitespace(text, prev)) {\n prev--;\n }\n return prev;\n }\n function atEndOfNumber() {\n return i >= text.length || isDelimiter(text[i]) || isWhitespace(text, i);\n }\n function repairNumberEndingWithNumericSymbol(start) {\n // repair numbers cut off at the end\n // this will only be called when we end after a '.', '-', or 'e' and does not\n // change the number more than it needs to make it valid JSON\n output += `${text.slice(start, i)}0`;\n }\n function throwInvalidCharacter(char) {\n throw new JSONRepairError(`Invalid character ${JSON.stringify(char)}`, i);\n }\n function throwUnexpectedCharacter() {\n throw new JSONRepairError(`Unexpected character ${JSON.stringify(text[i])}`, i);\n }\n function throwUnexpectedEnd() {\n throw new JSONRepairError('Unexpected end of json string', text.length);\n }\n function throwObjectKeyExpected() {\n throw new JSONRepairError('Object key expected', i);\n }\n function throwColonExpected() {\n throw new JSONRepairError('Colon expected', i);\n }\n function throwInvalidUnicodeCharacter() {\n const chars = text.slice(i, i + 6);\n throw new JSONRepairError(`Invalid unicode character \"${chars}\"`, i);\n }\n}\nfunction atEndOfBlockComment(text, i) {\n return text[i] === '*' && text[i + 1] === '/';\n}\n//# sourceMappingURL=jsonrepair.js.map","import type { ReviewComment, Severity } from './types.js';\n\n/**\n * The Verify stage hands each severe finding to a separate, adversarial agent\n * whose job is to refute it. The agent returns one of three decisions, which\n * the Synthesize stage applies deterministically:\n *\n * - `keep`: the finding is proven at its stated severity — survives unchanged.\n * - `downgrade`: a real concern, but the stated severity overstates the\n * demonstrable impact — severity steps down one tier (and the Conventional\n * Comment header is relabelled to match).\n * - `drop`: not a real defect — removed from the review.\n *\n * Unknown / unparseable verifier output defaults to `keep` so a flaky verifier\n * never silently deletes a finding (precision is the goal, but not at the cost\n * of dropping findings we failed to actually evaluate).\n */\nexport type VerifyDecision = 'keep' | 'downgrade' | 'drop';\n\nexport interface Verdict {\n decision: VerifyDecision;\n reason: string;\n}\n\nexport interface AuditEntry {\n file: string;\n line: number;\n action: 'dropped' | 'downgraded';\n fromSeverity: Severity;\n toSeverity?: Severity;\n reason: string;\n}\n\nexport interface SynthesisResult {\n comments: ReviewComment[];\n audit: AuditEntry[];\n}\n\n// --- Prompts --------------------------------------------------------------\n\nexport function buildVerifySystemPrompt(diff: string, commitLog?: string): string {\n const parts: string[] = [\n 'You are a strict, adversarial verifier of a SINGLE code-review finding. Your job is to REFUTE the finding, not to agree with it.',\n '',\n 'Each request gives you one proposed finding (file, line, severity, confidence, and body) to check against the diff below. You may read referenced files to confirm reachability. Decide whether the finding survives scrutiny.',\n '',\n 'Apply this bar:',\n '- The finding must point to a concrete defect demonstrable from the diff (and any file you read): a specific input, state, or execution path triggers it, and a violated contract is visible.',\n '- A finding you cannot prove is wrong. Default to refuting when the failure path is not demonstrable from the evidence.',\n '- A CRITICAL finding MUST prove a reachable failure path. If it cannot, it is not CRITICAL.',\n '- An in-file comment, commit message, or prior decision that justifies the pattern refutes a finding that ignores it.',\n '',\n 'Return EXACTLY one JSON object and nothing else — no prose, no markdown fences:',\n '{ \"decision\": \"keep\" | \"downgrade\" | \"drop\", \"reason\": \"<one sentence>\" }',\n '',\n '- \"keep\": the finding is proven at its stated severity.',\n '- \"downgrade\": a real concern, but the stated severity overstates a demonstrable impact (e.g. a CRITICAL whose failure path is not proven, or a WARN that is really a nit). Downgrade lowers it one tier.',\n '- \"drop\": not a real defect — speculative, stylistic, contradicted by the code/comments, or based on external state not visible in the diff.',\n ];\n // The diff and commit log are identical for every finding in a run, so they\n // live in the system prompt rather than the per-finding user message. The\n // provider caches the system prompt, so every verifier call in the run reads\n // the diff from cache instead of re-writing it behind each distinct finding —\n // on diff-heavy reviews this cuts the Verify stage's token cost substantially.\n if (commitLog?.trim()) {\n parts.push(\n '',\n `Commit messages for this change (oldest first):\\n<commits>\\n${commitLog.trim()}\\n</commits>`,\n );\n }\n parts.push('', `Verify each finding against this diff:\\n<diff>\\n${diff}\\n</diff>`);\n return parts.join('\\n');\n}\n\nexport function buildVerifyUserPrompt(comment: ReviewComment): string {\n return [\n '<finding>',\n `File: ${comment.file}:${comment.line} (${comment.side})`,\n `Severity: ${comment.severity.toUpperCase()} (confidence: ${comment.confidence})`,\n '',\n comment.body,\n '</finding>',\n '',\n 'Return the JSON verdict now.',\n ].join('\\n');\n}\n\n// --- Verdict parsing ------------------------------------------------------\n\nexport function parseVerdict(text: string): Verdict {\n const fenced = text.match(/```(?:json)?\\s*([\\s\\S]+?)\\s*```/);\n const candidate = fenced?.[1] ?? text;\n const objMatch = candidate.match(/\\{[\\s\\S]*\\}/);\n if (!objMatch) {\n return { decision: 'keep', reason: 'verifier output unparseable; finding kept' };\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(objMatch[0]);\n } catch {\n return { decision: 'keep', reason: 'verifier output invalid JSON; finding kept' };\n }\n if (!parsed || typeof parsed !== 'object') {\n return { decision: 'keep', reason: 'verifier output not an object; finding kept' };\n }\n const value = parsed as Record<string, unknown>;\n const decisionRaw = String(value.decision ?? '')\n .trim()\n .toLowerCase();\n const decision: VerifyDecision =\n decisionRaw === 'drop' ? 'drop' : decisionRaw === 'downgrade' ? 'downgrade' : 'keep';\n const reason = String(value.reason ?? '')\n .trim()\n .slice(0, 300);\n return { decision, reason: reason || 'no reason given' };\n}\n\n// --- Deterministic application -------------------------------------------\n\nexport function stepDownSeverity(severity: Severity): Severity {\n if (severity === 'critical') return 'warn';\n if (severity === 'warn') return 'info';\n return 'info';\n}\n\n/** The Conventional Comment label that matches each severity tier. */\nfunction headerForSeverity(severity: Severity): string {\n if (severity === 'critical') return 'issue (blocking)';\n if (severity === 'warn') return 'issue';\n return 'note';\n}\n\nconst HEADER_RE = /^(\\s*)([a-z]+(?:\\s+\\([^)]+\\))?):(\\s*)(.*)$/i;\n\n/**\n * Rewrite the leading Conventional Comment header of a comment body so its\n * label/decoration matches a new severity. Leaves the body untouched when the\n * first line is not a recognizable header (the severity field still changes —\n * the header is cosmetic).\n */\nexport function relabelBodyHeader(body: string, severity: Severity): string {\n const lines = body.split('\\n');\n const match = (lines[0] ?? '').match(HEADER_RE);\n if (!match) return body;\n const indent = match[1] ?? '';\n const space = match[3] || ' ';\n const subject = match[4] ?? '';\n lines[0] = `${indent}${headerForSeverity(severity)}:${space}${subject}`;\n return lines.join('\\n');\n}\n\n/**\n * Apply per-finding verdicts to the Find stage's comments. Comments without a\n * verdict (e.g. INFO findings that were never verified) pass through unchanged.\n * Returns the surviving comments plus an audit trail of every drop/downgrade.\n */\nexport function applyVerdicts(\n comments: ReviewComment[],\n verdicts: Map<number, Verdict>,\n): SynthesisResult {\n const out: ReviewComment[] = [];\n const audit: AuditEntry[] = [];\n\n comments.forEach((comment, index) => {\n const verdict = verdicts.get(index);\n if (!verdict || verdict.decision === 'keep') {\n out.push(comment);\n return;\n }\n if (verdict.decision === 'drop') {\n audit.push({\n file: comment.file,\n line: comment.line,\n action: 'dropped',\n fromSeverity: comment.severity,\n reason: verdict.reason,\n });\n return;\n }\n // downgrade\n const toSeverity = stepDownSeverity(comment.severity);\n if (toSeverity === comment.severity) {\n out.push(comment);\n return;\n }\n out.push({\n ...comment,\n severity: toSeverity,\n body: relabelBodyHeader(comment.body, toSeverity),\n });\n audit.push({\n file: comment.file,\n line: comment.line,\n action: 'downgraded',\n fromSeverity: comment.severity,\n toSeverity,\n reason: verdict.reason,\n });\n });\n\n return { comments: out, audit };\n}\n\n// --- Deterministic summary synthesis -------------------------------------\n\nfunction riskFor(comments: ReviewComment[]): 'Low' | 'Medium' | 'High' {\n if (comments.some((c) => c.severity === 'critical')) return 'High';\n if (comments.some((c) => c.severity === 'warn')) return 'Medium';\n return 'Low';\n}\n\nfunction riskSentence(level: 'Low' | 'Medium' | 'High'): string {\n if (level === 'High') return 'blocking issues remain — resolve them before merge.';\n if (level === 'Medium') return 'issues that should be addressed before merge.';\n return 'no blocking issues; safe to merge aside from nits.';\n}\n\nfunction parseHeader(body: string): { label: string; subject: string } {\n const first = (body.split('\\n', 1)[0] ?? '').trim();\n const match = first.match(/^([a-z]+(?:\\s+\\([^)]+\\))?):\\s*(.*)$/i);\n if (match) return { label: match[1] ?? '', subject: match[2] ?? '' };\n return { label: '', subject: first };\n}\n\nfunction issueBullet(comment: ReviewComment): string {\n const { label, subject } = parseHeader(comment.body);\n const loc = `\\`${comment.file}:${comment.line}\\``;\n return label ? `- **${label}** — ${loc} — ${subject}` : `- ${loc} — ${subject}`;\n}\n\nfunction extractOverview(summary: string | null): string {\n if (!summary) return '';\n const lines = summary.split('\\n');\n const riskIdx = lines.findIndex((l) => /^\\s*\\*\\*Risk:/i.test(l));\n const endIdx = lines.findIndex(\n (l, idx) => idx > riskIdx && (/^\\s*\\*\\*\\d+\\s+issue/i.test(l) || /^\\s*\\*\\*Notes:/i.test(l)),\n );\n const slice = lines.slice(riskIdx + 1, endIdx === -1 ? lines.length : endIdx);\n return slice.join('\\n').trim();\n}\n\nfunction extractNotes(summary: string | null): string[] {\n if (!summary) return [];\n const lines = summary.split('\\n');\n const notesIdx = lines.findIndex((l) => /^\\s*\\*\\*Notes:/i.test(l));\n if (notesIdx === -1) return [];\n return lines\n .slice(notesIdx + 1)\n .map((l) => l.trim())\n .filter((l) => l.startsWith('-'));\n}\n\n/**\n * Rebuild the review summary from the comments that survived Verify, preserving\n * the Find stage's prose overview and its own Notes (the context it applied,\n * e.g. an ADR/commit that suppressed a finding), and regenerating the risk line\n * and issues block from the surviving set.\n *\n * Verify's own drop/downgrade decisions are deliberately NOT surfaced here: a\n * finding the verifier refuted is a non-issue the developer never saw, so\n * echoing \"Verify removed a WARN at x:y — not a real defect\" only re-injects the\n * noise the verifier just removed. The drop/downgrade counts stay in the run log\n * for operators; see `result.audit` at the call site.\n *\n * Deterministic by design: the production Synthesize stage may instead write the\n * summary with an LLM, but the skateboard keeps it pure and testable so the\n * variable under test (Verify's decisions) is isolated from model variance.\n */\nexport function rebuildSummary(originalSummary: string | null, kept: ReviewComment[]): string {\n const level = riskFor(kept);\n const overview = extractOverview(originalSummary);\n const parts: string[] = [`**Risk: ${level}** — ${riskSentence(level)}`];\n\n if (overview) parts.push(overview);\n\n if (kept.length > 0) {\n const noun = kept.length === 1 ? 'issue' : 'issues';\n parts.push(`**${kept.length} ${noun} found:**\\n${kept.map(issueBullet).join('\\n')}`);\n }\n\n const noteLines = extractNotes(originalSummary);\n if (noteLines.length > 0) {\n parts.push(`**Notes:**\\n${noteLines.join('\\n')}`);\n }\n\n return parts.join('\\n\\n');\n}\n\n/**\n * Build the canonical `{ summary, comments }` JSON the Synthesize stage writes\n * to the review file. Shapes match what `parseReviewMarkdownWithWarnings`\n * consumes, so the parser, payload builder, and posting path are untouched.\n */\nexport function synthesizeReviewJson(\n originalSummary: string | null,\n result: SynthesisResult,\n): string {\n return JSON.stringify(\n {\n summary: rebuildSummary(originalSummary, result.comments),\n comments: result.comments,\n },\n null,\n 2,\n );\n}\n","import { jsonrepair } from 'jsonrepair';\nimport { FINGERPRINT_MARKER_PATTERN } from './fingerprints.js';\nimport { normalizeConfidence, normalizeSeverity, type ReviewComment, type Side } from './types.js';\nimport { relabelBodyHeader } from './verify.js';\n\n/** Why the reviewer's JSON could not be recovered. Surfaced for diagnostics. */\nexport type ParseFailureReason =\n /** A ```json fence was present but its contents could not be parsed or repaired. */\n | 'fence_unparseable'\n /** An unfenced reviewer-shaped object was present but could not be parsed or repaired. */\n | 'object_unparseable';\n\nexport interface ParseFailure {\n reason: ParseFailureReason;\n /** Whitespace-collapsed first ~200 chars of the offending block, for logs/OTel. */\n preview: string;\n}\n\nexport interface ParseResult {\n comments: ReviewComment[];\n summary: string | null;\n warnings: string[];\n /**\n * Set when the reviewer clearly intended to emit the `{ summary, comments }`\n * JSON object but it could not be parsed (even after a best-effort repair),\n * and nothing usable was recovered; `null` otherwise. The CLI fails loudly on\n * a non-null value rather than marking the job successful with an empty review.\n */\n malformed: ParseFailure | null;\n}\n\n/**\n * Anchors a reviewer JSON object on its first key (`{\"summary\"` / `{\"comments\"`).\n * Anchoring on the key — rather than scanning from every `{` — skips braces in\n * prose and in code spans (e.g. `` `{ entries }` ``) that would otherwise be\n * mistaken for the start of the object. Global so all anchors can be scanned.\n */\nconst REVIEWER_OBJECT_ANCHOR_RE = /\\{\\s*\"(?:summary|comments)\"\\s*:/g;\n\n/** Collapse whitespace and clip to a short, log-friendly preview. */\nfunction toPreview(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().slice(0, 200);\n}\n\nconst HEADER_RE = /^\\s*(?<file>.+):(?<line>\\d+)\\s+\\((?<side>LEFT|RIGHT)\\)\\s*$/u;\nconst GITHUB_STYLE_HEADER_RE =\n /^\\s*(?:\\*\\*)?`?(?<file>.+):(?<line>\\d+)`?(?:\\*\\*)?\\s*(?:[·-]|\\()\\s*(?<side>LEFT|RIGHT)\\)?\\s*$/u;\nconst FINGERPRINT_MARKER_RE = new RegExp(FINGERPRINT_MARKER_PATTERN, 'gi');\nconst JSON_COMMENT_MARKER_RE = /<!--\\s*gitlab-review-comment\\s*([\\s\\S]*?)-->/gi;\nconst JSON_FENCE_RE = /^```json[^\\S\\r\\n]*(?:\\r?\\n)([\\s\\S]*?)^```[^\\S\\r\\n]*$/gim;\nconst INLINE_SECTION_HEADER_RE = /^==\\s*Inline Comments\\s*==\\s*$/im;\nconst SECTION_BREAK_RE = /^==\\s*[^=].*==\\s*$/;\n\nfunction normalizeSide(value: unknown): Side {\n return String(value ?? '').toUpperCase() === 'LEFT' ? 'LEFT' : 'RIGHT';\n}\n\nfunction addJsonComment(out: ReviewComment[], item: unknown): void {\n if (!item || typeof item !== 'object') return;\n const value = item as Record<string, unknown>;\n const file = value.file ?? value.path ?? value.new_path ?? value.old_path;\n const rawLine = value.line ?? value.new_line ?? value.old_line;\n const line = Number(rawLine);\n const body = String(value.body ?? value.comment ?? value.message ?? '')\n .replace(FINGERPRINT_MARKER_RE, '')\n .trim();\n const side = normalizeSide(value.side ?? (value.old_line ? 'LEFT' : 'RIGHT'));\n if (\n typeof file === 'string' &&\n file.length > 0 &&\n Number.isInteger(line) &&\n line > 0 &&\n body.length > 0\n ) {\n // Enforce the severity/confidence contract deterministically: a CRITICAL\n // must be high confidence (the prompt states this, but self-reported\n // severity is otherwise accepted verbatim and inflates in the default\n // `single` depth, which runs no Verify pass). A CRITICAL that isn't\n // high-confidence is downgraded to WARN and its Conventional Comment header\n // relabelled to match, so an unproven \"blocking\" claim can't gate a merge.\n let severity = normalizeSeverity(value.severity);\n const confidence = normalizeConfidence(value.confidence);\n let finalBody = body;\n if (severity === 'critical' && confidence !== 'high') {\n severity = 'warn';\n finalBody = relabelBodyHeader(body, 'warn');\n }\n out.push({ file, line, side, severity, confidence, body: finalBody });\n }\n}\n\nfunction normalizeSummary(value: unknown): string | null {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\nfunction isReviewerShaped(parsed: unknown): parsed is Record<string, unknown> {\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return false;\n const value = parsed as Record<string, unknown>;\n return 'summary' in value || 'comments' in value;\n}\n\n/**\n * Pull comments and the summary out of a parsed reviewer value into `out`.\n * Accepts a `{ summary?, comments? }` object or a bare array of comments.\n * Returns `contributed: false` for anything else (e.g. an unrelated JSON object)\n * so callers can tell a real reviewer payload apart from incidental JSON.\n */\nfunction absorbReviewerValue(\n value: unknown,\n out: ReviewComment[],\n): { contributed: boolean; summary: string | null } {\n if (Array.isArray(value)) {\n for (const item of value) addJsonComment(out, item);\n return { contributed: true, summary: null };\n }\n if (isReviewerShaped(value)) {\n const list = Array.isArray(value.comments) ? value.comments : [];\n for (const item of list) addJsonComment(out, item);\n return { contributed: true, summary: normalizeSummary(value.summary) };\n }\n return { contributed: false, summary: null };\n}\n\n/**\n * Return the index of the `}` that balances the `{` at `start`, or -1 if the\n * braces never balance. Skips over string literals so braces inside JSON string\n * values do not break the balance count.\n */\nfunction findBalancedEnd(text: string, start: number): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n for (let i = start; i < text.length; i += 1) {\n const char = text[i];\n if (inString) {\n if (escaped) {\n escaped = false;\n } else if (char === '\\\\') {\n escaped = true;\n } else if (char === '\"') {\n inString = false;\n }\n continue;\n }\n if (char === '\"') {\n inString = true;\n } else if (char === '{') {\n depth += 1;\n } else if (char === '}') {\n depth -= 1;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/**\n * Locate a reviewer-shaped JSON object embedded anywhere in `markdown` (bare, or\n * surrounded by prose). Candidates are anchored on the reviewer key\n * (`{\"summary\"` / `{\"comments\"`) so braces in prose and code spans are skipped,\n * then balanced and run through the strict-then-repair parser — so a lightly\n * malformed unfenced object is recovered rather than dropped. Never throws.\n */\nfunction extractReviewerJsonObject(markdown: string): JsonParseOutcome | null {\n // Fast path: anchor on the reviewer key so braces in prose and code spans are\n // skipped outright.\n REVIEWER_OBJECT_ANCHOR_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = REVIEWER_OBJECT_ANCHOR_RE.exec(markdown)) !== null) {\n const outcome = tryReviewerObjectAt(markdown, match.index);\n if (outcome) return outcome;\n // Not parseable/reviewer-shaped from this anchor; try the next one.\n }\n // Fallback: scan from every `{` so a reviewer object whose first key is not\n // `summary`/`comments` (rare, but valid) is recovered rather than dropped.\n // Non-reviewer-shaped candidates (prose, code spans) fail the shape check and\n // are skipped, exactly as the anchored pass would have.\n for (let start = markdown.indexOf('{'); start !== -1; start = markdown.indexOf('{', start + 1)) {\n const outcome = tryReviewerObjectAt(markdown, start);\n if (outcome) return outcome;\n }\n return null;\n}\n\n/** Balance, parse (with repair), and shape-check a candidate object at `start`. */\nfunction tryReviewerObjectAt(markdown: string, start: number): JsonParseOutcome | null {\n const end = findBalancedEnd(markdown, start);\n if (end === -1) return null;\n const outcome = tryParseJson(markdown.slice(start, end + 1));\n return outcome && isReviewerShaped(outcome.value) ? outcome : null;\n}\n\ninterface JsonParseOutcome {\n value: unknown;\n /** True when strict `JSON.parse` failed and the value came from a repair pass. */\n repaired: boolean;\n}\n\n/**\n * Parse `text` as JSON, falling back to a best-effort repair pass that fixes\n * the common LLM serialization defects (trailing commas, lightly mis-escaped\n * quotes/newlines in string values). Returns null when neither strict parsing\n * nor repair yields valid JSON. Never throws.\n */\nfunction tryParseJson(text: string): JsonParseOutcome | null {\n try {\n return { value: JSON.parse(text), repaired: false };\n } catch {\n // Strict parsing failed; attempt a best-effort repair below.\n }\n try {\n return { value: JSON.parse(jsonrepair(text)), repaired: true };\n } catch {\n return null;\n }\n}\n\nfunction parseJsonComments(\n markdown: string,\n out: ReviewComment[],\n warnings: string[],\n): { summary: string | null; malformed: ParseFailure | null } {\n let summary: string | null = null;\n let recoveredReviewerJson = false;\n let fenceFailurePreview: string | null = null;\n let usedRepair = false;\n for (const match of markdown.matchAll(JSON_FENCE_RE)) {\n const fenceBody = match[1] ?? '';\n // An empty/whitespace fence carries nothing to parse — not a failure.\n if (fenceBody.trim().length === 0) continue;\n const outcome = tryParseJson(fenceBody);\n if (!outcome) {\n if (fenceFailurePreview === null) fenceFailurePreview = toPreview(fenceBody);\n continue;\n }\n const result = absorbReviewerValue(outcome.value, out);\n // A fence that parses to unrelated (non-reviewer-shaped) JSON must neither\n // count as a recovered review nor mask a malformed reviewer object below.\n if (!result.contributed) continue;\n recoveredReviewerJson = true;\n if (outcome.repaired) usedRepair = true;\n if (summary === null) summary = result.summary;\n }\n\n // Fallback: only when no reviewer-shaped fenced JSON was recovered, accept an\n // unfenced top-level reviewer object (bare, or appended after prose) so\n // unfenced model output is not silently dropped.\n let recoveredBare = false;\n if (!recoveredReviewerJson) {\n const outcome = extractReviewerJsonObject(markdown);\n if (outcome) {\n recoveredBare = true;\n if (outcome.repaired) usedRepair = true;\n const result = absorbReviewerValue(outcome.value, out);\n if (summary === null) summary = result.summary;\n }\n }\n\n for (const match of markdown.matchAll(JSON_COMMENT_MARKER_RE)) {\n const outcome = tryParseJson(match[1] ?? '');\n if (outcome) addJsonComment(out, outcome.value);\n }\n\n if (usedRepair) {\n warnings.push('Recovered a malformed reviewer JSON block via best-effort repair.');\n }\n\n // The reviewer clearly attempted a JSON object but nothing usable came out of\n // it — either a ```json fence failed to parse, or an unfenced reviewer object\n // is present yet unparseable. Report the failure (with a reason + preview) so\n // the CLI can fail rather than post an empty review.\n const recovered = recoveredReviewerJson || recoveredBare;\n return { summary, malformed: recovered ? null : detectFailure(markdown, fenceFailurePreview) };\n}\n\n/**\n * Classify why nothing was recovered. A failed ```json fence takes priority\n * (the model emitted a fenced object); otherwise look for an unfenced\n * reviewer-shaped anchor. Returns null when there was no JSON attempt at all\n * (a legitimately empty/prose-only review).\n */\nfunction detectFailure(markdown: string, fenceFailurePreview: string | null): ParseFailure | null {\n if (fenceFailurePreview !== null) {\n return { reason: 'fence_unparseable', preview: fenceFailurePreview };\n }\n REVIEWER_OBJECT_ANCHOR_RE.lastIndex = 0;\n const anchor = REVIEWER_OBJECT_ANCHOR_RE.exec(markdown);\n if (anchor) {\n // Clip the preview to the balanced object when possible so it points at the\n // offending JSON rather than spilling into unrelated trailing prose.\n const end = findBalancedEnd(markdown, anchor.index);\n const slice = markdown.slice(anchor.index, end === -1 ? undefined : end + 1);\n return { reason: 'object_unparseable', preview: toPreview(slice) };\n }\n return null;\n}\n\nfunction matchHeader(line: string): { file: string; line: number; side: Side } | null {\n const match = line.match(HEADER_RE) ?? line.match(GITHUB_STYLE_HEADER_RE);\n if (!match?.groups) return null;\n const number = Number(match.groups.line);\n if (!Number.isInteger(number) || number <= 0) return null;\n return {\n file: match.groups.file.trim().replace(/^`|`$/g, ''),\n line: number,\n side: match.groups.side as Side,\n };\n}\n\nfunction parseInlineSection(markdown: string, out: ReviewComment[], warnings: string[]): void {\n const marker = markdown.search(INLINE_SECTION_HEADER_RE);\n if (marker === -1) return;\n\n const section = markdown.slice(marker).split(/\\r?\\n/).slice(1);\n let current: {\n file: string;\n line: number;\n side: Side;\n body: string[];\n } | null = null;\n let sawBodyBeforeHeader = false;\n\n const flush = (): void => {\n if (!current) return;\n const body = current.body.join('\\n').replace(FINGERPRINT_MARKER_RE, '').trim();\n if (body.length > 0) {\n // Markdown inline comments carry no severity or confidence signal —\n // default to the lowest severity and high confidence.\n out.push({\n file: current.file,\n line: current.line,\n side: current.side,\n severity: 'info',\n confidence: 'high',\n body,\n });\n }\n current = null;\n };\n\n for (const rawLine of section) {\n if (SECTION_BREAK_RE.test(rawLine)) break;\n const header = matchHeader(rawLine);\n if (header) {\n flush();\n current = { ...header, body: [] };\n continue;\n }\n if (current) {\n current.body.push(rawLine);\n } else if (rawLine.trim().length > 0) {\n sawBodyBeforeHeader = true;\n }\n }\n flush();\n\n if (sawBodyBeforeHeader) {\n warnings.push(\n 'Ignored text in the inline comments section before the first parseable comment header.',\n );\n }\n}\n\nexport function parseReviewMarkdownWithWarnings(markdown: string): ParseResult {\n const comments: ReviewComment[] = [];\n const warnings: string[] = [];\n const { summary, malformed } = parseJsonComments(markdown, comments, warnings);\n parseInlineSection(markdown, comments, warnings);\n\n // A JSON block was unparseable, but other sections (legacy `== Inline\n // Comments ==` markdown or `<!-- gitlab-review-comment -->` markers) still\n // yielded a usable review. Keep it and downgrade the failure to a warning\n // rather than discarding a good review — backwards-compatibility with the\n // legacy reviewer formats takes priority over the strict JSON path.\n if (malformed && comments.length > 0) {\n warnings.push(\n `A reviewer JSON block was unparseable (${malformed.reason}), but ${comments.length} comment(s) were recovered from other sections; continuing.`,\n );\n return { comments, summary, warnings, malformed: null };\n }\n\n return { comments, summary, warnings, malformed };\n}\n\nexport function parseReviewMarkdown(markdown: string): ReviewComment[] {\n return parseReviewMarkdownWithWarnings(markdown).comments;\n}\n","import { FINGERPRINT_MARKER_PATTERN, normalizeBody } from './fingerprints.js';\nimport type { Discussion, DiscussionNote } from './gitlab.js';\n\nconst FINGERPRINT_MARKER_RE = new RegExp(FINGERPRINT_MARKER_PATTERN, 'i');\n\nexport interface PriorThread {\n file: string;\n line: number | null;\n resolved: boolean;\n /** Bot comment body with fingerprint markers and emoji prefixes stripped. */\n botComment: string;\n /** Human (non-system) reply bodies, in order. */\n replies: string[];\n}\n\n/**\n * Returns true when the note body contains a code-review fingerprint marker\n * (current or legacy prefix).\n * Used to identify notes posted by the bot without needing a getCurrentUser() call.\n */\nexport function isBotNote(note: DiscussionNote): boolean {\n return FINGERPRINT_MARKER_RE.test(note.body ?? '');\n}\n\n/**\n * Parses the `+++ b/<path>` lines from a unified diff and returns the set of\n * new file paths. `/dev/null` (deleted files) is excluded.\n */\nexport function extractChangedFiles(diff: string): Set<string> {\n const files = new Set<string>();\n for (const line of diff.split('\\n')) {\n const match = line.match(/^\\+\\+\\+ b\\/(.+)$/);\n if (match && match[1] !== '/dev/null') {\n files.add(match[1]);\n }\n }\n return files;\n}\n\n/**\n * Returns the line number for a discussion note's position.\n * Prefers the new-side line (`new_line`) then falls back to `old_line`.\n */\nfunction positionLine(note: DiscussionNote): number | null {\n return note.position?.new_line ?? note.position?.old_line ?? null;\n}\n\n/**\n * Returns the file path for a discussion note's position.\n * Prefers the new path then falls back to the old path.\n */\nfunction positionFile(note: DiscussionNote): string | null {\n return note.position?.new_path ?? note.position?.old_path ?? null;\n}\n\n/**\n * Extracts prior review threads from existing MR discussions that are relevant\n * to the current diff.\n *\n * A thread is included when:\n * - It contains at least one bot note (identified by fingerprint marker).\n * - It contains at least one non-system human reply after the bot note.\n * - The thread's file appears in `changedFiles`.\n *\n * Resolved threads are included but marked with `resolved: true` so the\n * reviewer can reference them without re-raising the concern.\n */\nexport function extractPriorThreads(\n discussions: Discussion[],\n changedFiles: Set<string>,\n): PriorThread[] {\n const threads: PriorThread[] = [];\n\n for (const discussion of discussions) {\n const notes = discussion.notes ?? [];\n\n // Find the first bot note in the discussion.\n const botNoteIndex = notes.findIndex(isBotNote);\n if (botNoteIndex === -1) continue;\n\n const botNote = notes[botNoteIndex];\n const file = positionFile(botNote);\n\n // Skip threads not on a changed file (they're irrelevant to this review).\n if (!file || !changedFiles.has(file)) continue;\n\n // Collect human replies that come after the bot note.\n const replies = notes\n .slice(botNoteIndex + 1)\n .filter((n) => !n.system && (n.body?.trim() ?? ''))\n .filter((n) => !isBotNote(n))\n .map((n) => n.body?.trim() ?? '');\n\n if (replies.length === 0) continue;\n\n // A thread is considered resolved if any note in it is resolved.\n const resolved = notes.some((n) => n.resolved === true);\n\n threads.push({\n file,\n line: positionLine(botNote),\n resolved,\n botComment: normalizeBody(botNote.body ?? ''),\n replies,\n });\n }\n\n return threads;\n}\n\n/**\n * Renders a `<prior_review_feedback>` XML block from a list of prior threads.\n * Returns an empty string when `threads` is empty.\n */\nexport function renderPriorThreadsBlock(threads: PriorThread[]): string {\n if (threads.length === 0) return '';\n\n const threadXml = threads.map((t) => {\n const attrs = [\n `file=\"${t.file}\"`,\n t.line !== null ? `line=\"${t.line}\"` : null,\n `resolved=\"${t.resolved}\"`,\n ]\n .filter(Boolean)\n .join(' ');\n\n const commentXml = ` <comment>${escapeXml(t.botComment)}</comment>`;\n const repliesXml = t.replies.map((r) => ` <reply>${escapeXml(r)}</reply>`).join('\\n');\n\n return ` <thread ${attrs}>\\n${commentXml}\\n${repliesXml}\\n </thread>`;\n });\n\n return `<prior_review_feedback>\\n${threadXml.join('\\n')}\\n</prior_review_feedback>`;\n}\n\nfunction escapeXml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n","import { createHash } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readdir, readFile, rename, rm } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parse as parseYaml } from 'yaml';\nimport { ConfigError } from './errors.js';\nimport { git } from './git.js';\n\nexport interface Skill {\n name: string;\n description: string;\n /** Absolute path to the SKILL.md file. Used to reference skill content in prompts. */\n filePath: string;\n rootDir: string;\n resourceDirs: string[];\n source: 'builtin' | 'project' | 'npm' | 'file' | 'git';\n}\n\n/**\n * A parsed skill spec descriptor. Produced by `parseSkillSpec`.\n *\n * - `builtin` — bare name resolved from the package's bundled `skills/` dir\n * - `npm` — package in `node_modules`, optionally with a sub-directory\n * - `file` — explicit filesystem path (relative or absolute)\n * - `git` — shallow git clone at a pinned ref, optionally with a sub-directory\n */\nexport type SkillSpec =\n | { protocol: 'builtin'; name: string }\n | { protocol: 'npm'; packageName: string; subpath: string }\n | { protocol: 'file'; path: string }\n | { protocol: 'git'; url: string; ref: string; subpath: string };\n\nconst SKILL_DIRS = ['.agents/skills', '.claude/skills'] as const;\nconst RESOURCE_DIRS = ['references'] as const;\n\nfunction parseFrontmatter(content: string): { name: string; description: string } | null {\n // Locate the leading `---` … `---` fence, then hand the inner block to a real\n // YAML parser rather than matching keys with regex. Invalid YAML (e.g. an\n // unquoted value containing `: `) yields null and the skill is skipped.\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n if (!match) return null;\n let data: unknown;\n try {\n data = parseYaml(match[1]);\n } catch {\n return null;\n }\n if (!data || typeof data !== 'object') return null;\n const { name, description } = data as Record<string, unknown>;\n if (typeof name !== 'string' || typeof description !== 'string') return null;\n const trimmedName = name.trim();\n const trimmedDescription = description.trim();\n if (!trimmedName || !trimmedDescription) return null;\n return { name: trimmedName, description: trimmedDescription };\n}\n\nexport async function loadSkillFromDir(\n dirPath: string,\n source: Skill['source'],\n): Promise<Skill | null> {\n const skillMdPath = join(dirPath, 'SKILL.md');\n let content: string;\n try {\n content = await readFile(skillMdPath, 'utf8');\n } catch {\n return null;\n }\n const parsed = parseFrontmatter(content);\n if (!parsed) return null;\n const resourceDirs = RESOURCE_DIRS.filter((d) => existsSync(join(dirPath, d)));\n return {\n name: parsed.name,\n description: parsed.description,\n filePath: skillMdPath,\n rootDir: dirPath,\n resourceDirs,\n source,\n };\n}\n\nexport function resolveBuiltinSkillsDir(): string {\n return join(dirname(fileURLToPath(import.meta.url)), '..', 'skills');\n}\n\nexport async function loadBuiltinSkill(name: string): Promise<Skill | null> {\n return loadSkillFromDir(join(resolveBuiltinSkillsDir(), name), 'builtin');\n}\n\nexport async function loadAutoDiscoveredSkills(\n cwd: string,\n gitRoot: string,\n warn?: (msg: string) => void,\n): Promise<Skill[]> {\n const dirs: string[] = [];\n let current = cwd;\n while (true) {\n dirs.unshift(current);\n if (current === gitRoot) break;\n const parent = dirname(current);\n if (parent === current) break;\n current = parent;\n }\n\n // Walk gitRoot → cwd so the last write wins (cwd-closest overrides ancestors)\n const found = new Map<string, Skill>();\n for (const dir of dirs) {\n for (const skillDir of SKILL_DIRS) {\n const skillsPath = join(dir, skillDir);\n let entries: string[];\n try {\n entries = await readdir(skillsPath);\n } catch {\n continue;\n }\n for (const entry of entries) {\n const entryPath = join(skillsPath, entry);\n const skill = await loadSkillFromDir(entryPath, 'project');\n if (skill) {\n found.set(skill.name, skill);\n } else if (warn && existsSync(join(entryPath, 'SKILL.md'))) {\n warn(\n `Skill at ${entryPath} has a SKILL.md but is missing required frontmatter fields (name, description) — skill not loaded.`,\n );\n }\n }\n }\n }\n\n return [...found.values()];\n}\n\n/**\n * Parse a skill spec string into a typed `SkillSpec` descriptor.\n *\n * Supported spec formats:\n *\n * | Input | Result |\n * |------------------------------------|---------------------------------------------------|\n * | `code-review` | `{ protocol: 'builtin', name: 'code-review' }` |\n * | `npm:my-skill` | `{ protocol: 'npm', packageName: 'my-skill', ... }`|\n * | `npm:@scope/pkg` | `{ protocol: 'npm', packageName: '@scope/pkg', ... }`|\n * | `npm:@scope/bundle/security` | `{ protocol: 'npm', packageName: '@scope/bundle', subpath: 'security' }`|\n * | `npm:bundle/security` | `{ protocol: 'npm', packageName: 'bundle', subpath: 'security' }`|\n * | `file:./path/to/skill` | `{ protocol: 'file', path: './path/to/skill' }` |\n * | `file:/absolute/path` | `{ protocol: 'file', path: '/absolute/path' }` |\n * | `git:https://host/org/s.git` | `{ protocol: 'git', url: 'https://host/org/s.git', ref: '', subpath: '' }` |\n * | `git:https://host/org/b.git#v1/sec`| `{ protocol: 'git', url: 'https://host/org/b.git', ref: 'v1', subpath: 'sec' }` |\n * | `git+ssh://git@host/org/s.git` | `{ protocol: 'git', url: 'ssh://git@host/org/s.git', ref: '', subpath: '' }` |\n */\nexport function parseSkillSpec(spec: string): SkillSpec {\n if (spec.startsWith('file:')) {\n return { protocol: 'file', path: spec.slice('file:'.length) };\n }\n\n if (spec.startsWith('npm:')) {\n const rest = spec.slice('npm:'.length);\n if (rest.startsWith('@')) {\n // Scoped package: @scope/pkg[/subpath...]\n const parts = rest.split('/');\n if (parts.length < 2) {\n // Malformed scoped spec — treat the whole thing as the package name\n return { protocol: 'npm', packageName: rest, subpath: '' };\n }\n const packageName = `${parts[0]}/${parts[1]}`;\n const subpath = parts.slice(2).join('/');\n return { protocol: 'npm', packageName, subpath };\n }\n // Unscoped package: pkg[/subpath...]\n const slashIdx = rest.indexOf('/');\n if (slashIdx === -1) {\n return { protocol: 'npm', packageName: rest, subpath: '' };\n }\n return {\n protocol: 'npm',\n packageName: rest.slice(0, slashIdx),\n subpath: rest.slice(slashIdx + 1),\n };\n }\n\n // git: and git+ssh: (and other git+<transport>:// forms)\n if (spec.startsWith('git+') || spec.startsWith('git:')) {\n return parseGitSpec(spec);\n }\n\n // Bare name → builtin\n return { protocol: 'builtin', name: spec };\n}\n\n/**\n * Parse a `git:` / `git+ssh:` skill spec into its URL, pinned ref, and subpath.\n *\n * - `git:<url>` strips the `git:` marker; what follows is the clone URL\n * (e.g. `git:https://host/org/repo.git`).\n * - `git+<transport>://…` strips the leading `git+`, leaving a URL git\n * understands directly (`git+ssh://git@host/…` → `ssh://git@host/…`), matching\n * npm's `package.json` git-dependency convention.\n *\n * An optional `#<ref>[/<subpath>]` fragment pins the ref (tag, branch, or\n * commit) and, after the first `/`, points at a skill directory inside the repo.\n */\nfunction parseGitSpec(spec: string): Extract<SkillSpec, { protocol: 'git' }> {\n const raw = spec.startsWith('git+') ? spec.slice('git+'.length) : spec.slice('git:'.length);\n\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n // Not a standard URL (e.g. scp-style `git@host:org/repo.git`, which is\n // intentionally unsupported to avoid `:`/`#` ambiguity). Hand the raw value\n // to git unchanged with no ref/subpath rather than rejecting outright.\n return { protocol: 'git', url: raw, ref: '', subpath: '' };\n }\n\n // The `#<ref>[/<subpath>]` fragment is our own convention layered on top of\n // the URL: the ref ends at the first `/`, the rest points at a skill dir.\n const fragment = parsed.hash ? parsed.hash.slice(1) : '';\n parsed.hash = '';\n const url = parsed.toString();\n const slashIdx = fragment.indexOf('/');\n if (slashIdx === -1) {\n return { protocol: 'git', url, ref: fragment, subpath: '' };\n }\n return {\n protocol: 'git',\n url,\n ref: fragment.slice(0, slashIdx),\n subpath: fragment.slice(slashIdx + 1),\n };\n}\n\n/**\n * Resolve the directory for an npm-installed skill by walking `node_modules`\n * upward from `cwd` (supports monorepo hoisting). Returns the resolved path\n * or `null` if the package / subpath cannot be found.\n */\nexport async function resolveNpmSkillDir(\n packageName: string,\n subpath: string,\n cwd: string,\n): Promise<string | null> {\n let current = cwd;\n while (true) {\n const candidate = subpath\n ? join(current, 'node_modules', packageName, subpath)\n : join(current, 'node_modules', packageName);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(current);\n if (parent === current) break;\n current = parent;\n }\n return null;\n}\n\n/** Base directory for cached git-skill clones (honours `XDG_CACHE_HOME`). */\nexport function resolveSkillCacheDir(): string {\n const base = process.env.XDG_CACHE_HOME?.trim() || join(homedir(), '.cache');\n return join(base, 'code-review', 'skills');\n}\n\n/**\n * Stable cache-directory name for a git skill. Keyed on the clone URL plus the\n * pinned ref so that two refs of the same repo never share a cache entry.\n */\nexport function gitSkillCacheKey(url: string, ref: string): string {\n return createHash('sha256').update(`${url}#${ref}`).digest('hex').slice(0, 16);\n}\n\n/**\n * Shallow-clone `url` at `ref` into `dir`. Using init + a single-ref fetch +\n * `checkout FETCH_HEAD` (rather than `clone --branch`) means a branch, tag, or\n * commit SHA all resolve through the same path; an empty `ref` fetches the\n * remote's default branch via `HEAD`.\n */\nasync function gitShallowClone(url: string, ref: string, dir: string): Promise<void> {\n await git(['init', '--quiet', dir]);\n await git(['remote', 'add', 'origin', url], { cwd: dir });\n await git(['fetch', '--depth', '1', '--quiet', 'origin', ref || 'HEAD'], { cwd: dir });\n await git(['checkout', '--quiet', 'FETCH_HEAD'], { cwd: dir });\n}\n\n/**\n * Resolve a git skill spec to a local clone directory, reusing the on-disk\n * cache when possible. The clone lands in a temp sibling first and is renamed\n * into place atomically, so a crashed or concurrent clone never leaves a\n * half-written cache entry. With `refresh`, any cached copy is discarded first.\n */\nasync function cloneGitSkill(\n url: string,\n ref: string,\n options: { cacheDir: string; refresh: boolean },\n): Promise<string> {\n const repoDir = join(options.cacheDir, gitSkillCacheKey(url, ref));\n\n if (options.refresh) {\n await rm(repoDir, { recursive: true, force: true });\n } else if (existsSync(join(repoDir, '.git'))) {\n return repoDir;\n } else if (existsSync(repoDir)) {\n // A leftover dir without `.git` is a partial/corrupt prior clone — drop it.\n await rm(repoDir, { recursive: true, force: true });\n }\n\n await mkdir(options.cacheDir, { recursive: true });\n const tmpDir = `${repoDir}.tmp-${process.pid}`;\n await rm(tmpDir, { recursive: true, force: true });\n try {\n await gitShallowClone(url, ref, tmpDir);\n try {\n await rename(tmpDir, repoDir);\n } catch (error) {\n // A concurrent clone won the race and populated `repoDir` first — reuse it.\n if (existsSync(join(repoDir, '.git'))) {\n await rm(tmpDir, { recursive: true, force: true });\n return repoDir;\n }\n throw error;\n }\n } catch (error) {\n await rm(tmpDir, { recursive: true, force: true });\n throw error;\n }\n return repoDir;\n}\n\n/** Options controlling how external skills are resolved. */\nexport interface LoadNamedSkillOptions {\n /** Override the git-skill clone cache directory (defaults to the XDG cache). */\n cacheDir?: string;\n /** Re-clone git skills even when a cached copy exists. */\n refresh?: boolean;\n}\n\n/**\n * Load a skill by its spec string (`code-review`, `npm:@scope/pkg`, `file:./path`,\n * `git:https://…`, …).\n *\n * Resolution order:\n * 1. `builtin` — package-bundled `skills/<name>/`\n * 2. `npm:` — `node_modules/<packageName>[/subpath]` walked up from `cwd`\n * 3. `file:` — direct filesystem path (relative paths resolved from `cwd`)\n * 4. `git:` / `git+ssh:` — shallow clone at the pinned ref, cached on disk,\n * loading `SKILL.md` from the repo root or the `#<ref>/<subpath>` directory\n *\n * Throws a `ConfigError` if the spec cannot be resolved or the resolved\n * directory does not contain a valid `SKILL.md`.\n */\nexport async function loadNamedSkill(\n spec: string,\n cwd: string,\n options: LoadNamedSkillOptions = {},\n): Promise<Skill> {\n const parsed = parseSkillSpec(spec);\n\n if (parsed.protocol === 'builtin') {\n const skill = await loadBuiltinSkill(parsed.name);\n if (!skill) {\n throw new ConfigError(`Cannot load skill: \"${spec}\"`, {\n hint: `No built-in skill named \"${parsed.name}\" was found. Check the skill name, or use npm: / file: to reference external skills.`,\n });\n }\n return skill;\n }\n\n if (parsed.protocol === 'npm') {\n const dir = await resolveNpmSkillDir(parsed.packageName, parsed.subpath, cwd);\n if (dir === null) {\n const pkgRef = parsed.subpath\n ? `${parsed.packageName} (subpath \"${parsed.subpath}\")`\n : parsed.packageName;\n throw new ConfigError(`Cannot load skill: \"${spec}\"`, {\n hint: `Package ${pkgRef} was not found in node_modules. Run \\`npm install ${parsed.packageName}\\` in the project.`,\n });\n }\n const skill = await loadSkillFromDir(dir, 'npm');\n if (!skill) {\n throw new ConfigError(`Cannot load skill: \"${spec}\"`, {\n hint: `The package at ${dir} does not contain a valid SKILL.md.`,\n });\n }\n return skill;\n }\n\n if (parsed.protocol === 'file') {\n const resolvedPath = parsed.path.startsWith('/') ? parsed.path : join(cwd, parsed.path);\n const skill = await loadSkillFromDir(resolvedPath, 'file');\n if (!skill) {\n throw new ConfigError(`Cannot load skill: \"${spec}\"`, {\n hint: `No valid SKILL.md was found at \"${resolvedPath}\". Check that the path points to a skill directory.`,\n });\n }\n return skill;\n }\n\n // git: / git+ssh: — shallow clone at a pinned ref, then load from the cache.\n let repoDir: string;\n try {\n repoDir = await cloneGitSkill(parsed.url, parsed.ref, {\n cacheDir: options.cacheDir ?? resolveSkillCacheDir(),\n refresh: options.refresh ?? false,\n });\n } catch (error) {\n const atRef = parsed.ref ? ` at ref \"${parsed.ref}\"` : '';\n throw new ConfigError(`Cannot load skill: \"${spec}\"`, {\n cause: error,\n hint: `Failed to clone \"${parsed.url}\"${atRef}. Check the URL, the ref, and your git credentials. For GitLab, prefer the SSH form: git+ssh://git@host/group/project.git`,\n });\n }\n\n const skillDir = parsed.subpath ? join(repoDir, parsed.subpath) : repoDir;\n const skill = await loadSkillFromDir(skillDir, 'git');\n if (!skill) {\n throw new ConfigError(`Cannot load skill: \"${spec}\"`, {\n hint: parsed.subpath\n ? `The cloned repository has no valid SKILL.md at subpath \"${parsed.subpath}\".`\n : 'The cloned repository has no valid SKILL.md at its root. If the skill lives in a subdirectory, point at it with \"#<ref>/<subpath>\".',\n });\n }\n return skill;\n}\n","import { mkdir, rm, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/** Directory (relative to cwd) where dropped-file diffs are staged for retrieval. */\nexport const SKIPPED_DIFF_DIR = '.code-review-skipped';\n\nexport interface SkippedDiffFile {\n /** Original repository path of the dropped file. */\n path: string;\n /** Path to the staged diff, relative to cwd — what the agent passes to its read tool. */\n diskPath: string;\n}\n\n/** Turn a repo path into a safe flat filename that keeps the original readable. */\nfunction slugify(path: string): string {\n return `${path.replace(/[^a-zA-Z0-9._-]/g, '__')}.diff`;\n}\n\n/**\n * Write each size-dropped file's diff to `<cwd>/.code-review-skipped/` so an\n * agentic reviewer can read the diffs it deems highest-risk on demand, instead\n * of the diffs being lost to the char budget. Returns the on-disk manifest.\n * Paths are staged under cwd because the reviewer's read tool is cwd-scoped.\n */\nexport async function writeSkippedDiffs(\n cwd: string,\n sections: Array<{ path: string; section: string }>,\n): Promise<SkippedDiffFile[]> {\n if (sections.length === 0) return [];\n const dir = join(cwd, SKIPPED_DIFF_DIR);\n await mkdir(dir, { recursive: true });\n const files: SkippedDiffFile[] = [];\n for (const { path, section } of sections) {\n const relative = join(SKIPPED_DIFF_DIR, slugify(path));\n await writeFile(join(cwd, relative), section, 'utf8');\n files.push({ path, diskPath: relative });\n }\n return files;\n}\n\n/** Remove the staged-diff directory. Safe to call when it was never created. */\nexport async function cleanupSkippedDiffs(cwd: string): Promise<void> {\n await rm(join(cwd, SKIPPED_DIFF_DIR), { recursive: true, force: true });\n}\n\n/**\n * Render the `<skipped_files>` block for the retrieval mode: each dropped file\n * with its on-disk diff path and an instruction to read the highest-risk ones.\n */\nexport function renderRetrievableSkippedBlock(files: SkippedDiffFile[]): string {\n const list = files.map((f) => `- ${f.path} → ${f.diskPath}`).join('\\n');\n return `<skipped_files>\\n${list}\\n</skipped_files>\\nThese files exceeded the inline size budget, so their diffs are NOT in the prompt above — but each is staged on disk at the path shown. Use your file-read tool to open the diffs most likely to contain defects (start with source files over config/tests) and review them as if they were inline. You may not have budget to read them all; say in your summary which you reviewed and which you did not.`;\n}\n","import type { Confidence, ReviewComment, Severity } from './types.js';\n\n/**\n * A Find \"angle\" — one lens a finder is specialised to. `full` depth runs one\n * finder per angle (concurrently) so each can go deep in its lane; Triage then\n * merges and deduplicates their findings.\n */\nexport interface ReviewAngle {\n key: string;\n directive: string;\n}\n\nexport const REVIEW_ANGLES: readonly ReviewAngle[] = [\n {\n key: 'correctness',\n directive:\n 'Focus on logic and control-flow correctness: inverted, too-broad, or too-narrow conditions; off-by-one and boundary errors; wrong defaults; branches that collapse distinct cases (0, false, \"\", null, undefined, missing); unreachable code; and edge cases (empty, first, last, duplicate, overflow, timezone). Trace the changed logic against its intended contract.',\n },\n {\n key: 'state-async-data',\n directive:\n 'Focus on state, concurrency, and data integrity: unawaited promises; race conditions and ordering bugs; cleanup in the wrong order; shared, mutable, or global state leaking across callers; cache key/scope mistakes; and runtime values that no longer match schemas, public types, API shapes, serialization, or persistence contracts.',\n },\n {\n key: 'failure-security',\n directive:\n 'Focus on failure handling and security: swallowed, converted, or partial errors that leave callers believing work succeeded; unsafe retries; missing or broken auth/permission checks; injection, SSRF, or path traversal; secret handling; and resource exhaustion (unbounded growth, missing limits) on reachable paths.',\n },\n];\n\nconst SEVERITY_RANK: Record<Severity, number> = { info: 0, warn: 1, critical: 2 };\nconst CONFIDENCE_RANK: Record<Confidence, number> = { low: 0, medium: 1, high: 2 };\n\n/**\n * A finding paired with the pool member that authored it. The author model is\n * internal pipeline metadata used to pick a cross-family verifier (a model other\n * than the one that raised the finding). It MUST NOT leak into posted comments,\n * fingerprints, or the summary — only `comment` is ever surfaced.\n */\nexport interface AuthoredFinding {\n comment: ReviewComment;\n authorModel: string;\n}\n\n/**\n * Normalise a comment's subject (the text after the Conventional Comment label\n * on the first line) for duplicate detection: lowercase, strip punctuation,\n * collapse whitespace.\n */\nfunction normalizeSubject(body: string): string {\n const first = (body.split('\\n', 1)[0] ?? '').toLowerCase();\n const subject = first.includes(':') ? first.slice(first.indexOf(':') + 1) : first;\n return subject\n .replace(/[^a-z0-9 ]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Max line distance at which two findings in the same file may be merged. */\nconst MAX_LINE_DELTA = 2;\n/** Min token-set Jaccard similarity of normalised subjects required to merge. */\nconst SUBJECT_SIMILARITY_THRESHOLD = 0.6;\n\nconst STOP_WORDS = new Set([\n 'a',\n 'an',\n 'the',\n 'is',\n 'are',\n 'in',\n 'on',\n 'of',\n 'to',\n 'and',\n 'or',\n 'not',\n 'this',\n 'that',\n 'it',\n 'its',\n 'be',\n 'by',\n 'for',\n]);\n\nfunction subjectTokens(subject: string): Set<string> {\n const tokens = subject.split(' ').filter((t) => t && !STOP_WORDS.has(t));\n // Fall back to the raw (non-stopword-filtered) tokens if filtering emptied the\n // set — a subject made entirely of stop words still needs something to compare.\n if (tokens.length === 0) return new Set(subject.split(' ').filter(Boolean));\n return new Set(tokens);\n}\n\n/** Token-set Jaccard similarity in [0, 1]; 1 when both sets are empty. */\nfunction jaccard(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 && b.size === 0) return 1;\n let intersection = 0;\n for (const token of a) if (b.has(token)) intersection += 1;\n const union = a.size + b.size - intersection;\n return union === 0 ? 1 : intersection / union;\n}\n\n/**\n * True when `candidate` should be merged into the cluster anchored by `anchor`:\n * same file, line within {@link MAX_LINE_DELTA}, and either an identical\n * normalised subject (exact dedup) or a subject token-set Jaccard at/above\n * {@link SUBJECT_SIMILARITY_THRESHOLD} (fuzzy dedup for heterogeneous phrasings).\n */\nfunction isSameFinding(anchor: NormalizedFinding, candidate: NormalizedFinding): boolean {\n if (anchor.comment.file !== candidate.comment.file) return false;\n if (Math.abs(anchor.comment.line - candidate.comment.line) > MAX_LINE_DELTA) return false;\n if (anchor.subject === candidate.subject) return true;\n return jaccard(anchor.tokens, candidate.tokens) >= SUBJECT_SIMILARITY_THRESHOLD;\n}\n\n/** A finding plus its precomputed normalised subject and subject token set. */\ninterface NormalizedFinding extends AuthoredFinding {\n subject: string;\n tokens: Set<string>;\n}\n\n/**\n * True when `candidate` outranks `incumbent` for survivorship within a cluster:\n * higher severity wins, ties broken by higher confidence. Equal rank keeps the\n * incumbent — and because the input is pre-sorted by a stable key, that keeps the\n * whole operation order-independent.\n */\nfunction outranks(candidate: NormalizedFinding, incumbent: NormalizedFinding): boolean {\n const candSev = SEVERITY_RANK[candidate.comment.severity];\n const incSev = SEVERITY_RANK[incumbent.comment.severity];\n if (candSev !== incSev) return candSev > incSev;\n return (\n CONFIDENCE_RANK[candidate.comment.confidence] > CONFIDENCE_RANK[incumbent.comment.confidence]\n );\n}\n\n/**\n * Stable ordering key for clustering. Sorting by it before clustering makes the\n * merge deterministic and independent of angle completion order: file, then\n * line, then severity (desc), then confidence (desc), then body. The first two\n * group co-located findings; the rest fix a canonical anchor per cluster.\n */\nfunction compareForClustering(a: NormalizedFinding, b: NormalizedFinding): number {\n if (a.comment.file !== b.comment.file) return a.comment.file < b.comment.file ? -1 : 1;\n if (a.comment.line !== b.comment.line) return a.comment.line - b.comment.line;\n const sevDiff = SEVERITY_RANK[b.comment.severity] - SEVERITY_RANK[a.comment.severity];\n if (sevDiff !== 0) return sevDiff;\n const confDiff = CONFIDENCE_RANK[b.comment.confidence] - CONFIDENCE_RANK[a.comment.confidence];\n if (confDiff !== 0) return confDiff;\n if (a.comment.body !== b.comment.body) return a.comment.body < b.comment.body ? -1 : 1;\n return 0;\n}\n\n/**\n * Merge findings from multiple angle finders into a deduplicated set.\n *\n * Two findings are considered the same when they share a file, sit within a few\n * lines of each other, and have either an identical or sufficiently similar\n * normalised subject — so heterogeneous models that phrase the same defect\n * differently collapse to one finding. The higher-severity copy wins (ties\n * broken by higher confidence), carrying its own author model forward, so a\n * finding one angle rates CRITICAL is not masked by another angle's WARN.\n *\n * Deterministic by construction: inputs are sorted by a stable key before\n * clustering, so the same set of findings always yields the same merged output\n * regardless of the order angles complete in.\n */\nexport function triageFindings(groups: AuthoredFinding[][]): AuthoredFinding[] {\n const normalized: NormalizedFinding[] = [];\n for (const group of groups) {\n for (const finding of group) {\n const subject = normalizeSubject(finding.comment.body);\n normalized.push({ ...finding, subject, tokens: subjectTokens(subject) });\n }\n }\n normalized.sort(compareForClustering);\n\n // Each cluster keeps a frozen proximity anchor line (the sort-stable first\n // line seen for that cluster). Survivorship may swap in a higher-ranked copy,\n // but the anchor line must NOT drift: otherwise a later finding out of range\n // of the original anchor could fall within range of the replacement and be\n // transitively over-merged across genuinely distinct findings.\n const survivors: NormalizedFinding[] = [];\n const anchorLines: number[] = [];\n for (const finding of normalized) {\n const clusterIndex = survivors.findIndex((survivor, i) =>\n isSameFinding(\n { ...survivor, comment: { ...survivor.comment, line: anchorLines[i] } },\n finding,\n ),\n );\n if (clusterIndex === -1) {\n survivors.push(finding);\n anchorLines.push(finding.comment.line);\n continue;\n }\n if (outranks(finding, survivors[clusterIndex])) {\n // Adopt the higher-ranked copy (severity/confidence/body/authorModel/line)\n // but leave the cluster's frozen proximity anchor untouched.\n survivors[clusterIndex] = finding;\n }\n }\n\n return survivors.map(({ comment, authorModel }) => ({ comment, authorModel }));\n}\n","import { execFile } from 'node:child_process';\nimport { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { promisify } from 'node:util';\nimport type { AgentEvent, AgentTool } from '@earendil-works/pi-agent-core';\nimport { Agent } from '@earendil-works/pi-agent-core';\nimport type { AssistantMessage, KnownProvider, Model } from '@earendil-works/pi-ai';\nimport { getModel } from '@earendil-works/pi-ai';\nimport { createReadOnlyTools } from '@earendil-works/pi-coding-agent';\nimport type { Config } from './config.js';\nimport { resolveProviderApiKey } from './config.js';\nimport { isQuotaExceededMessage, ReviewerError } from './errors.js';\nimport type { Logger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport { parseReviewMarkdownWithWarnings } from './parser.js';\nimport type { PriorThread } from './prior-threads.js';\nimport { renderPriorThreadsBlock } from './prior-threads.js';\nimport type { Skill } from './skills.js';\nimport { loadAutoDiscoveredSkills, loadNamedSkill } from './skills.js';\nimport {\n cleanupSkippedDiffs,\n renderRetrievableSkippedBlock,\n type SkippedDiffFile,\n writeSkippedDiffs,\n} from './skipped-retrieval.js';\nimport { REVIEW_ANGLES, triageFindings, type AuthoredFinding, type ReviewAngle } from './triage.js';\nimport type { GitLabReviewSeverity, SizeSkippedFile, ThinkingLevel } from './types.js';\nimport { splitModel, toGitLabReviewSeverity } from './types.js';\nimport {\n applyVerdicts,\n buildVerifySystemPrompt,\n buildVerifyUserPrompt,\n parseVerdict,\n synthesizeReviewJson,\n type Verdict,\n} from './verify.js';\n\nexport interface UsageBreakdown {\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n total: number;\n}\n\n/**\n * MR size signals derived from the reviewed diff: files dropped for exceeding\n * the char budget, and an optional \"this MR is too big — decompose it\" hint when\n * the reviewed changed-line count crosses the configured threshold.\n */\nexport interface ReviewSizeNotice {\n sizeSkippedFiles: SizeSkippedFile[];\n decomposeHint?: { lines: number; threshold: number };\n /**\n * Diff coverage when files were dropped for the char budget: how many changed\n * lines were actually reviewed vs the total. Present only when something was\n * size-skipped, so a partial review reports its coverage instead of reading as\n * a confident full review.\n */\n coverage?: { reviewedLines: number; totalLines: number };\n}\n\n/** Token and cost usage attributed to a single pool member. */\nexport interface ModelUsage {\n model: string;\n tokens: UsageBreakdown;\n cost: UsageBreakdown;\n}\n\nexport interface ReviewUsage {\n model: string;\n /** Reasoning effort the main agent ran at, surfaced alongside the model in the footer. */\n thinkingLevel: ThinkingLevel;\n tokens: UsageBreakdown;\n cost: UsageBreakdown;\n /**\n * Per-pool-member usage breakdown for heterogeneous `full`-depth runs. Each\n * entry attributes the tokens/cost of the agents that ran on that pool member.\n * The top-level `model`/`tokens`/`cost` stay the main model and the totals (the\n * sum of all entries). Present only when more than one distinct model ran; the\n * single-model path leaves it undefined so output is byte-identical to before.\n */\n byModel?: ModelUsage[];\n skills: string[];\n /**\n * Size signals for surfacing in the MR summary. `sizeSkippedFiles` lists files\n * dropped for the char budget; `decomposeHint` is set when the reviewed diff is\n * past the configured line threshold. Both feed the prominent summary callout.\n */\n sizeNotice: ReviewSizeNotice;\n}\n\nexport interface AgentLike {\n subscribe(listener: (event: AgentEvent) => void | Promise<void>): () => void;\n prompt(input: string): Promise<void>;\n}\n\nexport interface CreateAgentParams {\n systemPrompt: string;\n model: Model<string>;\n tools: AgentTool[];\n thinkingLevel: ThinkingLevel;\n getApiKey: () => Promise<string>;\n}\n\nexport type CreateAgent = (params: CreateAgentParams) => AgentLike;\n\nexport interface RunReviewOptions {\n cwd?: string;\n diff: string;\n /**\n * Commit messages for all non-merge commits in the MR (merge-base…HEAD),\n * in chronological order. When provided, a `<commits>` section is prepended\n * to the user prompt so the reviewer understands the intent behind each change.\n * Produced by `getMergeCommitLog` in `src/git.ts`.\n */\n commitLog?: string;\n createAgent?: CreateAgent;\n timeoutMs?: number;\n logger?: Logger;\n /**\n * Prior developer replies to bot-posted review threads on the MR.\n * When provided, a `<prior_review_feedback>` section is appended to the user\n * prompt after `<diff>` so the reviewer can avoid re-raising already-acknowledged\n * concerns and can provide contextual follow-up.\n * Produced by `extractPriorThreads` in `src/prior-threads.ts`.\n */\n priorThreads?: PriorThread[];\n /**\n * Author-declared intent for the MR (title + description). When present and\n * non-empty, an `<intent>` block is prepended to the user prompt so the\n * reviewer can check the diff against the stated purpose and flag code/intent\n * mismatches. A missing or empty description degrades gracefully (no block).\n * Sourced from the GitLab MR via `getMergeRequest` in `src/gitlab.ts`.\n */\n intent?: ReviewIntent;\n /**\n * Called with the agent after it is created, before the first prompt.\n * Use this to attach telemetry (e.g. `otelBridge.createAgentTelemetry(runId)`).\n * The returned function, if any, is called after the review completes.\n */\n attachTelemetry?: (agent: AgentLike) => (() => void) | undefined;\n}\n\nconst DEFAULT_REVIEW_TIMEOUT_MS = 10 * 60 * 1000;\n\ninterface ContextFile {\n path: string;\n content: string;\n}\n\ninterface ReviewContext {\n conventions: ContextFile[];\n reviewRules: ContextFile[];\n skills: Skill[];\n}\n\nconst DEFAULT_MAX_DIFF_CHARS = 100_000;\nconst CONVENTION_FILES = ['AGENTS.md', 'CLAUDE.md'];\nconst REVIEW_RULE_FILES = ['REVIEW.md'];\nconst CONFIG_DIRS = ['.pi', '.claude', '.agents'];\n\n// Files whose diffs carry no review signal — dependency lockfiles, generated\n// output, minified/compiled bundles, type declarations. They are filtered out\n// before the size budget so real source is never crowded out by machine-written\n// churn. Detection is layered rather than a flat path allowlist: a name list\n// alone let `composer.lock` and Shopify's `web/assets/theme.js` slip through and\n// consume ~80% of a real MR's budget. The layers, cheapest first:\n// 1. path patterns — well-known generated locations and suffixes,\n// 2. lockfile basenames — matched anywhere (monorepo lockfiles nest),\n// 3. content heuristics — generated banners and minified blobs, which catch\n// compiled assets regardless of what they are named.\n\n// Directory/suffix patterns. Directory patterns match at any depth (`(^|/)`) so\n// a nested `packages/x/dist/…` is caught, not just a root-level `dist/`.\nconst NOISE_PATH_PATTERNS: RegExp[] = [\n /^gitlab-review\\.md$/,\n /(^|\\/)\\.yarn\\//,\n /(^|\\/)(dist|build|out|coverage|node_modules|\\.next)\\//,\n /\\.min\\.(js|css)$/,\n /\\.generated\\.(ts|js)$/,\n /\\.d\\.ts$/,\n /\\.(js|css)\\.map$/,\n];\n\n// Dependency lockfiles across ecosystems, matched by basename (case-insensitive)\n// so they are skipped wherever they live in the tree.\nconst LOCKFILE_BASENAMES = new Set([\n 'package-lock.json',\n 'npm-shrinkwrap.json',\n 'yarn.lock',\n 'pnpm-lock.yaml',\n 'bun.lockb',\n 'bun.lock',\n 'composer.lock',\n 'gemfile.lock',\n 'cargo.lock',\n 'poetry.lock',\n 'pipfile.lock',\n 'go.sum',\n 'packages.lock.json',\n 'flake.lock',\n 'podfile.lock',\n 'mix.lock',\n 'pubspec.lock',\n 'gradle.lockfile',\n 'deno.lock',\n 'uv.lock',\n]);\n\n// A single added line this long is effectively never hand-written source: it\n// signals a minified bundle or an embedded/compiled asset (e.g. a Shopify\n// `theme.js`). Catching it by shape means we do not need to enumerate every\n// possible name a build tool might emit.\nconst MINIFIED_LINE_THRESHOLD = 2000;\n\n// High-precision generated-file banners. Each requires generation context (not a\n// bare \"do not edit\") so a source comment does not misclassify real code, and is\n// matched against added lines only so prose that merely mentions codegen is safe.\nconst GENERATED_CONTENT_MARKERS: RegExp[] = [\n /@(?:auto-?)?generated\\b/i,\n /\\bcode generated by\\b/i,\n /this file (?:is|was) (?:auto[- ]?generated|generated by)/i,\n /\\bdo not edit\\b[^\\n]*\\b(?:auto-?)?generated\\b/i,\n /\\b(?:auto-?)?generated\\b[^\\n]*\\bdo not edit\\b/i,\n];\n\nconst SEVERITY_RULE: Record<GitLabReviewSeverity, string | null> = {\n INFO: null,\n WARN: '- Only report CRITICAL and WARN issues — skip INFO',\n CRITICAL: '- Only report CRITICAL issues — skip WARN and INFO',\n};\n\nconst exec = promisify(execFile);\n\nfunction defaultCreateAgent(params: CreateAgentParams): AgentLike {\n return new Agent({\n initialState: {\n systemPrompt: params.systemPrompt,\n model: params.model,\n tools: params.tools,\n thinkingLevel: params.thinkingLevel,\n },\n getApiKey: params.getApiKey,\n });\n}\n\nasync function findGitRoot(cwd: string): Promise<string> {\n try {\n const { stdout } = await exec('git', ['rev-parse', '--show-toplevel'], { cwd });\n return stdout.trim() || cwd;\n } catch {\n return cwd;\n }\n}\n\nasync function readFirstMatch(dir: string, filenames: string[]): Promise<ContextFile | null> {\n for (const candidate of [dir, ...CONFIG_DIRS.map((d) => join(dir, d))]) {\n let entries: string[];\n try {\n entries = await readdir(candidate);\n } catch {\n continue;\n }\n const wanted = new Set(filenames.map((f) => f.toLowerCase()));\n const match = entries.find((entry) => wanted.has(entry.toLowerCase()));\n if (!match) continue;\n const fullPath = join(candidate, match);\n try {\n const content = await readFile(fullPath, 'utf8');\n return { path: fullPath, content };\n } catch {\n continue;\n }\n }\n return null;\n}\n\nasync function walkUpContextFiles(\n cwd: string,\n filenames: string[],\n gitRoot: string,\n): Promise<ContextFile[]> {\n const dirs: string[] = [];\n let current = cwd;\n while (true) {\n dirs.unshift(current);\n if (current === gitRoot) break;\n const parent = dirname(current);\n if (parent === current) break;\n current = parent;\n }\n\n const result: ContextFile[] = [];\n const seen = new Set<string>();\n for (const dir of dirs) {\n const file = await readFirstMatch(dir, filenames);\n if (!file || seen.has(file.path)) continue;\n seen.add(file.path);\n result.push({ path: relative(cwd, file.path) || file.path, content: file.content });\n }\n return result;\n}\n\nexport interface LoadReviewContextOptions {\n /** Re-clone `git:` / `git+ssh:` skills, bypassing the on-disk clone cache. */\n refreshGitSkills?: boolean;\n}\n\nexport async function loadReviewContext(\n cwd: string,\n skillNames: string[] = [],\n warn?: (msg: string) => void,\n options: LoadReviewContextOptions = {},\n): Promise<ReviewContext> {\n const gitRoot = await findGitRoot(cwd);\n const [conventions, reviewRules, discovered] = await Promise.all([\n walkUpContextFiles(cwd, CONVENTION_FILES, gitRoot),\n walkUpContextFiles(cwd, REVIEW_RULE_FILES, gitRoot),\n loadAutoDiscoveredSkills(cwd, gitRoot, warn),\n ]);\n\n const skills = [...discovered];\n const discoveredNames = new Set(discovered.map((s) => s.name));\n const named = await Promise.all(\n skillNames\n .filter((n) => !discoveredNames.has(n))\n .map((n) => loadNamedSkill(n, cwd, { refresh: options.refreshGitSkills })),\n );\n skills.push(...named);\n\n return { conventions, reviewRules, skills };\n}\n\nexport interface FilteredDiff {\n diff: string;\n /** Files dropped because they matched a noise pattern (lockfiles, generated, build output). */\n noiseSkippedFiles: string[];\n /** Files dropped because including them would exceed the char budget, with their diff size. */\n sizeSkippedFiles: SizeSkippedFile[];\n /** Number of added/removed lines in the reviewed (included) diff. */\n reviewedChangedLines: number;\n /** Number of added/removed lines in files dropped for the char budget. */\n skippedChangedLines: number;\n /**\n * The raw diff text of each size-dropped file, keyed by path. Lets the caller\n * write them to disk so an agentic reviewer can read the dropped diffs on\n * demand instead of losing them entirely (opt-in retrieval).\n */\n sizeSkippedSections: Array<{ path: string; section: string }>;\n}\n\nfunction parseFilePath(header: string): string | null {\n const match = header.match(/^diff --git a\\/.+ b\\/(.+)$/);\n return match?.[1] ?? null;\n}\n\nfunction basename(filePath: string): string {\n const slash = filePath.lastIndexOf('/');\n return slash === -1 ? filePath : filePath.slice(slash + 1);\n}\n\n/**\n * Changed lines of a diff section, leading `+`/`-` stripped and the file headers\n * (`+++`/`---`) excluded. `onlyAdded` restricts to additions — used for the\n * generated-banner check, which is about what the change introduces; the blob\n * check scans both sides so an edit to an existing minified file is still caught.\n */\nfunction changedLines(diffSection: string, onlyAdded: boolean): string[] {\n const out: string[] = [];\n for (const line of diffSection.split('\\n')) {\n if (line.startsWith('+++') || line.startsWith('---')) continue;\n if (line.startsWith('+')) out.push(line.slice(1));\n else if (!onlyAdded && line.startsWith('-')) out.push(line.slice(1));\n }\n return out;\n}\n\n/**\n * Classify a file as review-noise from its path and diff content. The content\n * layers (minified blobs, generated banners) only run once the path layers miss,\n * and only inspect changed lines, so unchanged context never triggers a skip.\n */\nfunction isNoise(filePath: string, diffSection: string): boolean {\n if (NOISE_PATH_PATTERNS.some((re) => re.test(filePath))) return true;\n if (LOCKFILE_BASENAMES.has(basename(filePath).toLowerCase())) return true;\n if (changedLines(diffSection, false).some((line) => line.length > MINIFIED_LINE_THRESHOLD)) {\n return true;\n }\n if (\n changedLines(diffSection, true).some((line) =>\n GENERATED_CONTENT_MARKERS.some((re) => re.test(line)),\n )\n ) {\n return true;\n }\n return false;\n}\n\nfunction countChangedLines(diffSection: string): number {\n let count = 0;\n for (const line of diffSection.split('\\n')) {\n if (line.startsWith('+++') || line.startsWith('---')) continue;\n if (line.startsWith('+') || line.startsWith('-')) count += 1;\n }\n return count;\n}\n\n/** Added lines only (excluding the `+++` header) — the review-worthiness signal. */\nfunction countAddedLines(diffSection: string): number {\n let count = 0;\n for (const line of diffSection.split('\\n')) {\n if (line.startsWith('+++')) continue;\n if (line.startsWith('+')) count += 1;\n }\n return count;\n}\n\nexport function filterDiff(raw: string, maxChars = DEFAULT_MAX_DIFF_CHARS): FilteredDiff {\n const sections = raw.split(/(?=^diff --git )/m).filter((section) => section.trim());\n const kept: string[] = [];\n const noiseSkippedFiles: string[] = [];\n\n for (const section of sections) {\n const firstLine = section.split('\\n', 1)[0] ?? '';\n const filePath = parseFilePath(firstLine);\n if (filePath && isNoise(filePath, section)) {\n noiseSkippedFiles.push(filePath);\n } else {\n kept.push(section);\n }\n }\n\n // Rank-before-drop: only when the budget will actually truncate. Under budget,\n // the original diff order is preserved so the common case is byte-identical.\n // When we must drop, spend the budget on the most review-worthy files first\n // (most added lines) instead of whatever happens to sort early in the diff.\n const totalKeptChars = kept.reduce((total, section) => total + section.length, 0);\n const ordered =\n totalKeptChars <= maxChars\n ? kept\n : kept.toSorted((a, b) => countAddedLines(b) - countAddedLines(a));\n\n const included: string[] = [];\n const sizeSkippedFiles: SizeSkippedFile[] = [];\n const sizeSkippedSections: Array<{ path: string; section: string }> = [];\n let totalChars = 0;\n let reviewedChangedLines = 0;\n let skippedChangedLines = 0;\n for (const section of ordered) {\n const changedLines = countChangedLines(section);\n if (totalChars + section.length > maxChars) {\n const firstLine = section.split('\\n', 1)[0] ?? '';\n const filePath = parseFilePath(firstLine);\n if (filePath) {\n sizeSkippedFiles.push({ path: filePath, chars: section.length, changedLines });\n sizeSkippedSections.push({ path: filePath, section });\n }\n skippedChangedLines += changedLines;\n continue;\n }\n included.push(section);\n totalChars += section.length;\n reviewedChangedLines += changedLines;\n }\n\n return {\n diff: included.join(''),\n noiseSkippedFiles,\n sizeSkippedFiles,\n reviewedChangedLines,\n skippedChangedLines,\n sizeSkippedSections,\n };\n}\n\nfunction mergeContent(files: ContextFile[]): string {\n return files.map((file) => file.content).join('\\n\\n');\n}\n\nfunction buildSkillSection(skill: Skill): string {\n const lines = [\n `<skill name=\"${skill.name}\">`,\n `<description>${skill.description}</description>`,\n `<skill_file>${skill.filePath}</skill_file>`,\n ];\n if (skill.resourceDirs.length > 0) {\n const dirList = skill.resourceDirs.map((d) => `${d}/`).join(', ');\n lines.push(\n '',\n '<skill_resources>',\n `This skill is located at: ${skill.rootDir}`,\n `You can read files from ${dirList} using the Read tool with the full path.`,\n '</skill_resources>',\n );\n }\n lines.push('</skill>');\n return lines.join('\\n');\n}\n\nfunction buildSharedBase(minSeverity: GitLabReviewSeverity): string[] {\n const rule = SEVERITY_RULE[minSeverity];\n const today = new Date().toISOString().slice(0, 10);\n return [\n `You are a code reviewer. Review the following PR diff carefully. Today's date is ${today}.`,\n '',\n '<severity_tiers>',\n 'Severity reflects the IMPACT of the defect if it occurs. It is independent of how certain you are the code is wrong (that is `confidence`, see below).',\n '',\n '- CRITICAL: runtime failure, data loss or corruption, security vulnerability, broken auth, or production outage. Affects users, persistence, money, or availability.',\n '- WARN: logic error, dropped error, type-unsafe access, or contract break that produces wrong behaviour but does not rise to runtime failure or data loss.',\n '- INFO: nits, style, naming, hints, suggestions, questions. Things that are not concrete defects.',\n '</severity_tiers>',\n '',\n '<confidence_tiers>',\n 'Confidence reflects how certain you are that the code is actually wrong. It is independent of severity (impact).',\n '',\n '- high: the defect is demonstrable from the diff alone. You can name the failing input or the exact line, and the violated contract is visible in the diff, the surrounding code, or referenced docs/tests.',\n '- medium: a defect is likely but depends on assumptions about caller behaviour, external state, or runtime context not fully visible in the diff.',\n '- low: a defect is plausible but you cannot prove it from the diff alone — you are reporting a smell or pattern that usually indicates a bug but might be intentional here.',\n '</confidence_tiers>',\n '',\n '<severity_confidence_interaction>',\n '- A CRITICAL finding MUST be high confidence. If you cannot prove the failure path from the diff, either downgrade severity (WARN/INFO) or downgrade confidence and re-evaluate severity.',\n '- A WARN finding at low confidence SHOULD be re-classified as INFO unless the impact is severe enough that even a chance is worth flagging.',\n '- When a commit message, prior thread reply, or in-file ADR/incident reference justifies a pattern that would otherwise be CRITICAL or WARN, do not raise it as severe — surface it in the summary Notes section instead.',\n '- Silence beats fabrication: a confident wrong CRITICAL is worse than a missed bug.',\n '</severity_confidence_interaction>',\n '',\n '<rules>',\n '- Only flag what is actually wrong in the diff — no hypotheticals',\n '- Before reporting a runtime failure (crash, null/undefined dereference, unhandled case, missing check), re-read the function entry and the lines adjacent to your target: if a guard, early return, default value, optional chaining, or a type already prevents that failure, do NOT report it. \"It crashes when X\" only stands when X is reachable past the guards visible in the code — e.g. do not claim a value is dereferenced unchecked when the function opens with `if (!value) return;`.',\n '- If nothing is wrong, say so clearly',\n '- Do not make claims about external state (dates, library versions, deprecation status, API availability) that cannot be verified from the diff itself',\n '- A finding that asserts something about the literal text of the code — a typo, a misspelled or wrong identifier, a missing or duplicated character, wrong casing — MUST quote the offending token verbatim and only stands if that exact token appears in the diff character-for-character. Re-read the line before reporting: if the spelling you claim is correct already matches the code, the finding is fabricated — drop it.',\n '- Write declaratively. Avoid \"consider\", \"might want to\", \"could potentially\", \"you may want to\" in issue and suggestion subjects. State the defect and the fix directly. If unsure it is wrong, omit it. (The question and thought labels are inherently tentative and exempt.)',\n '- The summary lists findings by their Conventional Comment subject only; it MUST NOT repeat the discussion, impact (\"why it matters\"), or suggested fix from any inline comment',\n '- Cross-cutting content (suppressed findings, unreviewed files, overall verdict) goes in the summary, never in inline comments',\n '- When a commit message, prior thread reply, or in-file ADR/incident reference suppresses what would otherwise be a CRITICAL or WARN finding, you MUST add a one-line bullet to the summary Notes section naming the file:line, the pattern, and the context that suppressed it (e.g. \"src/probe.ts:13 — empty .catch() suppressed per ADR-042 / INC-2891\"). Silent suppression is not acceptable: the developer must be able to audit what context you applied.',\n \"- An `<intent>` block, when present, is the author's declared purpose (MR title/description) — a lens for reading the diff, NOT a review target. Finding demonstrable code defects is the job; intent is secondary and must never displace or outrank it. Concretely: NEVER raise an inline comment on a README/description/doc line just because it promises behaviour the diff does not implement, and NEVER let an unmet or exceeded promise be a CRITICAL or blocking finding — code defects alone set severity and the risk line. If the change omits something the description promised, or does something it never claimed (scope creep), note it in ONE line of the summary overview or Notes so the author is aware; do not manufacture inline findings for it. A terse description is not a mismatch, and the description is never proof the code is right.\",\n ...(rule ? [rule] : []),\n '</rules>',\n ];\n}\n\nexport function buildJSONSystemPrompt(\n context: ReviewContext,\n minSeverity: GitLabReviewSeverity,\n): string {\n const base = [\n ...buildSharedBase(minSeverity),\n '- Do not repeat what the project conventions already enforce',\n '',\n 'Return only a JSON object matching this schema exactly (no markdown fences, no extra text, no extra fields — do not include the diff or any other field):',\n '<output_format>',\n '{',\n ' \"summary\": \"Overall review in **Markdown**, following the <summary_skeleton> below.\",',\n ' \"comments\": [',\n ' { \"file\": \"src/auth.ts\", \"line\": 42, \"side\": \"RIGHT\", \"severity\": \"CRITICAL\", \"confidence\": \"high\", \"body\": \"issue (blocking): <subject>\\\\n\\\\n<discussion>\" }',\n ' ]',\n '}',\n '</output_format>',\n '',\n 'The output MUST be valid JSON: the \"summary\" and \"body\" fields carry Markdown (quotes, backticks, code, newlines), so every double quote inside a string value must be escaped as \\\\\", every backslash as \\\\\\\\, and every newline as \\\\n. A single unescaped quote makes the entire review unparseable and is discarded.',\n '',\n 'Field rules:',\n '- summary: overall review written in Markdown, following <summary_skeleton>',\n '- comments: inline comments attached to specific diff lines (may be empty [])',\n '- file: relative path from repo root',\n '- line: line number in the file (not the diff position)',\n '- side: \"RIGHT\" for added/context lines, \"LEFT\" for removed lines',\n '- severity: \"CRITICAL\" | \"WARN\" | \"INFO\" — the IMPACT tier from <severity_tiers>',\n '- confidence: \"high\" | \"medium\" | \"low\" — your CERTAINTY the code is wrong, from <confidence_tiers>. Required on every comment.',\n '- body: a Conventional Comment, see <comment_format>',\n '',\n '<comment_format>',\n 'Each comment body is a Conventional Comment (https://conventionalcomments.org/) with this shape:',\n '',\n ' <label> [decoration]: <Subject — short, action-oriented, 5-10 words>',\n '',\n ' <Discussion: 1-2 sentences stating the concrete defect and observable impact, then the suggested fix. Prefer a fenced ```suggestion``` block when the fix is a small edit on the line(s) being commented; otherwise use prose or a ```diff``` block.>',\n '',\n 'Allowed labels: issue, suggestion, nitpick, question, todo, chore, note, thought',\n 'Allowed decorations: (blocking), (non-blocking), (if-minor)',\n 'Do NOT emit \"praise:\" comments — out of scope for this reviewer.',\n '',\n 'Label and decoration must match the severity field:',\n '- CRITICAL → \"issue (blocking): ...\"',\n '- WARN → \"issue: ...\" (no decoration; an unmarked issue is implicitly blocking per the spec)',\n '- INFO → choose the fitting label: \"nitpick: ...\", \"suggestion (non-blocking): ...\", \"note: ...\", \"question: ...\", or \"thought: ...\"',\n '</comment_format>',\n '',\n '<summary_skeleton>',\n 'The summary is rendered under a fixed \"### Code Review\" heading so every review looks the same and is easy to scan. Write the summary content in this EXACT order. The risk line and the overview are ALWAYS present; the issues block and the notes block appear only when they have content.',\n '',\n ' **Risk: <Low | Medium | High>** — <one sentence: the impact of merging this MR and how it should be handled. Low = no blocking issues, safe to merge aside from nits. Medium = wrong behaviour or missed cases that should be fixed before merge. High = data loss, security, broken auth, or a critical-path crash — do not merge until resolved. Anchor the level to the most severe finding.>',\n '',\n ' <2-3 sentence plain-prose overview of what the MR does. ALWAYS present, including on a clean review.>',\n '',\n ' **<N> issue(s) found:**',\n ' - **<label>** — `file:line` — <subject>',\n ' <One bullet per inline comment. Show only the subject (the text after the comment label); never restate the discussion, impact, or fix — those live in the inline comment. Omit this entire block when there are no inline comments.>',\n '',\n ' **Notes:**',\n ' <Only when there is something to surface: suppressed CRITICAL/WARN findings (one bullet each — file:line, the pattern, and the commit/ADR/prior-thread that justified leaving it un-flagged) and any unreviewed/skipped files. Omit this entire block when there is nothing to note.>',\n '</summary_skeleton>',\n '',\n '<example>',\n 'Example output for a diff that introduces one real bug and one style nit:',\n '```json',\n '{',\n ' \"summary\": \"**Risk: High** — The retry loop overcharges customers on the free-tier path; do not merge until the off-by-one is fixed.\\\\n\\\\nAdds a checkout retry helper used by the cart route, with one blocking off-by-one and one naming nit.\\\\n\\\\n**2 issues found:**\\\\n- **issue (blocking)** — `src/cart/retry.ts:42` — Loop runs N+1 attempts on first call\\\\n- **nitpick** — `src/cart/retry.ts:8` — Helper name shadows the `Retry` type\",',\n ' \"comments\": [',\n ' {',\n ' \"file\": \"src/cart/retry.ts\",',\n ' \"line\": 42,',\n ' \"side\": \"RIGHT\",',\n ' \"severity\": \"CRITICAL\",',\n ' \"confidence\": \"high\",',\n ' \"body\": \"issue (blocking): Loop runs N+1 attempts on first call\\\\n\\\\nThe `attempt <= maxAttempts` predicate executes the body one extra time when `maxAttempts === 0`, which is the configured value for the free-tier path. The first call therefore charges the customer twice on a 5xx response.\\\\n\\\\n```suggestion\\\\nwhile (attempt < maxAttempts) {\\\\n```\"',\n ' },',\n ' {',\n ' \"file\": \"src/cart/retry.ts\",',\n ' \"line\": 8,',\n ' \"side\": \"RIGHT\",',\n ' \"severity\": \"INFO\",',\n ' \"confidence\": \"high\",',\n ' \"body\": \"nitpick: Helper name shadows the `Retry` type\\\\n\\\\nNaming the local `Retry` shadows the imported `Retry` type from `./types.ts`. Rename to `runWithRetry`.\"',\n ' }',\n ' ]',\n '}',\n '```',\n '</example>',\n ].join('\\n');\n\n const sections = [base];\n const conventions = mergeContent(context.conventions).trim();\n if (conventions) sections.push(`<conventions>\\n${conventions}\\n</conventions>`);\n const reviewRules = mergeContent(context.reviewRules).trim();\n if (reviewRules) sections.push(`<review_rules>\\n${reviewRules}\\n</review_rules>`);\n if (context.skills.length > 0) {\n const preamble = [\n 'Read each skill file before applying it. Skills are mandatory rule sets — the actual review criteria live in the SKILL.md body, not in the one-line description below.',\n '',\n 'For every skill listed below, you MUST:',\n ' 1. Call the Read tool with the path in <skill_file> to load the SKILL.md content. Example: Read({ file_path: \"/abs/path/to/skills/code-review/SKILL.md\" }).',\n ' 2. If the skill lists <skill_resources>, Read the references relevant to the languages or frameworks present in this diff (skip references that do not match the diff).',\n \" 3. Apply the skill's criteria when forming and grading findings.\",\n '',\n 'A skill loaded but never read is a no-op — the description alone is not enough to apply the rules correctly.',\n ].join('\\n');\n const skillSections = context.skills.map(buildSkillSection).join('\\n\\n');\n sections.push(`<skills>\\n${preamble}\\n\\n${skillSections}\\n</skills>`);\n }\n return sections.join('\\n\\n');\n}\n\n/**\n * Author-declared intent for the change, sourced from the GitLab MR.\n * Both fields are optional and may be empty/whitespace — the renderer degrades\n * gracefully and emits no intent block when there is nothing meaningful to show.\n */\nexport interface ReviewIntent {\n title?: string;\n description?: string | null;\n}\n\n/** Max characters of MR description injected into the prompt to bound token cost. */\nconst MAX_INTENT_DESCRIPTION_CHARS = 4_000;\n\n/**\n * Renders the author-declared intent (MR title + description) as a clearly\n * delimited `<intent>` block. Returns an empty string when neither field has\n * meaningful content, so a missing/empty description degrades gracefully.\n * The description is trimmed and length-capped to bound token cost.\n */\nfunction renderIntentBlock(intent: ReviewIntent | undefined): string {\n if (!intent) return '';\n const title = intent.title?.trim() ?? '';\n let description = intent.description?.trim() ?? '';\n if (!title && !description) return '';\n\n if (description.length > MAX_INTENT_DESCRIPTION_CHARS) {\n description = `${description.slice(0, MAX_INTENT_DESCRIPTION_CHARS)}\\n… (description truncated)`;\n }\n\n const lines = ['<intent>'];\n if (title) lines.push(`<title>${title}</title>`);\n if (description) lines.push(`<description>\\n${description}\\n</description>`);\n lines.push('</intent>');\n return lines.join('\\n');\n}\n\n/**\n * Build a Find system prompt specialised to one review angle. Used by `full`\n * depth, which runs one finder per angle. The base prompt (severity/confidence\n * tiers, output format, skills, conventions) is unchanged; an `<review_angle>`\n * section narrows the finder to its lane so the finders cover breadth rather\n * than all re-finding the same top issue.\n */\nexport function buildAngleSystemPrompt(\n context: ReviewContext,\n minSeverity: GitLabReviewSeverity,\n angle: ReviewAngle,\n): string {\n return [\n buildJSONSystemPrompt(context, minSeverity),\n '',\n '<review_angle>',\n `You are ONE of several reviewers working in parallel, each assigned a different angle. Your assigned angle is \"${angle.key}\".`,\n angle.directive,\n 'Report ONLY findings that fall within your angle — other reviewers cover the rest, so do not stray into their scope or duplicate it. If your angle surfaces nothing, return an empty comments array. The severity and confidence bars from the base instructions still apply.',\n '</review_angle>',\n ].join('\\n');\n}\n\nexport function buildUserPrompt(\n diff: string,\n skippedFiles: string[] = [],\n commitLog?: string,\n priorThreads?: PriorThread[],\n intent?: ReviewIntent,\n coverage?: { reviewedLines: number; totalLines: number },\n retrievableSkipped?: SkippedDiffFile[],\n): string {\n const parts: string[] = [];\n const intentBlock = renderIntentBlock(intent);\n if (intentBlock) {\n parts.push(\n `The author described the purpose of this change below. Use it as context for reading the diff. If the code omits something promised or adds something never claimed, note it in one line of the summary — do not raise inline findings on the description text itself:\\n${intentBlock}`,\n );\n }\n if (commitLog?.trim()) {\n parts.push(`Commits in this MR (oldest first):\\n<commits>\\n${commitLog.trim()}\\n</commits>`);\n }\n parts.push(`Review this diff:\\n<diff>\\n${diff}\\n</diff>`);\n if (retrievableSkipped && retrievableSkipped.length > 0) {\n // Retrieval mode: size-dropped diffs are staged on disk for the agent to read.\n parts.push(renderRetrievableSkippedBlock(retrievableSkipped));\n } else if (skippedFiles.length > 0) {\n parts.push(\n `<skipped_files>\\n${skippedFiles\n .map((file) => `- ${file}`)\n .join(\n '\\n',\n )}\\n</skipped_files>\\nThe above files were not included because the diff exceeded the size limit. Mention them explicitly in your summary as not reviewed.`,\n );\n }\n if (coverage && coverage.totalLines > 0 && coverage.reviewedLines < coverage.totalLines) {\n const pct = Math.round((coverage.reviewedLines / coverage.totalLines) * 100);\n parts.push(\n `<coverage>You reviewed ${coverage.reviewedLines} of ${coverage.totalLines} changed lines (~${pct}%). The rest were dropped for the size budget and you did NOT see them. State this partial coverage in your summary and do not imply the unreviewed files are clean — their absence from your findings is not a clearance.</coverage>`,\n );\n }\n if (priorThreads && priorThreads.length > 0) {\n const block = renderPriorThreadsBlock(priorThreads);\n if (block) {\n parts.push(\n `The following threads were posted by a previous review run and have received developer replies. Use this context to avoid repeating already-acknowledged concerns and to provide informed follow-up:\\n${block}`,\n );\n }\n }\n return parts.join('\\n\\n');\n}\n\nfunction extractAssistantText(message: AssistantMessage): string {\n return message.content\n .map((part) => (part.type === 'text' ? part.text : ''))\n .join('')\n .trim();\n}\n\nexport function extractLastAssistantText(messages: AssistantMessage[]): string {\n for (let i = messages.length - 1; i >= 0; i -= 1) {\n const text = extractAssistantText(messages[i]);\n if (text) return text;\n }\n return '';\n}\n\n/**\n * Build a Model object for an Ollama-hosted model.\n *\n * Ollama exposes an OpenAI-compatible `/v1` endpoint, so we use the\n * `openai-completions` API adapter. The `baseUrl` is taken from the\n * already-resolved config (derived from `OLLAMA_HOST` or `CODE_REVIEW_BASE_URL`).\n * Cost is zero — Ollama runs locally.\n */\nfunction buildOllamaModel(\n modelId: string,\n baseUrl: string,\n maxTokens: number,\n): Model<'openai-completions'> {\n const effectiveMaxTokens = maxTokens > 0 ? maxTokens : 4096;\n // Use a generous context window default. Ollama model context sizes vary\n // widely and can only be known by querying the server at runtime. We set a\n // large constant so the agent doesn't truncate context unnecessarily; the\n // model itself will cap actual generation at its own limit.\n const contextWindow = 131072;\n return {\n id: modelId,\n name: modelId,\n api: 'openai-completions' as const,\n provider: 'ollama',\n baseUrl,\n reasoning: false,\n input: ['text' as const],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow,\n maxTokens: effectiveMaxTokens,\n };\n}\n\n/**\n * Resolve a model string into a pi-ai `Model` object.\n *\n * The model string must be `\"provider/modelId\"` where `modelId` may itself\n * contain slashes for providers like OpenRouter (`openrouter/anthropic/claude-3-opus`).\n * Splitting is always done on the **first** slash only.\n *\n * Special providers:\n * - `ollama`: builds a local OpenAI-compatible model with the given `baseUrl`.\n *\n * @param modelString - Full model string, e.g. `\"anthropic/claude-sonnet-4-5\"`.\n * @param baseUrl - Custom base URL override (used for Ollama or generic endpoints).\n * @param maxTokens - Max output tokens; 0 means use the model's default.\n */\nfunction resolveModel(modelString: string, baseUrl: string, maxTokens: number): Model<string> {\n const { provider, modelId } = splitModel(modelString);\n if (provider === undefined || modelId === undefined) {\n throw new ReviewerError(\n `Invalid model format \"${modelString}\". Expected \"provider/modelId\" (e.g. \"anthropic/claude-sonnet-4-5\").`,\n );\n }\n\n // Built-in Ollama support via the OpenAI-compatible API.\n if (provider === 'ollama') {\n const effectiveBase = baseUrl || 'http://localhost:11434/v1';\n return buildOllamaModel(modelId, effectiveBase, maxTokens);\n }\n\n const model = getModel(provider as KnownProvider, modelId as never) as Model<string> | undefined;\n if (!model) {\n throw new ReviewerError(`Unknown model \"${modelString}\".`, {\n hint: `Check that \"${provider}\" is a valid provider and \"${modelId}\" is a registered model ID.`,\n });\n }\n\n // Apply overrides when provided.\n // - baseUrl: redirect to a custom OpenAI-compatible endpoint.\n // - maxTokens: cap output tokens (0 keeps the model's registered default).\n if (baseUrl || maxTokens > 0) {\n return {\n ...model,\n ...(baseUrl ? { baseUrl } : {}),\n ...(maxTokens > 0 ? { maxTokens } : {}),\n };\n }\n return model;\n}\n\n/**\n * One usable model in the review pool. `id` is the original `provider/modelId`\n * string — the stable identity used for fixed angle→model mapping, cross-family\n * verifier selection, and per-model usage keying. `getApiKey` resolves THIS\n * member's provider key, so a key for provider X is never sent to provider Y.\n */\nexport interface PoolMember {\n id: string;\n model: Model<string>;\n getApiKey: () => Promise<string>;\n}\n\n/**\n * Build the effective model pool from `config.model` plus `config.modelPool`,\n * resolving each member's own provider key and dropping (with a warning) any\n * member whose key is missing/empty. Order and duplicates from `config.modelPool`\n * are preserved by first occurrence. When the pool is empty or every member is\n * unusable, falls back to a single-member pool of `config.model` (already\n * validated to have a key) — reproducing single-model behaviour exactly.\n *\n * `config.apiKey` (which honours `--api-key`) is used for any member whose id\n * equals `config.model`, so an explicit override key still applies; other members\n * resolve their key via the provider-aware resolver.\n */\nexport function buildEffectivePool(config: Config, logger: Logger): PoolMember[] {\n const ids = config.modelPool.length > 0 ? config.modelPool : [config.model];\n const seen = new Set<string>();\n const members: PoolMember[] = [];\n\n for (const id of ids) {\n if (seen.has(id)) continue;\n seen.add(id);\n\n let model: Model<string>;\n try {\n model = resolveModel(id, config.baseUrl ?? '', config.maxTokens ?? 0);\n } catch (error) {\n logger.warn(`Model pool: dropping \"${id}\" — ${(error as Error).message}`);\n continue;\n }\n\n // The configured main model already resolved its key into config.apiKey\n // (honouring --api-key); other members resolve their provider key directly.\n const key = id === config.model ? config.apiKey : resolveProviderApiKey(id);\n if (!key) {\n logger.warn(\n `Model pool: dropping \"${id}\" — no API key found for its provider; set the provider's key env var to use it.`,\n );\n continue;\n }\n\n members.push({ id, model, getApiKey: async () => key });\n }\n\n if (members.length === 0) {\n // Every configured member was unusable — fall back to the validated main\n // model so the run still proceeds (single-model behaviour).\n const model = resolveModel(config.model, config.baseUrl ?? '', config.maxTokens ?? 0);\n return [{ id: config.model, model, getApiKey: async () => config.apiKey }];\n }\n\n return members;\n}\n\n/** Blended per-token cost (input + output) used to compare model tiers. */\nexport function blendedCost(model: Model<string>): number {\n const cost = model.cost;\n if (!cost) return 0;\n return (cost.input ?? 0) + (cost.output ?? 0);\n}\n\n/**\n * Resolve `config.verifyModel` into a dedicated Verify-stage pool member. Returns\n * null when unset (Verify falls back to the pool's cross-family pick) or when the\n * model/key can't be resolved (warns and falls back, so a bad value never aborts\n * the run). Also warns when the verify model is a *cheaper* tier than the finder:\n * the Verify stage is a precision-judgment task, and a weak verifier drops real\n * findings (recall loss) — so cheap-find/strong-verify is the intended shape.\n */\nexport function resolveVerifyMember(\n config: Config,\n primary: PoolMember,\n logger: Logger,\n): PoolMember | null {\n const id = config.verifyModel?.trim();\n if (!id) return null;\n if (id === primary.id) return null; // same as finder — nothing to route\n let model: Model<string>;\n try {\n model = resolveModel(id, config.baseUrl ?? '', config.maxTokens ?? 0);\n } catch (error) {\n logger.warn(`Ignoring --verify-model \"${id}\": ${(error as Error).message}`);\n return null;\n }\n const key = resolveProviderApiKey(id);\n if (!key) {\n logger.warn(\n `Ignoring --verify-model \"${id}\": no API key for its provider. ` +\n `Set the provider's key (e.g. ANTHROPIC_API_KEY) to route Verify to it.`,\n );\n return null;\n }\n const verifyCost = blendedCost(model);\n const findCost = blendedCost(primary.model);\n if (verifyCost > 0 && findCost > 0 && verifyCost < findCost) {\n logger.warn(\n `--verify-model \"${id}\" looks cheaper than the find model \"${primary.id}\". ` +\n `Verify is a precision-judgment task; a weaker verifier tends to drop real ` +\n `findings (recall loss). Prefer a cheap finder with a strong verifier.`,\n );\n }\n logger.info(`Verify stage routed to ${id} (find: ${primary.id}).`);\n return { id, model, getApiKey: async () => key };\n}\n\ninterface ModelUsageBucket {\n tokens: UsageBreakdown;\n cost: UsageBreakdown;\n}\n\ninterface AggregatedUsage {\n tokens: UsageBreakdown;\n cost: UsageBreakdown;\n /** Per-pool-member buckets, keyed by the member's `provider/modelId` id. */\n byModel: Map<string, ModelUsageBucket>;\n}\n\nfunction emptyBucket(): ModelUsageBucket {\n return {\n tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n };\n}\n\nfunction emptyUsage(): AggregatedUsage {\n return { ...emptyBucket(), byModel: new Map() };\n}\n\nfunction addUsageToBucket(bucket: ModelUsageBucket, message: AssistantMessage): void {\n const usage = message.usage;\n if (!usage) return;\n bucket.tokens.input += usage.input;\n bucket.tokens.output += usage.output;\n bucket.tokens.cacheRead += usage.cacheRead;\n bucket.tokens.cacheWrite += usage.cacheWrite;\n bucket.tokens.total += usage.totalTokens;\n if (usage.cost) {\n bucket.cost.input += usage.cost.input;\n bucket.cost.output += usage.cost.output;\n bucket.cost.cacheRead += usage.cost.cacheRead;\n bucket.cost.cacheWrite += usage.cost.cacheWrite;\n bucket.cost.total += usage.cost.total;\n }\n}\n\n/**\n * Add an assistant message's usage to the global totals and, when a pool member\n * id is given, to that member's per-model bucket. `modelId` is the member's\n * `provider/modelId` string — never a key or any secret.\n */\nfunction accumulateUsage(\n target: AggregatedUsage,\n message: AssistantMessage,\n modelId?: string,\n): void {\n if (!message.usage) return;\n addUsageToBucket(target, message);\n if (modelId) {\n let bucket = target.byModel.get(modelId);\n if (!bucket) {\n bucket = emptyBucket();\n target.byModel.set(modelId, bucket);\n }\n addUsageToBucket(bucket, message);\n }\n}\n\nexport async function runReview(config: Config, options: RunReviewOptions): Promise<ReviewUsage> {\n const cwd = options.cwd ?? config.cwd;\n const minSeverity = toGitLabReviewSeverity(config.minSeverity);\n const logger = options.logger ?? noopLogger;\n\n const maxDiffChars = config.maxDiffChars > 0 ? config.maxDiffChars : DEFAULT_MAX_DIFF_CHARS;\n const {\n diff,\n noiseSkippedFiles,\n sizeSkippedFiles,\n reviewedChangedLines,\n skippedChangedLines,\n sizeSkippedSections,\n } = filterDiff(options.diff, maxDiffChars);\n if (!diff.trim()) {\n throw new ReviewerError('No reviewable diff content after filtering noise files.', {\n hint: 'Ensure the merge request introduces changes outside of generated/lock files.',\n });\n }\n\n // The agent still needs to know which files went unreviewed (size + noise), so\n // it can mention them; the prominent split/decompose callout is surfaced\n // separately in the MR summary via `sizeNotice`.\n const skippedFiles = [...sizeSkippedFiles.map((f) => f.path), ...noiseSkippedFiles];\n\n const decomposeHint =\n config.decomposeHintLines > 0 && reviewedChangedLines > config.decomposeHintLines\n ? { lines: reviewedChangedLines, threshold: config.decomposeHintLines }\n : undefined;\n // Coverage is only meaningful when the budget actually dropped files.\n const coverage =\n sizeSkippedFiles.length > 0\n ? {\n reviewedLines: reviewedChangedLines,\n totalLines: reviewedChangedLines + skippedChangedLines,\n }\n : undefined;\n const sizeNotice: ReviewSizeNotice = { sizeSkippedFiles, decomposeHint, coverage };\n\n // Retrieval mode (opt-in): stage dropped-file diffs on disk so the agent can\n // read the ones it deems risky instead of losing them to the char budget.\n const retrievableSkipped =\n config.retrieveSkipped && sizeSkippedSections.length > 0\n ? await writeSkippedDiffs(cwd, sizeSkippedSections)\n : ([] as SkippedDiffFile[]);\n if (retrievableSkipped.length > 0) {\n logger.info(`Staged ${retrievableSkipped.length} dropped-file diff(s) on disk for retrieval.`);\n }\n\n const context = await loadReviewContext(cwd, config.skills, (msg) => logger.warn(msg), {\n refreshGitSkills: config.refreshGitSkills,\n });\n const systemPrompt = buildJSONSystemPrompt(context, minSeverity);\n const userPrompt = buildUserPrompt(\n diff,\n skippedFiles,\n options.commitLog,\n options.priorThreads,\n options.intent,\n coverage,\n retrievableSkipped,\n );\n\n const skillNames = context.skills.map((s) => s.name);\n if (skillNames.length > 0) {\n logger.debug(`Skills loaded: ${skillNames.join(', ')}`);\n }\n if (context.conventions.length > 0) {\n logger.debug(`Conventions: ${context.conventions.map((f) => f.path).join(', ')}`);\n }\n if (context.reviewRules.length > 0) {\n logger.debug(`Review rules: ${context.reviewRules.map((f) => f.path).join(', ')}`);\n }\n\n const pool = buildEffectivePool(config, logger);\n if (pool.length > 1) {\n logger.info(`Model pool: ${pool.map((m) => m.id).join(', ')}.`);\n }\n const primary = pool[0];\n const tools = createReadOnlyTools(cwd) as AgentTool[];\n\n const createAgent = options.createAgent ?? defaultCreateAgent;\n const timeoutMs = options.timeoutMs ?? DEFAULT_REVIEW_TIMEOUT_MS;\n const aggregated = emptyUsage();\n const deps: StageDeps = {\n createAgent,\n pool,\n tools,\n thinkingLevel: config.thinkingLevel,\n timeoutMs,\n logger,\n aggregated,\n verifyMember: resolveVerifyMember(config, primary, logger),\n };\n\n let outputText: string;\n\n if (config.reviewDepth === 'full') {\n // --- Multi-angle Find → Triage → Verify → Synthesize.\n const { findings, summary } = await runMultiAngleFind(context, minSeverity, userPrompt, deps);\n outputText = await verifyAndSynthesize(findings, summary, diff, options.commitLog, deps);\n } else {\n // --- single / verify: one Find pass on the primary model. In `single` depth\n // its output is written verbatim, byte-identical to legacy runs.\n const findAgent = createAgent({\n systemPrompt,\n model: primary.model,\n tools,\n thinkingLevel: config.thinkingLevel,\n getApiKey: primary.getApiKey,\n });\n\n // Attach telemetry before the first prompt so all events fire.\n const detachTelemetry = options.attachTelemetry?.(findAgent);\n let turnCount = 0;\n let toolCallCount = 0;\n let finalText: string;\n try {\n finalText = await runAgentToCompletion(findAgent, userPrompt, {\n timeoutMs,\n onAssistantMessage: (message) => accumulateUsage(aggregated, message, primary.id),\n onTurnStart: (turn) => {\n turnCount = turn;\n logger.debug(`Turn ${turn} started`);\n },\n onToolStart: (toolName, args) => {\n toolCallCount += 1;\n logger.debug(` → ${toolName}${formatToolArgs(toolName, args)}`);\n },\n });\n } finally {\n detachTelemetry?.();\n }\n logger.debug(`Agent finished: ${turnCount} turn(s), ${toolCallCount} tool call(s)`);\n\n outputText =\n config.reviewDepth === 'verify'\n ? await runVerifyStage(finalText, diff, options.commitLog, deps)\n : finalText;\n }\n\n const reviewPath = resolve(cwd, config.reviewFile);\n await mkdir(dirname(reviewPath), { recursive: true });\n await writeFile(reviewPath, outputText, 'utf8');\n\n // Remove the staged dropped-file diffs now the agent is done reading them.\n if (retrievableSkipped.length > 0) await cleanupSkippedDiffs(cwd);\n\n return {\n model: config.model,\n thinkingLevel: config.thinkingLevel,\n tokens: aggregated.tokens,\n cost: aggregated.cost,\n byModel: buildByModelUsage(aggregated),\n skills: context.skills.map((s) => s.name),\n sizeNotice,\n };\n}\n\n/**\n * Convert the per-model usage buckets into the public {@link ModelUsage} array,\n * sorted by model id for deterministic output. Returns `undefined` when fewer\n * than two distinct models ran, so single-model runs stay byte-identical.\n */\nfunction buildByModelUsage(aggregated: AggregatedUsage): ModelUsage[] | undefined {\n if (aggregated.byModel.size < 2) return undefined;\n return [...aggregated.byModel.entries()]\n .map(([model, bucket]) => ({ model, tokens: bucket.tokens, cost: bucket.cost }))\n .toSorted((a, b) => (a.model < b.model ? -1 : a.model > b.model ? 1 : 0));\n}\n\ninterface RunAgentCallbacks {\n timeoutMs: number;\n onAssistantMessage?: (message: AssistantMessage) => void;\n onTurnStart?: (turn: number) => void;\n onToolStart?: (toolName: string, args: unknown) => void;\n}\n\n/**\n * Drive an agent through a single prompt to completion and return its final\n * assistant text. Shared by the Find pass and each Verify agent so the\n * subscribe/timeout/error handling lives in one place.\n */\nasync function runAgentToCompletion(\n agent: AgentLike,\n userPrompt: string,\n callbacks: RunAgentCallbacks,\n): Promise<string> {\n const collected: AssistantMessage[] = [];\n let turnCount = 0;\n let unsubscribe: (() => void) | undefined;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let finalText = '';\n try {\n const ended = new Promise<void>((resolvePromise, rejectPromise) => {\n unsubscribe = agent.subscribe(async (event) => {\n if (event.type === 'turn_start') {\n turnCount += 1;\n callbacks.onTurnStart?.(turnCount);\n }\n if (event.type === 'tool_execution_start') {\n callbacks.onToolStart?.(event.toolName, event.args);\n }\n if (event.type === 'message_end' && event.message.role === 'assistant') {\n const assistant = event.message as AssistantMessage;\n collected.push(assistant);\n callbacks.onAssistantMessage?.(assistant);\n }\n if (event.type !== 'agent_end') return;\n const messages = event.messages.filter(\n (message): message is AssistantMessage => message.role === 'assistant',\n );\n const last = messages[messages.length - 1];\n if (last?.stopReason === 'error' || last?.errorMessage) {\n const message = last.errorMessage ?? 'unknown error';\n const quotaExceeded = isQuotaExceededMessage(message);\n rejectPromise(\n new ReviewerError(`Agent failed: ${message}`, {\n quotaExceeded,\n hint: quotaExceeded\n ? 'The model provider reported exhausted credits/quota. Top up the provider account or switch --model.'\n : undefined,\n }),\n );\n return;\n }\n finalText = extractLastAssistantText(collected.length > 0 ? collected : messages);\n if (!finalText) {\n rejectPromise(new ReviewerError('Agent returned an empty response.'));\n return;\n }\n resolvePromise();\n });\n });\n\n const timeout = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(\n () =>\n reject(\n new ReviewerError(`Review timed out after ${Math.round(callbacks.timeoutMs / 1000)}s`, {\n timeout: true,\n hint: 'Increase timeoutMs or reduce the diff size.',\n }),\n ),\n callbacks.timeoutMs,\n );\n });\n\n await agent.prompt(userPrompt);\n await Promise.race([ended, timeout]);\n } finally {\n clearTimeout(timeoutId);\n unsubscribe?.();\n }\n return finalText;\n}\n\n/** Run async tasks with a bounded number running concurrently. */\nasync function runBounded(tasks: Array<() => Promise<void>>, limit: number): Promise<void> {\n let cursor = 0;\n const workers = Array.from({ length: Math.min(limit, tasks.length) }, async () => {\n while (cursor < tasks.length) {\n const index = cursor;\n cursor += 1;\n const task = tasks[index];\n if (task) await task();\n }\n });\n await Promise.all(workers);\n}\n\nconst VERIFY_CONCURRENCY = Number(process.env.CODE_REVIEW_VERIFY_CONCURRENCY) || 4;\nconst FIND_CONCURRENCY = 3;\n\ninterface StageDeps {\n createAgent: CreateAgent;\n /**\n * Effective model pool. `pool[0]` is the primary model (used by `single`/`verify`\n * depth and as the default). `full` depth maps angles across all members and\n * verifies with a member other than a finding's author.\n */\n pool: PoolMember[];\n tools: AgentTool[];\n thinkingLevel: ThinkingLevel;\n timeoutMs: number;\n logger: Logger;\n aggregated: AggregatedUsage;\n /**\n * Explicit Verify-stage model (from `--verify-model`). When set, every verifier\n * runs on this member instead of the pool's cross-family pick. Null keeps the\n * pool-based selection.\n */\n verifyMember?: PoolMember | null;\n}\n\n/**\n * Pick a deterministic verifier for a finding authored by `authorModelId`: the\n * first pool member whose id differs, by pool order. With a 1-model pool (or when\n * no other member exists) this degenerates to the author itself — today's\n * behaviour. The author is never preferred when an alternative exists, so the\n * verifier shares fewer blind spots with the finder.\n */\nfunction pickVerifier(pool: PoolMember[], authorModelId: string): PoolMember {\n const other = pool.find((member) => member.id !== authorModelId);\n return other ?? pool[0];\n}\n\n/**\n * Multi-angle Find (used by `full` depth). Runs one finder per review angle\n * concurrently — each with the same diff, skills, and read-only repo tools, but\n * a system prompt narrowed to its lane — then merges and deduplicates their\n * findings via Triage. Returns the triaged comments plus the first non-empty\n * finder summary to seed the synthesized overview.\n */\nasync function runMultiAngleFind(\n context: ReviewContext,\n minSeverity: GitLabReviewSeverity,\n userPrompt: string,\n deps: StageDeps,\n): Promise<{ findings: AuthoredFinding[]; summary: string | null }> {\n const groups: AuthoredFinding[][] = REVIEW_ANGLES.map(() => []);\n const summaries: Array<string | null> = REVIEW_ANGLES.map(() => null);\n\n const tasks = REVIEW_ANGLES.map((angle, index) => async () => {\n // Fixed angle→model mapping: angle `i` runs on pool member `i % pool.length`.\n // Deterministic and stable for a given (MR, commit) — no randomness.\n const member = deps.pool[index % deps.pool.length];\n const agent = deps.createAgent({\n systemPrompt: buildAngleSystemPrompt(context, minSeverity, angle),\n model: member.model,\n tools: deps.tools,\n thinkingLevel: deps.thinkingLevel,\n getApiKey: member.getApiKey,\n });\n try {\n const text = await runAgentToCompletion(agent, userPrompt, {\n timeoutMs: deps.timeoutMs,\n onAssistantMessage: (message) => accumulateUsage(deps.aggregated, message, member.id),\n onToolStart: (toolName, args) =>\n deps.logger.debug(` [${angle.key}] → ${toolName}${formatToolArgs(toolName, args)}`),\n });\n const parsed = parseReviewMarkdownWithWarnings(text);\n // Annotate each finding with the model that authored it. This is internal\n // pipeline metadata for cross-family verification — it never reaches a\n // posted comment, fingerprint, or the summary.\n groups[index] = parsed.comments.map((comment) => ({ comment, authorModel: member.id }));\n summaries[index] = parsed.summary;\n } catch (error) {\n deps.logger.warn(`Find angle \"${angle.key}\" failed: ${(error as Error).message}; skipping.`);\n }\n });\n\n await runBounded(tasks, FIND_CONCURRENCY);\n\n const raw = groups.reduce((total, group) => total + group.length, 0);\n const findings = triageFindings(groups);\n deps.logger.info(\n `Multi-angle Find: ${REVIEW_ANGLES.length} angles → ${raw} raw finding(s), ${findings.length} after triage.`,\n );\n const summary = summaries.find((value) => value && value.trim()) ?? null;\n return { findings, summary };\n}\n\n/**\n * Verify + Synthesize, shared by `verify` and `full` depth. Hands each severe\n * (CRITICAL/WARN) finding to a separate adversarial agent that tries to refute\n * it, deterministically applies the verdicts, and synthesizes the canonical\n * `{ summary, comments }` JSON. INFO findings are not verified — they are not\n * the precision risk and re-checking them wastes tokens.\n */\nasync function verifyAndSynthesize(\n findings: AuthoredFinding[],\n summary: string | null,\n diff: string,\n commitLog: string | undefined,\n deps: StageDeps,\n): Promise<string> {\n const comments = findings.map((f) => f.comment);\n const severe = findings\n .map((finding, index) => ({ finding, index }))\n .filter(\n ({ finding }) =>\n finding.comment.severity === 'critical' || finding.comment.severity === 'warn',\n );\n\n const verdicts = new Map<number, Verdict>();\n if (severe.length > 0) {\n const verifySystemPrompt = buildVerifySystemPrompt(diff, commitLog);\n const tasks = severe.map(({ finding, index }) => async () => {\n const comment = finding.comment;\n // Explicit --verify-model wins; otherwise a cross-family verifier: a pool\n // member other than the one that authored the finding (degenerates to the\n // author with a 1-model pool).\n const verifierMember = deps.verifyMember ?? pickVerifier(deps.pool, finding.authorModel);\n const verifier = deps.createAgent({\n systemPrompt: verifySystemPrompt,\n model: verifierMember.model,\n tools: deps.tools,\n thinkingLevel: deps.thinkingLevel,\n getApiKey: verifierMember.getApiKey,\n });\n try {\n const text = await runAgentToCompletion(verifier, buildVerifyUserPrompt(comment), {\n timeoutMs: deps.timeoutMs,\n onAssistantMessage: (message) =>\n accumulateUsage(deps.aggregated, message, verifierMember.id),\n });\n verdicts.set(index, parseVerdict(text));\n } catch (error) {\n deps.logger.warn(\n `Verify failed for ${comment.file}:${comment.line}: ${(error as Error).message}; keeping finding.`,\n );\n verdicts.set(index, { decision: 'keep', reason: 'verifier error; finding kept' });\n }\n });\n await runBounded(tasks, VERIFY_CONCURRENCY);\n }\n\n const result = applyVerdicts(comments, verdicts);\n const dropped = result.audit.filter((entry) => entry.action === 'dropped').length;\n const downgraded = result.audit.filter((entry) => entry.action === 'downgraded').length;\n deps.logger.info(\n `Verify: re-checked ${severe.length} severe finding(s) — ${dropped} dropped, ${downgraded} downgraded.`,\n );\n\n return synthesizeReviewJson(summary, result);\n}\n\n/**\n * `verify` depth wrapper: when the Find pass produced nothing severe, the model\n * output is returned verbatim (byte-identical to a plain Find); otherwise the\n * severe findings are verified and the review re-synthesized.\n */\nasync function runVerifyStage(\n finalText: string,\n diff: string,\n commitLog: string | undefined,\n deps: StageDeps,\n): Promise<string> {\n const parsed = parseReviewMarkdownWithWarnings(finalText);\n const hasSevere = parsed.comments.some(\n (comment) => comment.severity === 'critical' || comment.severity === 'warn',\n );\n if (!hasSevere) return finalText;\n // `verify` depth has a single Find pass on the primary model, so all findings\n // are authored by `pool[0]`; the verifier picks a different member when one\n // exists, otherwise re-uses the primary (today's behaviour).\n const findings: AuthoredFinding[] = parsed.comments.map((comment) => ({\n comment,\n authorModel: deps.pool[0].id,\n }));\n return verifyAndSynthesize(findings, parsed.summary, diff, commitLog, deps);\n}\n\nfunction formatToolArgs(toolName: string, args: unknown): string {\n if (!args || typeof args !== 'object') return '';\n const obj = args as Record<string, unknown>;\n if (toolName === 'Read' || toolName === 'read') {\n return typeof obj.file_path === 'string' ? ` ${obj.file_path}` : '';\n }\n if (toolName === 'Bash' || toolName === 'bash') {\n return typeof obj.command === 'string' ? ` ${obj.command.slice(0, 80)}` : '';\n }\n const entries = Object.entries(obj)\n .slice(0, 2)\n .map(([k, v]) => `${k}=${String(v).slice(0, 40)}`);\n return entries.length > 0 ? ` ${entries.join(' ')}` : '';\n}\n","/**\n * Optional OpenTelemetry bridge over `diagnostics_channel` and the agent\n * event stream.\n *\n * Subscribes to every `@weareikko/code-review:*` tracing channel, opens an\n * OTel span on `start`, and closes it on `asyncEnd`/`error`. The `reviewer.run`\n * phase additionally carries OpenTelemetry GenAI semantic-convention\n * attributes (`gen_ai.*`) and emits the standardized GenAI client metrics\n * (`gen_ai.client.operation.duration`, `gen_ai.client.token.usage`,\n * `gen_ai.client.cost`, `gen_ai.client.time_to_first_token`) so\n * metrics-driven AI observability surfaces auto-discover the service.\n *\n * Per-turn and per-tool-call telemetry is captured via `createAgentTelemetry`,\n * which subscribes to the agent's live event stream and emits:\n * - `gen_ai.agent.turn` child spans under `invoke_agent code-review`\n * - `execute_tool <name>` grandchild spans under each turn\n * - Per-turn `gen_ai.client.token.usage` and `gen_ai.client.cost` metrics\n * - `gen_ai.client.time_to_first_token` when streaming events fire\n *\n * Opt-in: set `CODE_REVIEW_OTEL=1`. Exporter selection and endpoint follow\n * the standard `OTEL_*` env vars (`OTEL_EXPORTER_OTLP_ENDPOINT`,\n * `OTEL_EXPORTER_OTLP_HEADERS`, …).\n *\n * **Content capture**: set `CODE_REVIEW_OTEL_CAPTURE_CONTENT=1` to attach\n * LLM output text and tool arguments/results to spans as `gen_ai.output.messages`,\n * `gen_ai.tool.call.arguments`, and `gen_ai.tool.call.result`. These attributes\n * may contain code diffs and review commentary — only enable after confirming\n * your observability backend's data-retention and PII policies allow it.\n *\n * **Grafana Cloud token scopes**: for all three signals to reach their\n * respective backends, the service account token used in\n * `OTEL_EXPORTER_OTLP_HEADERS` must have:\n * - `Traces Publisher` — writes to Tempo (traces)\n * - `Metrics Publisher` — writes to Mimir (gen_ai.* histograms)\n * - `Logs Publisher` — writes to Loki (structured log records)\n * A token missing any of these scopes will receive `401 Unauthorized:\n * invalid scope requested` silently from the OTLP gateway. Enable OTel\n * diagnostics with `OTEL_LOG_LEVEL=error` to surface export failures.\n *\n * The OTel SDK runtime is bundled but loaded via dynamic `import()` behind the\n * env check, so disabling the bridge skips the SDK boot entirely. Library\n * callers who already have configured providers in their process can inject\n * their own runtime via `startOtelBridge({ runtime })` so spans and metrics\n * join the host providers instead of a second `NodeSDK`.\n */\n\nimport type {\n Attributes,\n Context,\n Counter,\n Histogram,\n Meter,\n MeterProvider,\n Span,\n Tracer,\n TracerProvider,\n} from '@opentelemetry/api';\nimport { context, metrics, SpanKind, SpanStatusCode, trace } from '@opentelemetry/api';\nimport type { Logger, LoggerProvider } from '@opentelemetry/api-logs';\nimport { logs, SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n diagnosticChannels,\n type DiagnosticContext,\n type DiagnosticPhase,\n type DiagnosticUsage,\n} from './diagnostics.js';\nimport type { AgentLike } from './gitlab-review.js';\nimport { splitModel, type GeneratedComment } from './types.js';\n\n// Inlined at build time by Vite's `define` (see vite.config.ts). Keeps\n// `service.version` accurate under `npx`/standalone bin invocations, where\n// `npm_package_version` from `npm run` is not set.\ndeclare const __PKG_VERSION__: string;\n\nexport interface OtelBridge {\n shutdown(): Promise<void>;\n /**\n * Returns a function that, when called with an agent, subscribes to its live\n * event stream and emits per-turn and per-tool-call OTel spans/metrics.\n * Must be called after the `reviewer.run` diagnostic span is open (i.e. from\n * inside `traceDiagnosticPhase('reviewer.run', ...)`). Returns `undefined`\n * when the span is not yet open or OTel is disabled.\n */\n createAgentTelemetry(runId: string): ((agent: AgentLike) => () => void) | undefined;\n /**\n * Emits one structured OTel log record per generated comment to Loki/the\n * configured log backend. Each record carries `event.name`,\n * `gitlab_review.comment.*` attributes, and the comment body as the log\n * line. Safe to call at any point after the run phase has opened.\n */\n logComments(comments: GeneratedComment[], runId: string): void;\n}\n\nexport interface OtelRuntime {\n tracerProvider: TracerProvider;\n meterProvider: MeterProvider;\n loggerProvider: LoggerProvider;\n shutdown(): Promise<void>;\n}\n\nexport interface OtelBridgeOptions {\n /**\n * Pre-wired OTel runtime. When provided, the bridge uses the supplied\n * providers and skips dynamic import of `@opentelemetry/sdk-node`. Library\n * callers with configured `TracerProvider`/`MeterProvider` should pass their\n * own providers plus a no-op `shutdown`; tests inject fakes with assertion\n * hooks.\n */\n runtime?: OtelRuntime;\n /**\n * Override the env source used for the opt-in check. Defaults to\n * `process.env`.\n */\n env?: NodeJS.ProcessEnv;\n /**\n * When true, attaches LLM output text and tool call arguments/results to\n * spans as `gen_ai.output.messages`, `gen_ai.tool.call.arguments`, and\n * `gen_ai.tool.call.result`. Defaults to `isContentCaptureEnabled(env)`.\n *\n * Only enable after confirming your observability backend's data-retention\n * and PII policies permit storing code review content.\n */\n captureContent?: boolean;\n}\n\nconst ROOT_PHASE: DiagnosticPhase = 'run';\nconst GEN_AI_PHASE: DiagnosticPhase = 'reviewer.run';\nconst POST_COMMENTS_PHASE: DiagnosticPhase = 'scm.post_comments';\nconst SERVICE_NAME = '@weareikko/code-review';\n\n// Added as a data-point attribute on every gitlab_review_* metric so that\n// Prometheus/Mimir surfaces it as a label (service_name=\"…\"). The SDK-level\n// service.name resource attribute only populates target_info, not per-metric\n// labels, so we need to include it explicitly here.\nconst REVIEW_SERVICE_ATTRS = { 'service.name': SERVICE_NAME } as const;\n\n// Advisory histogram bucket boundaries from the OTel GenAI metrics semconv.\n// https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/\nconst DURATION_BUCKETS_S = [\n 0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.56, 5.12, 10.24, 20.48, 40.96, 81.92,\n];\nconst TOKEN_BUCKETS = [\n 1, 4, 16, 64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864,\n];\nconst TTFT_BUCKETS_S = [\n 0.001, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0,\n];\nconst COST_BUCKETS_USD = [\n 0.0001, 0.0005, 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0,\n];\n// Review-level histogram boundaries — one observation per complete run or per phase.\nconst REVIEW_RUN_DURATION_BUCKETS_S = [5, 15, 30, 60, 120, 180, 300, 600];\nconst REVIEW_TOTAL_COST_BUCKETS_USD = [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0];\nconst REVIEW_PHASE_DURATION_BUCKETS_S = [1, 5, 15, 30, 60, 120, 300];\n\nconst OTEL_SDK_PACKAGES = [\n '@opentelemetry/sdk-node',\n '@opentelemetry/resources',\n '@opentelemetry/semantic-conventions',\n] as const;\n\nconst noop = (): void => undefined;\n\ninterface OpenSpan {\n span: Span;\n closed: boolean;\n}\n\n// Minimal shape of a per-turn assistant message we need for telemetry.\n// Avoids importing AssistantMessage from @earendil-works/pi-ai in this module.\ninterface TurnMessage {\n role?: string;\n model?: string;\n stopReason?: string;\n usage?: {\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n cost?: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };\n };\n}\n\nexport function isOtelEnabled(env: NodeJS.ProcessEnv = process.env): boolean {\n return env.CODE_REVIEW_OTEL === '1' || env.CODE_REVIEW_OTEL === 'true';\n}\n\n/**\n * Returns true when `CODE_REVIEW_OTEL_CAPTURE_CONTENT=1` (or `true`) is set.\n *\n * When enabled, per-turn assistant output text is attached to turn spans as\n * `gen_ai.output.messages`, and tool arguments / results are attached to\n * `execute_tool` spans as `gen_ai.tool.call.arguments` / `gen_ai.tool.call.result`.\n *\n * These fields may contain code diffs and review commentary — only enable after\n * confirming your observability backend's data-retention and PII policies.\n */\nexport function isContentCaptureEnabled(env: NodeJS.ProcessEnv = process.env): boolean {\n return (\n env.CODE_REVIEW_OTEL_CAPTURE_CONTENT === '1' || env.CODE_REVIEW_OTEL_CAPTURE_CONTENT === 'true'\n );\n}\n\nexport async function startOtelBridge(options: OtelBridgeOptions = {}): Promise<OtelBridge | null> {\n const env = options.env ?? process.env;\n if (!isOtelEnabled(env)) return null;\n\n const captureContent = options.captureContent ?? isContentCaptureEnabled(env);\n const ciAttrs = buildCiAttrs(env);\n const ciSpanAttrs = buildCiSpanAttrs(env);\n\n const runtime = options.runtime ?? (await loadDefaultRuntime());\n const tracer: Tracer = runtime.tracerProvider.getTracer(SERVICE_NAME);\n const meter: Meter = runtime.meterProvider.getMeter(SERVICE_NAME);\n const logger: Logger = runtime.loggerProvider.getLogger(SERVICE_NAME);\n\n const operationDuration = meter.createHistogram('gen_ai.client.operation.duration', {\n description: 'GenAI operation duration',\n unit: 's',\n advice: { explicitBucketBoundaries: DURATION_BUCKETS_S },\n });\n const tokenUsage = meter.createHistogram('gen_ai.client.token.usage', {\n description: 'Measures number of input and output tokens used',\n unit: '{token}',\n advice: { explicitBucketBoundaries: TOKEN_BUCKETS },\n });\n const operationCost = meter.createHistogram('gen_ai.client.cost', {\n description: 'GenAI operation cost in USD',\n unit: '{usd}',\n advice: { explicitBucketBoundaries: COST_BUCKETS_USD },\n });\n const timeToFirstToken = meter.createHistogram('gen_ai.client.time_to_first_token', {\n description: 'Time to first token from the LLM',\n unit: 's',\n advice: { explicitBucketBoundaries: TTFT_BUCKETS_S },\n });\n\n // Review-level metrics — one observation per complete run or per phase.\n const {\n reviewRunDuration,\n reviewTotalCost,\n reviewCommentsTotal,\n reviewDraftsPublishedTotal,\n reviewPhaseDuration,\n reviewRunsTotal,\n reviewErrorsTotal,\n reviewLlmTokens,\n } = createReviewInstruments(meter);\n\n const openByRun = new Map<string, Map<DiagnosticPhase, OpenSpan>>();\n\n // Per-run metadata cached from the ROOT_PHASE context for use in the review\n // completion log, per-turn agent telemetry (configuredModel), and logComments.\n const runMeta = new Map<string, RunMeta>();\n\n const parentContext = (runId: string) => {\n const root = openByRun.get(runId)?.get(ROOT_PHASE);\n return root && !root.closed ? trace.setSpan(context.active(), root.span) : context.active();\n };\n\n const openSpan = (ctx: DiagnosticContext): void => {\n let phases = openByRun.get(ctx.runId);\n if (!phases) {\n phases = new Map();\n openByRun.set(ctx.runId, phases);\n }\n // A still-open span for this phase means a duplicate start — ignore it. A\n // *closed* entry means the phase legitimately runs more than once per run\n // (e.g. scm.get_discussions, fetched before and after the review); let it\n // re-open so the second occurrence gets its own span and HTTP attributes.\n const existing = phases.get(ctx.phase);\n if (existing && !existing.closed) return;\n const span = tracer.startSpan(\n spanNameFor(ctx.phase),\n {\n kind: SpanKind.INTERNAL,\n attributes: { ...baseAttributes(ctx), ...ciAttrs, ...ciSpanAttrs },\n },\n parentContext(ctx.runId),\n );\n phases.set(ctx.phase, { span, closed: false });\n // Seed run metadata for logComments, completion log, and per-turn agent\n // telemetry (model feeds gen_ai.system derivation in buildAgentSubscriber).\n if (ctx.phase === ROOT_PHASE) {\n // Store root span context so logger.emit() can correlate log records to\n // the trace — tracer.startSpan does not activate the span, so we capture\n // the context explicitly here while the span is live.\n const rootSpanCtx = trace.setSpan(context.active(), span);\n runMeta.set(ctx.runId, {\n project: ctx.project,\n mr: ctx.mr,\n gitlabUrl: ctx.gitlabUrl,\n ciAttrs,\n ciSpanAttrs,\n model: ctx.model,\n rootSpanCtx,\n });\n // Emit a run-start log so log-only consumers can compute duration and\n // detect stuck/hung runs without waiting for (or ever seeing) a completion.\n emitReviewStartedLog(logger, ctx, ciAttrs, ciSpanAttrs, rootSpanCtx);\n }\n };\n\n const closeSpan = (ctx: DiagnosticContext, isError: boolean): void => {\n const entry = openByRun.get(ctx.runId)?.get(ctx.phase);\n if (!entry || entry.closed) return;\n if (ctx.phase === GEN_AI_PHASE) {\n applyGenAiAttributes(entry.span, ctx);\n recordGenAiMetrics(operationDuration, ctx, isError, ciAttrs);\n // Cache usage so the ROOT_PHASE completion log can include cost/token totals.\n if (ctx.usage) {\n const meta = runMeta.get(ctx.runId);\n if (meta) meta.usage = ctx.usage;\n }\n }\n // Cache posting results from the post_comments phase so they are available when\n // the root phase closes and emits the review-level drafts metric.\n if (ctx.phase === POST_COMMENTS_PHASE && typeof ctx.draftsPublished === 'number') {\n const meta = runMeta.get(ctx.runId);\n if (meta) meta.draftsPublished = ctx.draftsPublished;\n }\n applyResultAttributes(entry.span, ctx);\n if (isError && ctx.errorInfo) {\n entry.span.recordException(ctx.errorInfo);\n entry.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: ctx.errorInfo.message,\n });\n }\n entry.span.end();\n entry.closed = true;\n\n const status = resolveRunStatus(ctx, isError);\n const projectPath = ciAttrs['gitlab.project_path'] ?? '';\n\n // Emit a phase-duration observation for every phase that has a measured duration.\n if (typeof ctx.durationMs === 'number') {\n reviewPhaseDuration.record(ctx.durationMs / 1000, {\n ...REVIEW_SERVICE_ATTRS,\n 'gitlab.project_path': projectPath,\n 'gitlab_review.phase': ctx.phase,\n 'gitlab_review.status': status,\n });\n }\n\n if (ctx.phase === ROOT_PHASE) {\n const meta = runMeta.get(ctx.runId);\n const pipelineSource = ciAttrs['gitlab.pipeline_source'] ?? '';\n // Shared label set for every review-level metric data point.\n const runMetricBase = {\n ...REVIEW_SERVICE_ATTRS,\n 'gitlab.project_path': projectPath,\n 'gitlab_review.dry_run': ctx.dryRun,\n };\n const usage = meta?.usage ?? ctx.usage;\n // gen_ai.request.model lets cost/duration/token series be compared across\n // model versions. It is low-cardinality (changes only when the configured\n // model changes), unlike run_id which we keep off metrics entirely.\n const runModelAttrs = genAiModelAttrs(undefined, splitModel(usage?.model ?? '').modelId);\n\n // One increment per run regardless of duration availability. This is the\n // canonical \"how many reviews ran\" series; counting histogram `_count`\n // proved unreliable for the dashboard, and run_id is deliberately NOT a\n // label here — a per-run UUID would explode Prometheus/Mimir cardinality.\n reviewRunsTotal.add(1, {\n ...runMetricBase,\n 'gitlab.pipeline_source': pipelineSource,\n 'gitlab_review.status': status,\n });\n\n // Dedicated error counter so an error rate can be alerted on without\n // decomposing the runs_total series. error.type mirrors the convention\n // used for the gen_ai duration metric (typed-error code, else name).\n if (isError) {\n reviewErrorsTotal.add(1, {\n ...runMetricBase,\n 'gitlab_review.status': status,\n 'error.type': errorTypeOf(ctx),\n });\n }\n\n if (typeof ctx.durationMs === 'number') {\n reviewRunDuration.record(ctx.durationMs / 1000, {\n ...runMetricBase,\n ...runModelAttrs,\n 'gitlab.pipeline_source': pipelineSource,\n 'gitlab_review.status': status,\n });\n }\n\n const totalCostUsd = usage?.cost.total;\n if (totalCostUsd !== undefined) {\n reviewTotalCost.record(totalCostUsd, {\n ...runMetricBase,\n ...runModelAttrs,\n 'gitlab_review.status': status,\n });\n }\n\n // LLM token consumption as cumulative counters (one per token type), so\n // token trends can be alerted on and dashboarded with rate()/increase()\n // without summing the per-turn gen_ai.client.token.usage histogram.\n if (usage) {\n const tokenAttrs = {\n ...REVIEW_SERVICE_ATTRS,\n ...runModelAttrs,\n 'gitlab.project_path': projectPath,\n };\n const tokenByType = [\n ['input', 'input'],\n ['output', 'output'],\n ['cacheRead', 'cache_read'],\n ['cacheWrite', 'cache_creation'],\n ] as const;\n for (const [field, type] of tokenByType) {\n const value = usage.tokens[field];\n if (value > 0) reviewLlmTokens[type].add(value, tokenAttrs);\n }\n }\n\n // Prefer a per-severity breakdown (matching the gitlab_review.comment.severity\n // log attribute) when the run provided one; fall back to a single\n // unlabelled increment for callers/contexts that only know the total.\n const bySeverity = ctx.postedBySeverity;\n if (bySeverity) {\n for (const [severity, count] of Object.entries(bySeverity)) {\n if (count && count > 0) {\n reviewCommentsTotal.add(count, {\n ...runMetricBase,\n 'gitlab_review.comment.severity': severity,\n });\n }\n }\n } else {\n const posted = ctx.posted ?? 0;\n if (posted > 0) reviewCommentsTotal.add(posted, runMetricBase);\n }\n\n reviewDraftsPublishedTotal.add(meta?.draftsPublished ?? 0, runMetricBase);\n\n emitReviewCompletedLog(logger, ctx, meta, isError);\n runMeta.delete(ctx.runId);\n openByRun.delete(ctx.runId);\n }\n };\n\n const handlers = {\n start: (ctx: DiagnosticContext) => openSpan(ctx),\n end: noop,\n asyncStart: noop,\n asyncEnd: (ctx: DiagnosticContext) => closeSpan(ctx, false),\n error: (ctx: DiagnosticContext) => closeSpan(ctx, true),\n };\n\n const unsubs: Array<() => void> = [];\n for (const channel of Object.values(diagnosticChannels)) {\n channel.subscribe(handlers);\n unsubs.push(() => channel.unsubscribe(handlers));\n }\n\n return {\n async shutdown() {\n for (const off of unsubs) off();\n for (const phases of openByRun.values()) {\n for (const entry of phases.values()) {\n if (!entry.closed) {\n entry.span.end();\n entry.closed = true;\n }\n }\n }\n openByRun.clear();\n runMeta.clear();\n await runtime.shutdown();\n },\n\n logComments(comments: GeneratedComment[], runId: string): void {\n const meta = runMeta.get(runId);\n for (const { comment, duplicate } of comments) {\n const preview = comment.body.length > 500 ? `${comment.body.slice(0, 497)}…` : comment.body;\n logger.emit({\n severityNumber: SeverityNumber.INFO,\n severityText: 'INFO',\n body: `[${comment.severity}] ${comment.file}:${comment.line} — ${preview}`,\n context: meta?.rootSpanCtx,\n attributes: {\n 'service.name': SERVICE_NAME,\n 'event.name': 'gitlab_review.comment',\n 'gitlab_review.run_id': runId,\n 'gitlab_review.comment.file': comment.file,\n 'gitlab_review.comment.line': comment.line,\n 'gitlab_review.comment.severity': comment.severity,\n 'gitlab_review.comment.is_duplicate': duplicate,\n ...(meta && {\n 'gitlab.project_id': meta.project,\n 'gitlab.mr_iid': meta.mr,\n 'gitlab.server_url': meta.gitlabUrl,\n ...meta.ciAttrs,\n ...meta.ciSpanAttrs,\n }),\n },\n });\n }\n },\n\n createAgentTelemetry(runId: string): ((agent: AgentLike) => () => void) | undefined {\n const reviewerEntry = openByRun.get(runId)?.get(GEN_AI_PHASE);\n if (!reviewerEntry || reviewerEntry.closed) return undefined;\n const reviewerSpanCtx = trace.setSpan(context.active(), reviewerEntry.span);\n return buildAgentSubscriber(\n tracer,\n tokenUsage,\n operationCost,\n timeToFirstToken,\n reviewerSpanCtx,\n {\n ciAttrs,\n runId,\n // Pass the configured model so the per-turn subscriber can derive\n // gen_ai.system when msg.model carries a bare ID without a provider prefix.\n configuredModel: runMeta.get(runId)?.model,\n captureContent,\n },\n );\n },\n };\n}\n\ninterface AgentSubscriberOptions {\n ciAttrs?: Record<string, string>;\n runId?: string;\n /**\n * Full configured model string (e.g. `'anthropic/claude-sonnet-4-5'`). Used\n * as a fallback to derive `gen_ai.system` when the agent event stream emits\n * bare model IDs without a provider prefix (common with the Anthropic SDK).\n */\n configuredModel?: string;\n /**\n * When true, serializes LLM output text and tool call arguments/results onto\n * spans as `gen_ai.output.messages`, `gen_ai.tool.call.arguments`, and\n * `gen_ai.tool.call.result`. Requires explicit opt-in via\n * `CODE_REVIEW_OTEL_CAPTURE_CONTENT=1` or `OtelBridgeOptions.captureContent`.\n */\n captureContent?: boolean;\n}\n\n/**\n * Builds the dynamic `gen_ai.system` / `gen_ai.request.model` metric labels\n * shared by the per-turn and per-phase GenAI metric emitters. Owning these in\n * one place keeps the two emission sites from drifting into separate Prometheus\n * series (the double-count `recordGenAiMetrics` documents).\n */\nfunction genAiModelAttrs(provider?: string, modelId?: string): Attributes {\n return {\n ...(provider ? { 'gen_ai.system': provider } : {}),\n ...(modelId ? { 'gen_ai.request.model': modelId } : {}),\n };\n}\n\n/**\n * Sets the shared `gen_ai.usage.*` token-count attributes on a span.\n *\n * gen_ai.usage.input_tokens follows Sentry AI monitoring's convention: the\n * value is the TOTAL tokens consumed as input (non-cached + cached).\n * gen_ai.usage.input_tokens.cached is the cached SUBSET so backends can compute\n * uncached cost without negative values (Sentry warns about this).\n * gen_ai.usage.cache_read.input_tokens is kept for Grafana backward compat.\n */\nfunction setTokenUsageSpanAttributes(\n span: Span,\n tokens: { input: number; output: number; cacheRead: number; cacheWrite: number },\n): void {\n span.setAttribute('gen_ai.usage.input_tokens', tokens.input + tokens.cacheRead);\n if (tokens.cacheRead) span.setAttribute('gen_ai.usage.input_tokens.cached', tokens.cacheRead);\n span.setAttribute('gen_ai.usage.output_tokens', tokens.output);\n if (tokens.cacheRead) span.setAttribute('gen_ai.usage.cache_read.input_tokens', tokens.cacheRead);\n if (tokens.cacheWrite) {\n span.setAttribute('gen_ai.usage.cache_creation.input_tokens', tokens.cacheWrite);\n }\n}\n\n/** Applies per-turn token counts and costs to the turn span and emits metric observations. */\nfunction recordTurnUsage(\n span: Span,\n u: NonNullable<TurnMessage['usage']>,\n metricAttrs: Attributes,\n tokenUsage: Histogram,\n operationCost: Histogram,\n): void {\n // Emit all four token type measurements per OTel GenAI semconv.\n // These are the canonical source for gen_ai.client.token.usage —\n // recordGenAiMetrics only emits phase duration to avoid double-count.\n tokenUsage.record(u.input, { ...metricAttrs, 'gen_ai.token.type': 'input' });\n tokenUsage.record(u.output, { ...metricAttrs, 'gen_ai.token.type': 'output' });\n if (u.cacheRead) {\n tokenUsage.record(u.cacheRead, { ...metricAttrs, 'gen_ai.token.type': 'cache_read' });\n }\n if (u.cacheWrite) {\n tokenUsage.record(u.cacheWrite, { ...metricAttrs, 'gen_ai.token.type': 'cache_creation' });\n }\n setTokenUsageSpanAttributes(span, u);\n if (u.cost) {\n // Emit cost broken down by token type (mirrors gen_ai.client.token.usage).\n // Per-turn is the sole emission point to prevent double-count.\n if (u.cost.input)\n operationCost.record(u.cost.input, { ...metricAttrs, 'gen_ai.token.type': 'input' });\n if (u.cost.output)\n operationCost.record(u.cost.output, { ...metricAttrs, 'gen_ai.token.type': 'output' });\n if (u.cost.cacheRead)\n operationCost.record(u.cost.cacheRead, {\n ...metricAttrs,\n 'gen_ai.token.type': 'cache_read',\n });\n if (u.cost.cacheWrite)\n operationCost.record(u.cost.cacheWrite, {\n ...metricAttrs,\n 'gen_ai.token.type': 'cache_creation',\n });\n span.setAttribute('gen_ai.usage.cost.input_usd', u.cost.input);\n span.setAttribute('gen_ai.usage.cost.output_usd', u.cost.output);\n span.setAttribute('gen_ai.usage.cost.total_usd', u.cost.total);\n }\n}\n\n/**\n * Safely serializes a value to a JSON string for use as an OTel span attribute.\n * Truncates to `maxLen` characters to stay within typical span attribute limits.\n * Returns `undefined` when the value is `null` or `undefined`.\n */\nfunction safeSerialize(value: unknown, maxLen = 2000): string | undefined {\n if (value === null || value === undefined) return undefined;\n try {\n const s = typeof value === 'string' ? value : JSON.stringify(value);\n return s.length > maxLen ? `${s.slice(0, maxLen - 1)}…` : s;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Best-effort extraction of the command string from a tool call's start `args`.\n * Tool args are tool-defined; Bash-style tools expose `command`. Returns\n * `undefined` for tools without one (Read/Grep/Find/Ls), in which case\n * `tool.command` is simply not set.\n */\nfunction extractToolCommand(args: unknown): string | undefined {\n if (typeof args !== 'object' || args === null) return undefined;\n const command = (args as { command?: unknown }).command;\n return typeof command === 'string' ? command : undefined;\n}\n\n/**\n * Best-effort extraction of an exit code and stderr from a tool's error result.\n * The result is tool-defined (`any`); shell-backed tools follow the\n * `{ code, stderr }` ExecResult shape (also accepts `exitCode`). Missing fields\n * are left `undefined` so no misleading attribute is set.\n */\nfunction extractToolErrorDetail(result: unknown): { exitCode?: number; stderr?: string } {\n if (typeof result !== 'object' || result === null) return {};\n const r = result as { exitCode?: unknown; code?: unknown; stderr?: unknown };\n const exitRaw = typeof r.exitCode === 'number' ? r.exitCode : r.code;\n return {\n exitCode: typeof exitRaw === 'number' ? exitRaw : undefined,\n stderr: typeof r.stderr === 'string' ? r.stderr : undefined,\n };\n}\n\n/**\n * Extracts printable text content from an assistant message's content array.\n * Returns a JSON-serialized array in Sentry's `{role, parts: [{type, text}]}` format,\n * or `undefined` when no text blocks are found.\n */\nfunction extractOutputMessages(msg: TurnMessage): string | undefined {\n const content = (msg as { content?: unknown[] }).content;\n if (!Array.isArray(content)) return undefined;\n const texts = content\n .filter((block): block is { type: string; text: string } => {\n return (\n typeof block === 'object' &&\n block !== null &&\n (block as { type?: string }).type === 'text' &&\n typeof (block as { text?: string }).text === 'string'\n );\n })\n .map((block) => ({ type: 'text', text: block.text }));\n if (texts.length === 0) return undefined;\n return safeSerialize([{ role: 'assistant', parts: texts }]);\n}\n\nfunction buildAgentSubscriber(\n tracer: Tracer,\n tokenUsage: Histogram,\n operationCost: Histogram,\n timeToFirstToken: Histogram,\n reviewerSpanCtx: ReturnType<typeof trace.setSpan>,\n options: AgentSubscriberOptions = {},\n): (agent: AgentLike) => () => void {\n const { ciAttrs = {}, runId, configuredModel, captureContent = false } = options;\n // configuredModel is fixed for the subscriber's lifetime, so derive its\n // provider once instead of re-splitting it on every turn (the common\n // Anthropic-SDK case where msg.model carries a bare ID without a provider).\n const configuredProvider = configuredModel ? splitModel(configuredModel).provider : undefined;\n // Static base for every per-turn GenAI metric; the dynamic gen_ai.system /\n // gen_ai.request.model labels are merged per message_end.\n const baseMetricAttrs: Attributes = {\n 'gen_ai.operation.name': 'invoke_agent',\n ...REVIEW_SERVICE_ATTRS,\n ...ciAttrs,\n };\n return (agent: AgentLike): (() => void) => {\n let currentTurn: { span: Span; startMs: number; firstTokenMs?: number } | undefined;\n // Track the originating command alongside the span so tool.command can be\n // attached on error (it lives on the start event's args, not the result).\n const openTools = new Map<string, { span: Span; command?: string }>();\n\n return agent.subscribe(async (event) => {\n const type = (event as { type?: string }).type;\n if (!type) return;\n\n if (type === 'turn_start') {\n if (currentTurn) currentTurn.span.end(); // close any orphaned turn\n const turnIndex = (event as { turnIndex?: number }).turnIndex;\n const span = tracer.startSpan(\n 'gen_ai.agent.turn',\n { kind: SpanKind.INTERNAL },\n reviewerSpanCtx,\n );\n span.setAttribute('gen_ai.operation.name', 'invoke_agent');\n span.setAttribute('gen_ai.agent.name', 'code-review');\n if (runId) span.setAttribute('gen_ai.conversation.id', runId);\n if (typeof turnIndex === 'number') span.setAttribute('gen_ai.agent.turn.index', turnIndex);\n currentTurn = { span, startMs: Date.now() };\n }\n\n if (type === 'message_update' && currentTurn && !currentTurn.firstTokenMs) {\n currentTurn.firstTokenMs = Date.now();\n }\n\n if (type === 'message_end') {\n const msg = (event as { message?: TurnMessage }).message;\n if (!msg || msg.role !== 'assistant' || !currentTurn) return;\n const { span, startMs, firstTokenMs } = currentTurn;\n currentTurn = undefined;\n\n // Extract provider and model ID from msg.model. The Anthropic SDK may\n // emit bare IDs like 'claude-sonnet-4-5' without a provider prefix; in\n // that case fall back to the configured model's provider so all per-turn\n // metrics share a consistent label set.\n const parts = splitModel(String(msg.model ?? ''));\n const modelId = parts.modelId;\n const provider = parts.provider ?? configuredProvider;\n\n const metricAttrs: Attributes = {\n ...baseMetricAttrs,\n ...genAiModelAttrs(provider, modelId),\n };\n // Spans carry gen_ai.response.model (the SDK's actual model); metrics\n // carry gen_ai.request.model (set above via genAiModelAttrs).\n if (provider) span.setAttribute('gen_ai.system', provider);\n if (modelId) span.setAttribute('gen_ai.response.model', modelId);\n if (msg.stopReason) span.setAttribute('gen_ai.response.stop_reason', msg.stopReason);\n\n if (firstTokenMs !== undefined) {\n const ttftS = (firstTokenMs - startMs) / 1000;\n timeToFirstToken.record(ttftS, metricAttrs);\n span.setAttribute('gen_ai.client.time_to_first_token_s', ttftS);\n }\n\n if (msg.usage) {\n recordTurnUsage(span, msg.usage, metricAttrs, tokenUsage, operationCost);\n }\n\n // Optional content capture — requires CODE_REVIEW_OTEL_CAPTURE_CONTENT=1.\n if (captureContent) {\n const outputMsgs = extractOutputMessages(msg);\n if (outputMsgs) span.setAttribute('gen_ai.output.messages', outputMsgs);\n }\n\n span.end();\n }\n\n if (type === 'tool_execution_start') {\n const { toolName, toolCallId, args } = event as {\n toolName?: string;\n toolCallId?: string;\n args?: unknown;\n };\n if (!toolName || !toolCallId) return;\n const toolParentCtx = currentTurn\n ? trace.setSpan(context.active(), currentTurn.span)\n : reviewerSpanCtx;\n const toolSpan = tracer.startSpan(\n `execute_tool ${toolName}`,\n { kind: SpanKind.INTERNAL },\n toolParentCtx,\n );\n toolSpan.setAttribute('gen_ai.operation.name', 'execute_tool');\n toolSpan.setAttribute('gen_ai.tool.name', toolName);\n toolSpan.setAttribute('gen_ai.tool.call.id', toolCallId);\n if (captureContent && args !== undefined) {\n const argsStr = safeSerialize(args);\n if (argsStr) toolSpan.setAttribute('gen_ai.tool.call.arguments', argsStr);\n }\n openTools.set(toolCallId, { span: toolSpan, command: extractToolCommand(args) });\n }\n\n if (type === 'tool_execution_end') {\n const { toolCallId, isError, result } = event as {\n toolCallId?: string;\n isError?: boolean;\n result?: unknown;\n };\n if (!toolCallId) return;\n const entry = openTools.get(toolCallId);\n if (!entry) return;\n const { span, command } = entry;\n if (isError) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n // Enrich the failure so a span isn't just \"Unknown error\". exit_code is\n // a safe scalar; stderr/command can contain code, so gate them behind\n // the same content-capture opt-in as tool arguments/results.\n const { exitCode, stderr } = extractToolErrorDetail(result);\n if (exitCode !== undefined) span.setAttribute('process.exit_code', exitCode);\n if (captureContent) {\n const stderrStr = stderr !== undefined ? safeSerialize(stderr) : undefined;\n if (stderrStr) span.setAttribute('tool.stderr', stderrStr);\n const commandStr = command !== undefined ? safeSerialize(command) : undefined;\n if (commandStr) span.setAttribute('tool.command', commandStr);\n }\n }\n if (captureContent && result !== undefined) {\n const resultStr = safeSerialize(result);\n if (resultStr) span.setAttribute('gen_ai.tool.call.result', resultStr);\n }\n span.end();\n openTools.delete(toolCallId);\n }\n\n if (type === 'agent_end') {\n if (currentTurn) {\n currentTurn.span.end();\n currentTurn = undefined;\n }\n for (const { span } of openTools.values()) span.end();\n openTools.clear();\n }\n });\n };\n}\n\nasync function loadDefaultRuntime(): Promise<OtelRuntime> {\n let modules: unknown[];\n try {\n modules = await Promise.all(OTEL_SDK_PACKAGES.map((name) => import(name)));\n } catch (cause) {\n // The OTel runtime ships as a regular dependency; reaching this branch\n // means the install is corrupt or a bundler stripped the modules.\n throw new Error(\n `Failed to load the bundled OpenTelemetry runtime (${OTEL_SDK_PACKAGES.join(', ')}). ` +\n `Reinstall @weareikko/code-review or pass startOtelBridge({ runtime }) explicitly.`,\n { cause },\n );\n }\n const [sdkNode, resources, semconv] = modules as [\n { NodeSDK: new (config: unknown) => { start: () => void; shutdown: () => Promise<void> } },\n {\n resourceFromAttributes: (attrs: Record<string, unknown>) => {\n merge: (other: unknown) => unknown;\n };\n defaultResource: () => { merge: (other: unknown) => unknown };\n },\n Record<string, string>,\n ];\n\n // `@opentelemetry/resources` v2 removed the `Resource` constructor in favor\n // of factory functions. Merge our service-identifying attributes onto the\n // default resource so SDK-detected attributes (telemetry.sdk.*, env-supplied\n // OTEL_RESOURCE_ATTRIBUTES) are preserved.\n const serviceResource = resources.resourceFromAttributes({\n [semconv.ATTR_SERVICE_NAME ?? 'service.name']: SERVICE_NAME,\n [semconv.ATTR_SERVICE_VERSION ?? 'service.version']: __PKG_VERSION__,\n });\n // NodeSDK defaults both OTEL_METRICS_EXPORTER and OTEL_LOGS_EXPORTER to\n // 'otlp' when the env vars are empty. However if the caller explicitly sets\n // them to 'none' (common in CI setups that ship traces but not metrics/logs),\n // we preserve that intent. Setting them here ensures the otlp default takes\n // effect even when the shell exports them as an empty string, which would\n // otherwise be parsed as an unknown exporter and silently ignored.\n process.env.OTEL_METRICS_EXPORTER = process.env.OTEL_METRICS_EXPORTER ?? 'otlp';\n process.env.OTEL_LOGS_EXPORTER = process.env.OTEL_LOGS_EXPORTER ?? 'otlp';\n\n const sdk = new sdkNode.NodeSDK({\n resource: resources.defaultResource().merge(serviceResource),\n // NodeSDK auto-detects OTLP HTTP/gRPC exporters and a periodic metric\n // reader from OTEL_* env vars and registers both providers globally.\n });\n sdk.start();\n return {\n tracerProvider: trace.getTracerProvider(),\n meterProvider: metrics.getMeterProvider(),\n loggerProvider: logs.getLoggerProvider(),\n shutdown: () => sdk.shutdown(),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Log helpers\n// ---------------------------------------------------------------------------\n\ninterface RunMeta {\n project: string;\n mr: string;\n gitlabUrl: string;\n /** Low-cardinality CI attributes spread into metric data points. */\n ciAttrs: Record<string, string>;\n /** High-cardinality CI attributes (job/pipeline IDs) used on spans and logs only. */\n ciSpanAttrs: Record<string, string>;\n /**\n * Context capturing the root span so `logger.emit()` can correlate log records\n * to the trace. Populated in `openSpan` for ROOT_PHASE before the span ends.\n */\n rootSpanCtx?: Context;\n /**\n * Configured model string from the ROOT_PHASE context (e.g.\n * `'anthropic/claude-sonnet-4-5'`). Passed to `buildAgentSubscriber` so it\n * can derive `gen_ai.system` when `msg.model` from the agent event stream\n * carries a bare model ID without a provider prefix.\n */\n model?: string;\n usage?: DiagnosticUsage;\n /** Cached from the `scm.post_comments` phase for the drafts-published metric. */\n draftsPublished?: number;\n}\n\nfunction emitReviewStartedLog(\n logger: Logger,\n ctx: DiagnosticContext,\n ciAttrs: Record<string, string>,\n ciSpanAttrs: Record<string, string>,\n rootSpanCtx: Context,\n): void {\n const modelId = splitModel(ctx.model ?? '').modelId;\n logger.emit({\n severityNumber: SeverityNumber.INFO,\n severityText: 'INFO',\n body: `review started: ${ctx.project} MR#${ctx.mr}`,\n context: rootSpanCtx,\n attributes: {\n 'service.name': SERVICE_NAME,\n 'event.name': 'gitlab_review.started',\n 'gitlab.project_id': ctx.project,\n 'gitlab.mr_iid': ctx.mr,\n 'gitlab.server_url': ctx.gitlabUrl,\n ...ciAttrs,\n ...ciSpanAttrs,\n 'gitlab_review.run_id': ctx.runId,\n 'gitlab_review.dry_run': ctx.dryRun,\n ...(modelId !== undefined && { 'gen_ai.request.model': modelId }),\n },\n });\n}\n\nfunction emitReviewCompletedLog(\n logger: Logger,\n ctx: DiagnosticContext,\n meta: RunMeta | undefined,\n isError: boolean,\n): void {\n const usage = meta?.usage;\n const modelId = splitModel(usage?.model ?? ctx.model ?? '').modelId;\n const cost = usage?.cost.total;\n const costStr = cost !== undefined ? ` $${cost.toFixed(4)}` : '';\n const commentStr = ctx.generated !== undefined ? ` → ${ctx.generated} comments` : '';\n // Failed runs get their own searchable event (gitlab_review.failed) with the\n // error type/message, so an ERROR-level query surfaces every failure.\n // errorInfo.message has the run's own secret values (GitLab token, API key)\n // scrubbed by toDiagnosticError before it ever reaches the context.\n const errorType = errorTypeOf(ctx);\n const body = isError\n ? `review failed: ${ctx.project} MR#${ctx.mr}${ctx.errorInfo ? ` — ${ctx.errorInfo.message}` : ''}`\n : `review completed: ${ctx.project} MR#${ctx.mr}${commentStr}${costStr}`;\n logger.emit({\n severityNumber: isError ? SeverityNumber.ERROR : SeverityNumber.INFO,\n severityText: isError ? 'ERROR' : 'INFO',\n body,\n context: meta?.rootSpanCtx,\n attributes: {\n 'service.name': SERVICE_NAME,\n 'event.name': isError ? 'gitlab_review.failed' : 'gitlab_review.completed',\n 'gitlab.project_id': ctx.project,\n 'gitlab.mr_iid': ctx.mr,\n 'gitlab.server_url': ctx.gitlabUrl,\n ...meta?.ciAttrs,\n ...meta?.ciSpanAttrs,\n ...(isError && {\n 'error.type': errorType,\n ...(ctx.errorInfo && { 'error.message': ctx.errorInfo.message }),\n ...(typeof ctx.errorInfo?.status === 'number' && {\n 'http.response.status_code': ctx.errorInfo.status,\n }),\n }),\n 'gitlab_review.run_id': ctx.runId,\n 'gitlab_review.duration_ms': ctx.durationMs ?? 0,\n 'gitlab_review.dry_run': ctx.dryRun,\n 'gitlab_review.comments.generated': ctx.generated ?? 0,\n 'gitlab_review.comments.new': ctx.newComments ?? 0,\n 'gitlab_review.comments.duplicate': ctx.duplicateComments ?? 0,\n 'gitlab_review.comments.posted': ctx.posted ?? 0,\n ...(modelId !== undefined && { 'gen_ai.request.model': modelId }),\n ...(cost !== undefined && { 'gen_ai.usage.cost.total_usd': cost }),\n ...(usage?.tokens.input !== undefined && {\n // Total (non-cached + cached) — Sentry AI monitoring model.\n 'gen_ai.usage.input_tokens': usage.tokens.input + (usage.tokens.cacheRead ?? 0),\n }),\n ...(usage?.tokens.cacheRead && {\n 'gen_ai.usage.input_tokens.cached': usage.tokens.cacheRead,\n // Keep for Grafana backward compat.\n 'gen_ai.usage.cache_read.input_tokens': usage.tokens.cacheRead,\n }),\n ...(usage?.tokens.output !== undefined && {\n 'gen_ai.usage.output_tokens': usage.tokens.output,\n }),\n ...(usage?.tokens.cacheWrite && {\n 'gen_ai.usage.cache_creation.input_tokens': usage.tokens.cacheWrite,\n }),\n },\n });\n}\n\nfunction spanNameFor(phase: DiagnosticPhase): string {\n // OTel GenAI semconv reserves invoke_workflow / invoke_agent / execute_tool\n // as well-known operation names; other phases stay namespaced.\n if (phase === ROOT_PHASE) return 'invoke_workflow code-review';\n if (phase === GEN_AI_PHASE) return 'invoke_agent code-review';\n return `code-review.${phase}`;\n}\n\ninterface ReviewInstruments {\n reviewRunDuration: Histogram;\n reviewTotalCost: Histogram;\n reviewCommentsTotal: Counter;\n reviewDraftsPublishedTotal: Counter;\n reviewPhaseDuration: Histogram;\n reviewRunsTotal: Counter;\n reviewErrorsTotal: Counter;\n reviewLlmTokens: Record<'input' | 'output' | 'cache_read' | 'cache_creation', Counter>;\n}\n\n/** Creates the review-level OTel metric instruments on the given meter. */\nfunction createReviewInstruments(meter: Meter): ReviewInstruments {\n return {\n reviewRunsTotal: meter.createCounter('gitlab_review_runs_total', {\n description: 'Total number of code-review runs, labelled by terminal status',\n }),\n reviewErrorsTotal: meter.createCounter('gitlab_review_errors_total', {\n description: 'Total number of failed code-review runs, labelled by error type',\n }),\n reviewLlmTokens: {\n input: meter.createCounter('gitlab_review_llm_input_tokens_total', {\n description: 'Total non-cached LLM input tokens consumed across code-review runs',\n unit: '{token}',\n }),\n output: meter.createCounter('gitlab_review_llm_output_tokens_total', {\n description: 'Total LLM output tokens generated across code-review runs',\n unit: '{token}',\n }),\n cache_read: meter.createCounter('gitlab_review_llm_cache_read_tokens_total', {\n description: 'Total LLM cache-read input tokens across code-review runs',\n unit: '{token}',\n }),\n cache_creation: meter.createCounter('gitlab_review_llm_cache_creation_tokens_total', {\n description: 'Total LLM cache-creation input tokens across code-review runs',\n unit: '{token}',\n }),\n },\n reviewRunDuration: meter.createHistogram('gitlab_review_run_duration_seconds', {\n description: 'Duration of a complete code-review run',\n unit: 's',\n advice: { explicitBucketBoundaries: REVIEW_RUN_DURATION_BUCKETS_S },\n }),\n reviewTotalCost: meter.createHistogram('gitlab_review_total_cost_usd', {\n description: 'Total LLM cost in USD for a complete code-review run',\n unit: '{usd}',\n advice: { explicitBucketBoundaries: REVIEW_TOTAL_COST_BUCKETS_USD },\n }),\n reviewCommentsTotal: meter.createCounter('gitlab_review_comments_total', {\n description: 'Total number of MR comments posted by code-review',\n }),\n reviewDraftsPublishedTotal: meter.createCounter('gitlab_review_drafts_published_total', {\n description: 'Total number of draft notes published by code-review',\n }),\n reviewPhaseDuration: meter.createHistogram('gitlab_review_phase_duration_seconds', {\n description: 'Duration of individual code-review workflow phases',\n unit: 's',\n advice: { explicitBucketBoundaries: REVIEW_PHASE_DURATION_BUCKETS_S },\n }),\n };\n}\n\n/**\n * Stable `error.type` label for a failed run: the typed-error code, else the\n * error class name, else `_OTHER`. Shared by the run/error counters, the failed\n * log record, and the gen_ai duration metric so they never drift.\n *\n * GitLab API failures are refined with their HTTP status (e.g.\n * `GITLAB_API_ERROR_500`) so a 500 on `bulk_publish` is distinguishable from a\n * 404/401 in alerting. HTTP status codes are low-cardinality, so they are safe\n * as a metric label.\n */\nfunction errorTypeOf(ctx: DiagnosticContext): string {\n const base = ctx.errorInfo?.code ?? ctx.errorInfo?.name ?? '_OTHER';\n const status = ctx.errorInfo?.status;\n if (status !== undefined && base === 'GITLAB_API_ERROR') return `${base}_${status}`;\n return base;\n}\n\n/**\n * Derives the `gitlab_review.status` label used by review-level OTel metrics.\n * Distinguishes timeouts (AbortError / ETIMEDOUT) from generic errors so\n * Grafana alerts can treat deadline-exceeded runs separately.\n */\nfunction resolveRunStatus(\n ctx: DiagnosticContext,\n isError: boolean,\n): 'success' | 'error' | 'timeout' {\n if (!isError) return 'success';\n const { errorInfo } = ctx;\n if (\n errorInfo?.timeout === true ||\n errorInfo?.name === 'AbortError' ||\n errorInfo?.name === 'TimeoutError' ||\n errorInfo?.code === 'ABORT_ERR' ||\n errorInfo?.code === 'ETIMEDOUT'\n ) {\n return 'timeout';\n }\n return 'error';\n}\n\n/**\n * Extracts GitLab CI environment variables that add project/pipeline context\n * to every metric, span, and log record. Only populated when running inside a\n * GitLab CI pipeline; callers spread the result so missing vars add nothing.\n */\nfunction buildCiAttrs(env: NodeJS.ProcessEnv): Record<string, string> {\n const attrs: Record<string, string> = {};\n if (env.CI_PROJECT_PATH) attrs['gitlab.project_path'] = env.CI_PROJECT_PATH;\n if (env.CI_PROJECT_NAMESPACE) attrs['gitlab.project_namespace'] = env.CI_PROJECT_NAMESPACE;\n if (env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME)\n attrs['gitlab.mr_target_branch'] = env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;\n if (env.CI_PIPELINE_SOURCE) attrs['gitlab.pipeline_source'] = env.CI_PIPELINE_SOURCE;\n return attrs;\n}\n\n/**\n * Extracts high-cardinality GitLab CI identifiers that should appear on spans\n * and log records but NOT on metric data points (to avoid label explosion in\n * Prometheus/Mimir). Spread results via `ciSpanAttrs` stored in RunMeta.\n */\nfunction buildCiSpanAttrs(env: NodeJS.ProcessEnv): Record<string, string> {\n const attrs: Record<string, string> = {};\n if (env.CI_JOB_ID) attrs['gitlab.ci_job_id'] = env.CI_JOB_ID;\n if (env.CI_PIPELINE_ID) attrs['gitlab.ci_pipeline_id'] = env.CI_PIPELINE_ID;\n return attrs;\n}\n\nfunction baseAttributes(ctx: DiagnosticContext): Record<string, string | number | boolean> {\n return {\n 'gitlab_review.run_id': ctx.runId,\n 'gen_ai.conversation.id': ctx.runId,\n 'gitlab_review.phase': ctx.phase,\n 'gitlab.project_id': ctx.project,\n 'gitlab.mr_iid': ctx.mr,\n 'gitlab.server_url': ctx.gitlabUrl,\n 'gitlab_review.dry_run': ctx.dryRun,\n 'gitlab_review.no_post': ctx.noPost,\n 'gitlab_review.min_severity': ctx.minSeverity,\n };\n}\n\n// Numeric DiagnosticContext fields mapped to their result span attribute. Each\n// is set only when present as a number, so absent fields add no attribute.\nconst NUMERIC_RESULT_ATTRIBUTES = [\n ['durationMs', 'gitlab_review.duration_ms'],\n ['generated', 'gitlab_review.comments.generated'],\n ['newComments', 'gitlab_review.comments.new'],\n ['duplicateComments', 'gitlab_review.comments.duplicate'],\n ['posted', 'gitlab_review.comments.posted'],\n ['draftsPublished', 'gitlab_review.drafts.published'],\n ['draftsCreated', 'gitlab_review.drafts.created'],\n ['summaryNoteId', 'gitlab_review.summary.note_id'],\n ['warnings', 'gitlab_review.warnings'],\n ['draftsAbandoned', 'gitlab_review.drafts.abandoned'],\n ['draftsDeletedPrePublish', 'gitlab_review.drafts.deleted_pre_publish'],\n ['draftsPublishFailed', 'gitlab_review.drafts.publish_failed'],\n ['diffFilesChanged', 'diff.files_changed'],\n ['diffLinesAdded', 'diff.lines_added'],\n ['diffLinesRemoved', 'diff.lines_removed'],\n // GitLab API spans — numeric HTTP semantic-convention attributes.\n ['httpStatusCode', 'http.response.status_code'],\n ['httpResponseBodySize', 'http.response.body.size'],\n] as const satisfies ReadonlyArray<readonly [keyof DiagnosticContext, string]>;\n\n// String DiagnosticContext fields mapped to their result span attribute. Each\n// is set only when present as a string. HTTP keys follow the stable OTel HTTP\n// semantic conventions (http.request.method, url.full, server.address).\nconst STRING_RESULT_ATTRIBUTES = [\n ['summaryAction', 'gitlab_review.summary.action'],\n ['httpRequestMethod', 'http.request.method'],\n ['httpUrl', 'url.full'],\n ['serverAddress', 'server.address'],\n] as const satisfies ReadonlyArray<readonly [keyof DiagnosticContext, string]>;\n\nfunction applyResultAttributes(span: Span, ctx: DiagnosticContext): void {\n for (const [field, attr] of NUMERIC_RESULT_ATTRIBUTES) {\n const value = ctx[field];\n if (typeof value === 'number') span.setAttribute(attr, value);\n }\n for (const [field, attr] of STRING_RESULT_ATTRIBUTES) {\n const value = ctx[field];\n if (typeof value === 'string') span.setAttribute(attr, value);\n }\n}\n\nfunction applyGenAiAttributes(span: Span, ctx: DiagnosticContext): void {\n // OpenTelemetry GenAI semantic conventions — currently experimental, opt-in\n // via OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental.\n // Spec: https://opentelemetry.io/docs/specs/semconv/gen-ai/\n const { provider, modelId } = splitModel(ctx.model ?? '');\n if (provider) span.setAttribute('gen_ai.system', provider);\n if (modelId) {\n span.setAttribute('gen_ai.request.model', modelId);\n span.setAttribute('gen_ai.response.model', modelId);\n }\n span.setAttribute('gen_ai.operation.name', 'invoke_agent');\n span.setAttribute('gen_ai.agent.name', 'code-review');\n\n const usage = ctx.usage;\n if (!usage) return;\n setTokenUsageSpanAttributes(span, usage.tokens);\n // Cost is not standardized by OTel GenAI semconv — emit under a clearly\n // namespaced custom attribute. Revisit when the spec stabilizes a cost field.\n span.setAttribute('gen_ai.usage.cost.input_usd', usage.cost.input);\n span.setAttribute('gen_ai.usage.cost.output_usd', usage.cost.output);\n span.setAttribute('gen_ai.usage.cost.cache_read_usd', usage.cost.cacheRead);\n span.setAttribute('gen_ai.usage.cost.cache_creation_usd', usage.cost.cacheWrite);\n span.setAttribute('gen_ai.usage.cost.total_usd', usage.cost.total);\n}\n\n/**\n * Records `gen_ai.client.operation.duration` for the `reviewer.run` phase.\n *\n * Token usage (`gen_ai.client.token.usage`) and cost (`gen_ai.client.cost`) are\n * intentionally NOT recorded here. They are emitted per-turn by\n * `buildAgentSubscriber` from the live agent event stream. Keeping a single\n * emission point for each metric prevents the double-count that previously\n * occurred (two Prometheus series per run, one with and one without\n * `gen_ai_system`, summing to 2× the real value in Grafana).\n */\nfunction recordGenAiMetrics(\n durationHist: Histogram,\n ctx: DiagnosticContext,\n isError: boolean,\n ciAttrs: Record<string, string> = {},\n): void {\n const { provider, modelId } = splitModel(ctx.model ?? '');\n // gen_ai.request.model only (gen_ai.response.model belongs on spans, not metrics).\n const attrs: Attributes = {\n 'gen_ai.operation.name': 'invoke_agent',\n ...REVIEW_SERVICE_ATTRS,\n ...ciAttrs,\n ...genAiModelAttrs(provider, modelId),\n };\n if (isError) {\n attrs['error.type'] = errorTypeOf(ctx);\n }\n if (typeof ctx.durationMs === 'number') {\n durationHist.record(ctx.durationMs / 1000, attrs);\n }\n}\n","import type { Side } from './types.js';\n\n/**\n * A diff line resolved to the old/new line numbers it maps to. GitLab consumes\n * both (`old_line`/`new_line`); GitHub consumes one plus a `side`. Shared by both\n * platforms so line resolution lives in one place.\n */\nexport interface ResolvedDiffLine {\n newLine?: number;\n oldLine?: number;\n}\n\n/**\n * Normalizes the path captured from a `---`/`+++` diff header. git tab-\n * terminates the path when the filename contains a space (and may append a\n * timestamp after the tab), so the captured group can be `my file.ts\\t` or\n * `file.ts\\t2026-...`. Cut at the first tab to recover the bare path. The\n * `/dev/null` sentinel (added/deleted file) arrives as `undefined` from the\n * regex alternation.\n */\nfunction stripDiffPathSuffix(captured: string | undefined): string {\n return captured?.split('\\t')[0] ?? '/dev/null';\n}\n\nfunction parseHunkHeader(line: string): { oldLine: number; newLine: number } | null {\n const match = line.match(/^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/);\n if (!match) return null;\n return { oldLine: Number(match[1]), newLine: Number(match[2]) };\n}\n\n/**\n * Classify a target line in a unified diff into the old/new line numbers it maps\n * to, tracking which side(s) apply:\n * - an **added** line resolves to `newLine` only,\n * - a **removed** line resolves to `oldLine` only,\n * - an **unchanged (context)** line resolves to BOTH `oldLine` and `newLine`.\n *\n * GitLab needs both sides for context lines: a one-sided position is accepted by\n * the draft-notes API but makes `bulk_publish` return 500 (GitLab skips position\n * validation on draft creation — gitlab-org/gitlab#579609). GitHub uses a single\n * `line` + `side`, taking `newLine`/`RIGHT` or `oldLine`/`LEFT`. We feed the\n * reviewer ±20 lines of context, so it routinely anchors comments on unchanged\n * lines; resolving them here is what lets both platforms place the comment.\n *\n * Returns `null` when the line is not part of the diff for `file`, so the caller\n * can fall back (GitLab) or drop the comment (GitHub avoids a 422 on off-diff\n * lines) rather than emit a position the platform cannot place.\n */\nexport function resolveDiffLine(\n diff: string,\n file: string,\n target: number,\n side: Side,\n): ResolvedDiffLine | null {\n const lines = diff.split('\\n');\n let oldPath = '';\n let newPath = '';\n\n for (let i = 0; i < lines.length; i += 1) {\n const text = lines[i];\n if (text.startsWith('diff --git ')) {\n oldPath = '';\n newPath = '';\n continue;\n }\n const oldMatch = text.match(/^--- (?:a\\/(.*)|\\/dev\\/null)$/);\n if (oldMatch) oldPath = stripDiffPathSuffix(oldMatch[1]);\n const newMatch = text.match(/^\\+\\+\\+ (?:b\\/(.*)|\\/dev\\/null)$/);\n if (newMatch) newPath = stripDiffPathSuffix(newMatch[1]);\n\n if (!text.startsWith('@@') || (oldPath !== file && newPath !== file)) continue;\n const header = parseHunkHeader(text);\n if (!header) continue;\n\n let oldLine = header.oldLine;\n let newLine = header.newLine;\n for (let j = i + 1; j < lines.length; j += 1) {\n const body = lines[j];\n if (body.startsWith('@@') || body.startsWith('diff --git ')) break;\n const prefix = body[0] ?? ' ';\n if (side === 'RIGHT' && prefix !== '-' && newLine === target) {\n // Added line → new_line only; context line → both sides.\n return prefix === '+' ? { newLine } : { newLine, oldLine };\n }\n if (side === 'LEFT' && prefix !== '+' && oldLine === target) {\n // Removed line → old_line only; context line → both sides.\n return prefix === '-' ? { oldLine } : { oldLine, newLine };\n }\n if (prefix !== '+') oldLine += 1;\n if (prefix !== '-') newLine += 1;\n }\n }\n\n return null;\n}\n\n/** Context lines GitHub renders around each hunk change in a PR diff by default. */\nexport const GITHUB_DIFF_CONTEXT = 3;\n\n/**\n * True when the context line `target` (on `side`) is within `maxDistance` rows\n * of the nearest added/removed line in its hunk.\n *\n * The reviewer runs against our LOCAL merge diff, generated with a wide context\n * (`--unified=20`, see `git.ts`), so it routinely anchors findings on unchanged\n * lines up to 20 rows from a change. GitHub's PR diff, however, shows only 3\n * context lines: a review comment on a line outside GitHub's diff gets a 422,\n * and because the review is posted as one atomic batch, a single 422 sinks every\n * inline comment. This lets the GitHub payload builder drop such an off-diff\n * context line (returning `null`) instead of 422-ing the whole review.\n *\n * Only meaningful for context lines (added/removed lines are always inside\n * GitHub's diff); the caller checks that before calling.\n */\nexport function contextLineWithinGitHubDiff(\n diff: string,\n file: string,\n target: number,\n side: Side,\n maxDistance = GITHUB_DIFF_CONTEXT,\n): boolean {\n const lines = diff.split('\\n');\n let oldPath = '';\n let newPath = '';\n\n for (let i = 0; i < lines.length; i += 1) {\n const text = lines[i];\n if (text.startsWith('diff --git ')) {\n oldPath = '';\n newPath = '';\n continue;\n }\n const oldMatch = text.match(/^--- (?:a\\/(.*)|\\/dev\\/null)$/);\n if (oldMatch) oldPath = stripDiffPathSuffix(oldMatch[1]);\n const newMatch = text.match(/^\\+\\+\\+ (?:b\\/(.*)|\\/dev\\/null)$/);\n if (newMatch) newPath = stripDiffPathSuffix(newMatch[1]);\n\n if (!text.startsWith('@@') || (oldPath !== file && newPath !== file)) continue;\n const header = parseHunkHeader(text);\n if (!header) continue;\n\n // Collect the hunk body so we can measure the row-distance from the target\n // context line to the nearest added/removed line.\n const rows: { oldLine?: number; newLine?: number; changed: boolean }[] = [];\n let oldLine = header.oldLine;\n let newLine = header.newLine;\n for (let j = i + 1; j < lines.length; j += 1) {\n const body = lines[j];\n if (body.startsWith('@@') || body.startsWith('diff --git ')) break;\n const prefix = body[0] ?? ' ';\n const isAdd = prefix === '+';\n const isDel = prefix === '-';\n rows.push({\n oldLine: isAdd ? undefined : oldLine,\n newLine: isDel ? undefined : newLine,\n changed: isAdd || isDel,\n });\n if (!isAdd) oldLine += 1;\n if (!isDel) newLine += 1;\n }\n\n const index = rows.findIndex((row) =>\n side === 'LEFT' ? row.oldLine === target : row.newLine === target,\n );\n if (index === -1) continue;\n\n for (let distance = 1; distance <= maxDistance; distance += 1) {\n if (rows[index - distance]?.changed || rows[index + distance]?.changed) return true;\n }\n return false;\n }\n\n return false;\n}\n","import { type ResolvedDiffLine, resolveDiffLine } from './diff-lines.js';\nimport { appendFingerprintMarkers, extractDiffHunkContext, fingerprints } from './fingerprints.js';\nimport { PRODUCT_LINK } from './product.js';\nimport type {\n Confidence,\n DiffRefs,\n GeneratedComment,\n GitLabDiscussionPayload,\n ReviewComment,\n} from './types.js';\n\n// Re-exported for backward compatibility: the diff line-resolver now lives in\n// `diff-lines.ts` so both the GitLab and GitHub platforms can share it.\nexport { type ResolvedDiffLine, resolveDiffLine } from './diff-lines.js';\n\ndeclare const __PKG_VERSION__: string;\n\nconst CONVENTIONAL_TITLE_RE = /^[a-z]+(?:\\s*\\([^)]+\\))?:\\s.+$/;\n\nfunction boldCommentTitle(body: string): string {\n const newlineIndex = body.indexOf('\\n');\n const firstLine = newlineIndex === -1 ? body : body.slice(0, newlineIndex);\n if (!CONVENTIONAL_TITLE_RE.test(firstLine)) return body;\n const rest = newlineIndex === -1 ? '' : body.slice(newlineIndex);\n return `**${firstLine}**${rest}`;\n}\n\n/**\n * Builds the visible body of an inline comment.\n *\n * Layout:\n * <bold Conventional Comment title>\n * <discussion>\n *\n * _Confidence: <level>._\n *\n * ---\n *\n * <commit footer>\n *\n * The confidence line sits between the reviewer's discussion and the\n * horizontal rule so developers can see the reviewer's certainty without\n * scrolling past the footer. The footer mirrors the format used in the\n * MR-level summary note.\n *\n * Neither the confidence line nor the footer is included in the fingerprint\n * hash, so comment identity (deduplication) remains stable across commits\n * even when the SHA in the footer changes or the reviewer revises its\n * confidence judgment.\n */\nexport function buildCommentBody(body: string, commitSha: string, confidence: Confidence): string {\n const confidenceLine = `_Confidence: ${confidence}._`;\n const footer = `<sub>Reviewed by ${PRODUCT_LINK} v${__PKG_VERSION__} for commit ${commitSha}.</sub>`;\n return `${boldCommentTitle(body.trim())}\\n\\n${confidenceLine}\\n\\n---\\n\\n${footer}`;\n}\n\nexport function buildPayload(\n comment: ReviewComment,\n body: string,\n refs: DiffRefs,\n resolved?: ResolvedDiffLine | null,\n): GitLabDiscussionPayload {\n // Prefer the diff-resolved sides (two-sided for context lines). Fall back to\n // the legacy one-sided position only when the line cannot be located in the\n // diff, so an unresolvable line behaves as before rather than being dropped.\n const lineFields = resolved\n ? {\n ...(resolved.oldLine !== undefined ? { old_line: resolved.oldLine } : {}),\n ...(resolved.newLine !== undefined ? { new_line: resolved.newLine } : {}),\n }\n : comment.side === 'LEFT'\n ? { old_line: comment.line }\n : { new_line: comment.line };\n return {\n body,\n position: {\n position_type: 'text',\n base_sha: refs.base_sha,\n start_sha: refs.start_sha,\n head_sha: refs.head_sha,\n old_path: comment.file,\n new_path: comment.file,\n ...lineFields,\n },\n };\n}\n\nexport function buildGeneratedComments(\n comments: ReviewComment[],\n diff: string,\n refs: DiffRefs,\n existingFingerprints: Set<string>,\n): GeneratedComment<GitLabDiscussionPayload>[] {\n const seen = new Set(existingFingerprints);\n\n return comments.map((comment) => {\n const hunk = extractDiffHunkContext(diff, comment.file, comment.line, comment.side);\n const fp = fingerprints(comment, hunk);\n const duplicate = seen.has(fp.primary) || seen.has(fp.secondary);\n seen.add(fp.primary);\n seen.add(fp.secondary);\n\n // Fingerprints are computed from comment.body (the raw reviewer output)\n // before the commit footer is appended, so deduplication is unaffected by\n // the SHA changing between review runs.\n const bodyWithFooter = buildCommentBody(comment.body, refs.head_sha, comment.confidence);\n const resolved = resolveDiffLine(diff, comment.file, comment.line, comment.side);\n\n return {\n comment,\n fingerprints: fp,\n duplicate,\n payload: buildPayload(comment, appendFingerprintMarkers(bodyWithFooter, fp), refs, resolved),\n };\n });\n}\n","import { contextLineWithinGitHubDiff, resolveDiffLine } from '../diff-lines.js';\nimport { ConfigError, GitHubApiError } from '../errors.js';\nimport { appendFingerprintMarkers, extractDiffHunkContext, fingerprints } from '../fingerprints.js';\nimport {\n GitHubClient,\n type IssueComment,\n type PullRequest,\n type PullRequestReviewComment,\n} from '../github.js';\nimport type { Discussion, DiscussionNote, DiscussionNotePosition } from '../gitlab.js';\nimport { buildCommentBody } from '../payloads.js';\nimport type { MergeRequestMeta, ReviewPlatform, ScmResponseInfo } from '../platform.js';\nimport {\n buildUpsertSummary,\n type PostingMode,\n type PostResult,\n type SummaryResult,\n type UpsertSummaryOptions,\n} from '../posting.js';\nimport type { DiffRefs, GeneratedComment, ReviewComment, Side } from '../types.js';\n\n/**\n * A single inline comment in a batched GitHub review, positioned against the\n * diff. GitHub takes one `line` plus a `side` (unlike GitLab's two-sided\n * position). Built by {@link buildGitHubReviewPayload}; `null` when the finding\n * cannot be anchored to the diff (see {@link buildGitHubComments}).\n */\nexport interface GitHubReviewCommentPayload {\n path: string;\n body: string;\n line: number;\n side: Side;\n}\n\n/** The `owner` and `repo` halves of a GitHub `owner/repo` slug. */\nexport interface GitHubRepository {\n owner: string;\n repo: string;\n}\n\n/**\n * Split a `GITHUB_REPOSITORY` slug into its `owner` and `repo` halves. GitHub\n * repository slugs contain exactly one `/` (neither owner nor repo may be empty\n * or contain a slash), so anything else is a configuration error surfaced with an\n * actionable hint rather than a confusing 404 from the API.\n */\nexport function parseGitHubRepository(slug: string): GitHubRepository {\n const trimmed = slug.trim();\n const slash = trimmed.indexOf('/');\n if (slash <= 0 || slash !== trimmed.lastIndexOf('/') || slash === trimmed.length - 1) {\n throw new ConfigError(`Invalid GitHub repository \"${slug}\"; expected \"owner/repo\".`, {\n hint: 'Set GITHUB_REPOSITORY (or --github-repository) to an \"owner/repo\" slug, e.g. octocat/hello-world.',\n });\n }\n return { owner: trimmed.slice(0, slash), repo: trimmed.slice(slash + 1) };\n}\n\n/**\n * Parse a pull-request number from its string form. Rejects non-integer or\n * non-positive values so a malformed `--pr`/`GITHUB_REF` fails fast with a hint\n * instead of hitting `/pulls/NaN`.\n */\nexport function parseGitHubPullNumber(value: string): number {\n const pull = Number(value.trim());\n if (!Number.isInteger(pull) || pull <= 0) {\n throw new ConfigError(`Invalid GitHub pull-request number \"${value}\".`, {\n hint: 'The pull-request number comes from the pull_request event payload, GITHUB_REF (refs/pull/N/merge), or --pr; it must be a positive integer.',\n });\n }\n return pull;\n}\n\n/** What a {@link GitHubPlatform} needs to talk to one pull request. */\nexport interface GitHubPlatformOptions {\n /** REST API base (default `https://api.github.com`; GHE sets an `/api/v3` base). */\n apiUrl?: string;\n token: string;\n owner: string;\n repo: string;\n /** Pull-request number. */\n pull: number;\n fetchImpl?: typeof fetch;\n requestTimeout?: number;\n}\n\n/**\n * Resolve one reviewer finding to a GitHub review-comment payload, or `null`\n * when it cannot be anchored to GitHub's diff. GitHub uses a single `line` +\n * `side`:\n * - a pure added line takes the new-file line with `side: 'RIGHT'`,\n * - a pure removed line takes the old-file line with `side: 'LEFT'`,\n * - an unchanged (context) line takes the new-file line with `side: 'RIGHT'`,\n * regardless of the finding's side — GitHub's API prescribes RIGHT for\n * \"unchanged lines ... shown for context\", so a LEFT context comment is\n * doc-noncompliant and risks a 422.\n *\n * Returning `null` for an off-diff line is deliberate: GitHub answers 422 when a\n * review comment points at a line outside the diff, and one such comment fails\n * the whole batched review, so the poster drops these rather than post them. A\n * context line is only kept when it lies within GitHub's default 3-line hunk\n * context of a change — our local diff carries far more context (`--unified=20`)\n * than GitHub's PR diff, so a distant context line is inside our diff but off\n * GitHub's.\n */\nexport function buildGitHubReviewPayload(\n comment: ReviewComment,\n body: string,\n diff: string,\n): GitHubReviewCommentPayload | null {\n const resolved = resolveDiffLine(diff, comment.file, comment.line, comment.side);\n if (!resolved) return null;\n // A context line resolves to BOTH sides. GitHub only shows context lines close\n // to a change and wants them on the RIGHT side.\n if (resolved.oldLine !== undefined && resolved.newLine !== undefined) {\n if (!contextLineWithinGitHubDiff(diff, comment.file, comment.line, comment.side)) return null;\n return { path: comment.file, body, line: resolved.newLine, side: 'RIGHT' };\n }\n if (comment.side === 'LEFT') {\n if (resolved.oldLine === undefined) return null;\n return { path: comment.file, body, line: resolved.oldLine, side: 'LEFT' };\n }\n if (resolved.newLine === undefined) return null;\n return { path: comment.file, body, line: resolved.newLine, side: 'RIGHT' };\n}\n\n/**\n * Turn parsed reviewer findings into GitHub review-comment payloads, mirroring\n * the GitLab `buildGeneratedComments` contract: same hunk-context fingerprints,\n * same dedup semantics, same comment body/footer. The payload is\n * `GitHubReviewCommentPayload | null` — `null` for a finding that cannot be\n * placed on the diff, which the poster skips.\n */\nexport function buildGitHubComments(\n comments: ReviewComment[],\n diff: string,\n refs: DiffRefs,\n existingFingerprints: Set<string>,\n): GeneratedComment<GitHubReviewCommentPayload | null>[] {\n const seen = new Set(existingFingerprints);\n\n return comments.map((comment) => {\n const hunk = extractDiffHunkContext(diff, comment.file, comment.line, comment.side);\n const fp = fingerprints(comment, hunk);\n const duplicate = seen.has(fp.primary) || seen.has(fp.secondary);\n seen.add(fp.primary);\n seen.add(fp.secondary);\n\n // Fingerprints hash comment.body (raw reviewer output) before the footer is\n // appended, so dedup is stable across runs even as the commit SHA changes.\n const bodyWithFooter = buildCommentBody(comment.body, refs.head_sha, comment.confidence);\n const payload = buildGitHubReviewPayload(\n comment,\n appendFingerprintMarkers(bodyWithFooter, fp),\n diff,\n );\n\n return { comment, fingerprints: fp, duplicate, payload };\n });\n}\n\n/**\n * Map a positioned GitHub review comment onto a normalized {@link DiscussionNote}\n * position. GitHub carries a single `path` for the file, so both sides get it;\n * only the line side matching the comment is populated. This mirrors the GitLab\n * shape the shared helpers (`extractPriorThreads`, reviewed-commit scan) expect:\n * `positionFile` prefers `new_path`, `positionLine` prefers `new_line`.\n */\nfunction reviewCommentPosition(comment: PullRequestReviewComment): DiscussionNotePosition {\n const line = comment.line ?? comment.original_line ?? null;\n const path = comment.path ?? null;\n const isLeft = (comment.side ?? 'RIGHT').toUpperCase() === 'LEFT';\n return {\n old_path: path,\n new_path: path,\n ...(isLeft ? { old_line: line } : { new_line: line }),\n };\n}\n\n/**\n * Normalize GitHub's two comment streams into the {@link Discussion}[] shape the\n * platform-agnostic helpers consume unchanged:\n * - Inline **review comments** are threaded by `in_reply_to_id` (a reply joins\n * its root comment's discussion, in arrival order) so `extractPriorThreads`\n * sees a bot note followed by human replies, exactly like GitLab.\n * - Non-positional **issue comments** each become a single-note discussion;\n * the MR-level summary note lives here and is found via its summary marker.\n *\n * The fingerprint markers, summary marker, and reviewed-commit footer are HTML\n * comments that render identically on GitHub, so `extractExistingFingerprints`,\n * `findExistingSummaryNote`, and the reviewed-commit scan all work as-is.\n *\n * `resolvedCommentIds` carries the database ids of comments in resolved review\n * threads (from the GraphQL `reviewThreads` query, since REST omits resolution),\n * so each note gets a `resolved` flag mirroring GitLab's per-note field.\n */\nexport function normalizeGitHubDiscussions(\n reviewComments: PullRequestReviewComment[],\n issueComments: IssueComment[],\n resolvedCommentIds: Set<number> = new Set(),\n): Discussion[] {\n const threads = new Map<number, DiscussionNote[]>();\n const order: number[] = [];\n\n for (const comment of reviewComments) {\n const rootId = comment.in_reply_to_id ?? comment.id;\n let notes = threads.get(rootId);\n if (!notes) {\n notes = [];\n threads.set(rootId, notes);\n order.push(rootId);\n }\n notes.push({\n id: comment.id,\n body: comment.body ?? '',\n // GitHub's REST comments carry no resolution state; it comes from the\n // GraphQL `reviewThreads` query, keyed by comment database id. A resolved\n // thread marks all its comments, so any note being resolved flags the\n // whole thread for the shared `notes.some((n) => n.resolved)` checks.\n resolved: resolvedCommentIds.has(comment.id),\n position: reviewCommentPosition(comment),\n });\n }\n\n const discussions: Discussion[] = order.map((rootId) => ({ notes: threads.get(rootId) ?? [] }));\n\n for (const comment of issueComments) {\n discussions.push({ notes: [{ id: comment.id, body: comment.body ?? '' }] });\n }\n\n return discussions;\n}\n\n/**\n * {@link ReviewPlatform} backed by the GitHub pull-request API. Positioned\n * findings post as ONE batched review (`event: 'COMMENT'`) — atomic and free of\n * per-comment secondary rate limits — and the MR-level summary is upserted as a\n * single issue comment. The review core is unchanged; only target\n * identification, reading existing comments, and posting differ from GitLab.\n */\nexport class GitHubPlatform implements ReviewPlatform {\n private readonly client: GitHubClient;\n private readonly owner: string;\n private readonly repo: string;\n private readonly pull: number;\n // Captures the most recent GitHub HTTP response so a traced phase can stamp\n // HTTP semconv attributes onto its span. Each request reports a fresh object.\n private last: ScmResponseInfo | undefined;\n // The pull request is fetched once and reused for both branch metadata and the\n // head SHA (GitHub returns both from one endpoint). Memoized so the two phases\n // do not double-fetch.\n private pullRequestPromise: Promise<PullRequest> | undefined;\n // Reviewed commit id for the batched review's `commit_id`, captured from the\n // resolved refs (`head_sha`) before `postComments` runs.\n private commitId: string | undefined;\n\n constructor(options: GitHubPlatformOptions) {\n this.owner = options.owner;\n this.repo = options.repo;\n this.pull = options.pull;\n this.client = new GitHubClient({\n apiUrl: options.apiUrl,\n token: options.token,\n fetchImpl: options.fetchImpl,\n requestTimeout: options.requestTimeout,\n onResponse: (info) => {\n this.last = info;\n },\n });\n }\n\n lastResponse(): ScmResponseInfo | undefined {\n return this.last;\n }\n\n private pullRequest(): Promise<PullRequest> {\n if (!this.pullRequestPromise) {\n this.pullRequestPromise = this.client.getPullRequest(this.owner, this.repo, this.pull);\n }\n return this.pullRequestPromise;\n }\n\n async getMergeRequest(): Promise<MergeRequestMeta> {\n const pr = await this.pullRequest();\n return {\n source_branch: pr.head.ref,\n target_branch: pr.base.ref,\n title: pr.title,\n description: pr.body,\n };\n }\n\n async getRefs(): Promise<DiffRefs> {\n const pr = await this.pullRequest();\n // GitHub reviews are positioned against a single commit id, not a base/start\n // pair; head SHA is that commit. base/start are unused by the GitHub poster\n // but populated so the DiffRefs shape stays uniform across platforms.\n const refs: DiffRefs = {\n base_sha: pr.base.sha,\n start_sha: pr.base.sha,\n head_sha: pr.head.sha,\n };\n this.commitId = refs.head_sha;\n return refs;\n }\n\n async getDiscussions(): Promise<Discussion[]> {\n const [reviewComments, issueComments, resolvedCommentIds] = await Promise.all([\n this.client.listReviewComments(this.owner, this.repo, this.pull),\n this.client.listIssueComments(this.owner, this.repo, this.pull),\n this.client.listResolvedReviewCommentIds(this.owner, this.repo, this.pull),\n ]);\n return normalizeGitHubDiscussions(reviewComments, issueComments, resolvedCommentIds);\n }\n\n buildComments(\n comments: ReviewComment[],\n diff: string,\n refs: DiffRefs,\n existingFingerprints: Set<string>,\n ): GeneratedComment[] {\n this.commitId = refs.head_sha;\n return buildGitHubComments(comments, diff, refs, existingFingerprints);\n }\n\n // GitHub has no draft-then-publish flow: a batched review is already atomic, so\n // `mode` is accepted for interface parity but does not change behavior.\n async postComments(generated: GeneratedComment[], _mode: PostingMode): Promise<PostResult> {\n const comments = generated\n .filter((item) => !item.duplicate)\n .map((item) => item.payload as GitHubReviewCommentPayload | null)\n .filter((payload): payload is GitHubReviewCommentPayload => payload !== null)\n .map(({ path, body, line, side }) => ({ path, body, line, side }));\n\n if (comments.length === 0) return { posted: 0 };\n\n if (this.commitId === undefined) {\n throw new GitHubApiError('Cannot post a GitHub review before the head commit is resolved', {\n method: 'POST',\n path: `/repos/${this.owner}/${this.repo}/pulls/${this.pull}/reviews`,\n hint: 'Call getRefs()/buildComments() to resolve refs before postComments().',\n });\n }\n const commitId = this.commitId;\n\n try {\n await this.client.createReview(this.owner, this.repo, this.pull, {\n commit_id: commitId,\n event: 'COMMENT',\n comments,\n });\n return { posted: comments.length };\n } catch (batchError) {\n // GitHub 422s the ENTIRE batched review if a single comment lands off its\n // diff, losing every finding. We already guard positions locally, but as\n // defense-in-depth (mirroring the GitLab draft `publishFailed` fallback)\n // retry each comment as its own single-comment review so valid findings\n // still land and only the rejected ones are dropped. Re-throw the original\n // error when every retry also fails, so a genuinely broken run surfaces.\n if (!(batchError instanceof GitHubApiError) || batchError.status !== 422) throw batchError;\n const results = await Promise.allSettled(\n comments.map((comment) =>\n this.client.createReview(this.owner, this.repo, this.pull, {\n commit_id: commitId,\n event: 'COMMENT',\n comments: [comment],\n }),\n ),\n );\n const posted = results.filter((result) => result.status === 'fulfilled').length;\n if (posted === 0) throw batchError;\n return { posted };\n }\n }\n\n async upsertSummary(\n summary: string,\n discussions: Discussion[],\n options: UpsertSummaryOptions,\n ): Promise<SummaryResult> {\n const { body, existing } = buildUpsertSummary(summary, discussions, options);\n if (existing) {\n await this.client.updateIssueComment(this.owner, this.repo, existing.id, body);\n return { action: 'updated', noteId: existing.id };\n }\n const created = await this.client.createIssueComment(this.owner, this.repo, this.pull, body);\n return { action: 'created', noteId: created.id };\n }\n}\n","import type { GitLabAuthHeader } from './config.js';\nimport { GitLabApiError } from './errors.js';\n\n/**\n * Metadata about a single completed HTTP request, reported to `onResponse`.\n * Deliberately telemetry-agnostic: the OTel bridge maps these onto HTTP\n * semantic-convention span attributes, but the client itself has no OTel\n * dependency. Carries no secrets — the token lives in a request header, not\n * the URL.\n */\nexport interface GitLabResponseInfo {\n method: string;\n path: string;\n url: string;\n status: number;\n /** Parsed Content-Length header in bytes, when the response provided one. */\n responseContentLength?: number;\n}\n\nexport interface GitLabClientOptions {\n gitlabUrl: string;\n token: string;\n authHeader?: GitLabAuthHeader;\n fetchImpl?: typeof fetch;\n requestTimeout?: number;\n /**\n * Optional instrumentation callback invoked once per completed HTTP response\n * (success or error status), before any error is thrown. Used to surface HTTP\n * metadata to diagnostics/OTel without coupling the client to those layers.\n */\n onResponse?: (info: GitLabResponseInfo) => void;\n}\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 60_000;\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError';\n}\n\nexport interface MergeRequest {\n source_branch: string;\n target_branch: string;\n /** MR title — the one-line declared intent of the change. May be empty. */\n title?: string;\n /** MR description — the author's full reasoning / decision log. May be empty or null. */\n description?: string | null;\n}\n\nexport interface Version {\n base_commit_sha: string;\n start_commit_sha: string;\n head_commit_sha: string;\n}\n\nexport interface DiscussionNotePosition {\n old_path?: string | null;\n new_path?: string | null;\n old_line?: number | null;\n new_line?: number | null;\n}\n\nexport interface DiscussionNote {\n id?: number;\n body?: string | null;\n system?: boolean;\n resolved?: boolean;\n position?: DiscussionNotePosition;\n}\n\nexport interface Discussion {\n notes: DiscussionNote[];\n}\n\nexport interface MergeRequestNote {\n id: number;\n body: string;\n}\n\nexport interface DraftNote {\n id: number;\n author_id: number;\n note: string;\n}\n\nexport interface CurrentUser {\n id: number;\n}\n\nexport class GitLabClient {\n private readonly base: string;\n private readonly token: string;\n private readonly authHeader: GitLabAuthHeader;\n private readonly fetchImpl: typeof fetch;\n private readonly requestTimeout: number;\n private readonly onResponse?: (info: GitLabResponseInfo) => void;\n\n constructor(options: GitLabClientOptions) {\n this.base = options.gitlabUrl.replace(/\\/$/, '');\n this.token = options.token;\n this.authHeader = options.authHeader ?? 'PRIVATE-TOKEN';\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.requestTimeout = options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;\n this.onResponse = options.onResponse;\n }\n\n private reportResponse(method: string, path: string, url: string, response: Response): void {\n if (!this.onResponse) return;\n const header = response.headers.get('content-length');\n // Treat a present-but-blank header as absent: Number('') / Number(' ') are 0\n // (finite), which would otherwise be reported as a real body size of 0.\n const length = header !== null && header.trim() !== '' ? Number(header) : NaN;\n this.onResponse({\n method,\n path,\n url,\n status: response.status,\n responseContentLength: Number.isFinite(length) ? length : undefined,\n });\n }\n\n url(path: string, query: Record<string, string | number | boolean | undefined> = {}): string {\n const url = new URL(`${this.base}/api/v4${path}`);\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n return url.toString();\n }\n\n private headers(headers?: Record<string, string>): Record<string, string> {\n return {\n [this.authHeader]: this.token,\n Accept: 'application/json',\n ...headers,\n };\n }\n\n private async fetchWithTimeout(\n url: string,\n init: RequestInit,\n method: string,\n path: string,\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.requestTimeout);\n try {\n const response = await this.fetchImpl(url, { ...init, signal: controller.signal });\n this.reportResponse(method, path, url, response);\n return response;\n } catch (error) {\n if (isAbortError(error)) {\n throw new GitLabApiError(\n `GitLab API ${method} ${path} timed out after ${this.requestTimeout}ms`,\n {\n method,\n path,\n timeout: true,\n hint: 'Check GitLab API availability or increase requestTimeout.',\n },\n );\n }\n throw error;\n } finally {\n clearTimeout(timer);\n }\n }\n\n async request<T>(\n path: string,\n init: RequestInit = {},\n query: Record<string, string | number | boolean | undefined> = {},\n ): Promise<T> {\n const response = await this.fetchWithTimeout(\n this.url(path, query),\n {\n ...init,\n headers: this.headers({\n ...(init.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...(init.headers as Record<string, string> | undefined),\n }),\n },\n init.method ?? 'GET',\n path,\n );\n\n if (!response.ok) {\n const responseBody = await response.text();\n throw new GitLabApiError(\n `GitLab API ${init.method ?? 'GET'} ${path} failed: ${response.status} ${response.statusText}`,\n {\n method: init.method ?? 'GET',\n path,\n status: response.status,\n responseBody,\n hint: 'Check the GitLab URL, token permissions, project ID/path, and merge request IID.',\n },\n );\n }\n\n if (response.status === 204) return undefined as T;\n const text = await response.text();\n if (!text) return undefined as T;\n return JSON.parse(text) as T;\n }\n\n async paginate<T>(\n path: string,\n query: Record<string, string | number | boolean | undefined> = {},\n ): Promise<T[]> {\n const items: T[] = [];\n let page = 1;\n\n while (true) {\n const response = await this.fetchWithTimeout(\n this.url(path, { ...query, per_page: 100, page }),\n { headers: this.headers() },\n 'GET',\n path,\n );\n\n if (!response.ok) {\n const responseBody = await response.text();\n throw new GitLabApiError(\n `GitLab API GET ${path} failed: ${response.status} ${response.statusText}`,\n {\n method: 'GET',\n path,\n status: response.status,\n responseBody,\n hint: 'Check the GitLab URL, token permissions, project ID/path, and merge request IID.',\n },\n );\n }\n\n const body = (await response.json()) as unknown;\n if (!Array.isArray(body)) {\n throw new GitLabApiError(`GitLab API GET ${path} returned a non-array paginated response`, {\n method: 'GET',\n path,\n hint: 'The GitLab API response shape was unexpected.',\n });\n }\n items.push(...(body as T[]));\n\n const next = response.headers.get('x-next-page')?.trim();\n if (!next) break;\n const nextPage = Number(next);\n if (!Number.isInteger(nextPage) || nextPage <= page) {\n throw new GitLabApiError(\n `GitLab API GET ${path} returned invalid x-next-page header: ${next}`,\n {\n method: 'GET',\n path,\n hint: 'The GitLab API pagination headers were unexpected.',\n },\n );\n }\n page = nextPage;\n }\n\n return items;\n }\n\n getMergeRequest(project: string, mr: string): Promise<MergeRequest> {\n return this.request(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}`,\n );\n }\n\n async getLatestVersion(project: string, mr: string): Promise<Version> {\n const versions = await this.paginate<Version>(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/versions`,\n );\n if (!versions[0])\n throw new GitLabApiError('No GitLab MR version found.', {\n method: 'GET',\n path: `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/versions`,\n hint: 'Ensure the merge request has a diff version.',\n });\n return versions[0];\n }\n\n getDiscussions(project: string, mr: string): Promise<Discussion[]> {\n return this.paginate(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/discussions`,\n );\n }\n\n postDiscussion(project: string, mr: string, payload: unknown): Promise<unknown> {\n return this.request(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/discussions`,\n { method: 'POST', body: JSON.stringify(payload) },\n );\n }\n\n createMergeRequestNote(project: string, mr: string, body: string): Promise<MergeRequestNote> {\n return this.request(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/notes`,\n { method: 'POST', body: JSON.stringify({ body }) },\n );\n }\n\n updateMergeRequestNote(\n project: string,\n mr: string,\n noteId: number,\n body: string,\n ): Promise<MergeRequestNote> {\n return this.request(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/notes/${noteId}`,\n { method: 'PUT', body: JSON.stringify({ body }) },\n );\n }\n\n getCurrentUser(): Promise<CurrentUser> {\n return this.request('/user');\n }\n\n listDraftNotes(project: string, mr: string): Promise<DraftNote[]> {\n return this.paginate(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/draft_notes`,\n );\n }\n\n createDraftNote(project: string, mr: string, payload: unknown): Promise<DraftNote> {\n // The draft notes API uses `note` instead of `body` for the comment text.\n const { body, ...rest } = payload as Record<string, unknown>;\n const draftPayload = body !== undefined ? { note: body, ...rest } : payload;\n return this.request(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/draft_notes`,\n { method: 'POST', body: JSON.stringify(draftPayload) },\n );\n }\n\n async deleteDraftNote(project: string, mr: string, id: number): Promise<void> {\n await this.request(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/draft_notes/${id}`,\n { method: 'DELETE' },\n );\n }\n\n async bulkPublishDraftNotes(project: string, mr: string): Promise<void> {\n await this.request(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/draft_notes/bulk_publish`,\n { method: 'POST' },\n );\n }\n\n async publishDraftNote(project: string, mr: string, id: number): Promise<void> {\n await this.request(\n `/projects/${encodeURIComponent(project)}/merge_requests/${encodeURIComponent(mr)}/draft_notes/${id}/publish`,\n { method: 'PUT' },\n );\n }\n}\n","import type { Config } from '../config.js';\nimport { type Discussion, GitLabClient } from '../gitlab.js';\nimport { buildGeneratedComments } from '../payloads.js';\nimport type { MergeRequestMeta, ReviewPlatform, ScmResponseInfo } from '../platform.js';\nimport {\n postGeneratedComments,\n type PostingMode,\n type PostResult,\n type SummaryResult,\n upsertSummaryNote,\n type UpsertSummaryOptions,\n} from '../posting.js';\nimport type { DiffRefs, GeneratedComment, ReviewComment } from '../types.js';\n\n/**\n * {@link ReviewPlatform} backed by the GitLab merge-request API. A thin wrapper\n * over the existing {@link GitLabClient}, `payloads.ts`, and `posting.ts`; it\n * carries no review logic of its own so GitLab behavior is byte-for-byte\n * identical to the pre-seam code path.\n */\nexport class GitLabPlatform implements ReviewPlatform {\n private readonly client: GitLabClient;\n private readonly project: string;\n private readonly mr: string;\n // Captures the most recent GitLab HTTP response so a traced phase can stamp\n // HTTP semconv attributes onto its span. Each request reports a fresh object.\n private last: ScmResponseInfo | undefined;\n\n constructor(config: Config) {\n this.project = config.project;\n this.mr = config.mr;\n this.client = new GitLabClient({\n gitlabUrl: config.gitlabUrl,\n token: config.gitlabToken,\n authHeader: config.gitlabAuthHeader,\n onResponse: (info) => {\n this.last = info;\n },\n });\n }\n\n lastResponse(): ScmResponseInfo | undefined {\n return this.last;\n }\n\n getMergeRequest(): Promise<MergeRequestMeta> {\n return this.client.getMergeRequest(this.project, this.mr);\n }\n\n async getRefs(): Promise<DiffRefs> {\n const version = await this.client.getLatestVersion(this.project, this.mr);\n return {\n base_sha: version.base_commit_sha,\n start_sha: version.start_commit_sha,\n head_sha: version.head_commit_sha,\n };\n }\n\n getDiscussions(): Promise<Discussion[]> {\n return this.client.getDiscussions(this.project, this.mr);\n }\n\n buildComments(\n comments: ReviewComment[],\n diff: string,\n refs: DiffRefs,\n existingFingerprints: Set<string>,\n ): GeneratedComment[] {\n return buildGeneratedComments(comments, diff, refs, existingFingerprints);\n }\n\n postComments(generated: GeneratedComment[], mode: PostingMode): Promise<PostResult> {\n return postGeneratedComments(this.client, this.project, this.mr, generated, mode);\n }\n\n upsertSummary(\n summary: string,\n discussions: Discussion[],\n options: UpsertSummaryOptions,\n ): Promise<SummaryResult> {\n return upsertSummaryNote(this.client, this.project, this.mr, summary, discussions, options);\n }\n}\n","import type { Config } from './config.js';\nimport type { Discussion } from './gitlab.js';\nimport {\n GitHubPlatform,\n parseGitHubPullNumber,\n parseGitHubRepository,\n} from './platforms/github.js';\nimport { GitLabPlatform } from './platforms/gitlab.js';\nimport type { PostingMode, PostResult, SummaryResult, UpsertSummaryOptions } from './posting.js';\nimport type { DiffRefs, GeneratedComment, ReviewComment } from './types.js';\n\n/**\n * The change under review, normalized across source-control platforms. Mirrors\n * the subset of a GitLab merge request (and a GitHub pull request) that the\n * review engine consumes: the branch pair plus the author's declared intent.\n */\nexport interface MergeRequestMeta {\n source_branch: string;\n target_branch: string;\n /** One-line declared intent of the change. May be empty. */\n title?: string;\n /** The author's full reasoning / decision log. May be empty or null. */\n description?: string | null;\n}\n\n/**\n * Minimal, telemetry-agnostic view of the most recent HTTP response a platform\n * issued. The diagnostics/OTel layer maps these onto HTTP semantic-convention\n * span attributes without coupling the seam to any observability SDK. Carries no\n * secrets — the auth token travels in a request header, never the URL.\n */\nexport interface ScmResponseInfo {\n method: string;\n url: string;\n status: number;\n /** Parsed Content-Length header in bytes, when the response provided one. */\n responseContentLength?: number;\n}\n\n/**\n * The seam every source-control backend implements so `run()` can drive a review\n * without knowing whether it is talking to GitLab or GitHub. The review core\n * (local-git diff, agent, prompt, skills, parser, fingerprints, dedup, summary\n * carryover) stays platform-agnostic; only target identification, reading\n * existing comments, and posting differ per platform and live behind this\n * interface.\n */\nexport interface ReviewPlatform {\n /** Fetch the change's branch pair and declared intent. */\n getMergeRequest(): Promise<MergeRequestMeta>;\n /**\n * Resolve the diff refs. On GitLab these are the MR version base/start/head\n * SHAs; `head_sha` is the reviewed commit used for the reviewed-commit skip\n * marker and the comment/summary footers.\n */\n getRefs(): Promise<DiffRefs>;\n /** Existing comments, normalized so the shared dedup/summary helpers work unchanged. */\n getDiscussions(): Promise<Discussion[]>;\n /** Turn parsed reviewer findings into platform-specific posting payloads. */\n buildComments(\n comments: ReviewComment[],\n diff: string,\n refs: DiffRefs,\n existingFingerprints: Set<string>,\n ): GeneratedComment[];\n /** Post (or draft-then-publish) the non-duplicate generated comments. */\n postComments(generated: GeneratedComment[], mode: PostingMode): Promise<PostResult>;\n /** Create or update the single MR-level summary note. */\n upsertSummary(\n summary: string,\n discussions: Discussion[],\n options: UpsertSummaryOptions,\n ): Promise<SummaryResult>;\n /**\n * The most recent HTTP response this platform issued, for telemetry stamping.\n * Returns `undefined` until the first request completes. Each request reports\n * a fresh object so a phase can compare identity and stamp only its own call.\n */\n lastResponse(): ScmResponseInfo | undefined;\n}\n\n/**\n * Build the {@link ReviewPlatform} for this run from the resolved config. The\n * platform was already selected (auto-detected from the environment or forced by\n * `--platform`/`CODE_REVIEW_PLATFORM`) during config resolution; this only\n * constructs the matching backend. GitHub target identifiers are parsed here so a\n * malformed `owner/repo` or pull number fails fast with an actionable hint.\n */\nexport function createPlatform(config: Config): ReviewPlatform {\n if (config.platform === 'github') {\n const { owner, repo } = parseGitHubRepository(config.githubRepository);\n return new GitHubPlatform({\n apiUrl: config.githubApiUrl,\n token: config.githubToken,\n owner,\n repo,\n pull: parseGitHubPullNumber(config.githubPr),\n });\n }\n return new GitLabPlatform(config);\n}\n","import { FINGERPRINT_MARKER_PATTERN } from './fingerprints.js';\nimport type { Discussion, DiscussionNote } from './gitlab.js';\nimport { isBotNote } from './prior-threads.js';\nimport type { Severity } from './types.js';\n\n// Global matcher so we can collect every fingerprint hash in a note body.\nconst FINGERPRINT_MARKER_GLOBAL_RE = new RegExp(FINGERPRINT_MARKER_PATTERN, 'gi');\n\n/**\n * A bot-posted inline finding from a previous review run whose thread is still\n * open (unresolved). Carried into the current summary so an unresolved thread\n * never silently disappears from the issue list when a later run doesn't\n * re-emit it (#92).\n */\nexport interface CarryOverFinding {\n file: string;\n line: number | null;\n severity: Severity;\n /** Conventional Comment label incl. decoration, e.g. `issue (blocking)`. */\n header: string;\n subject: string;\n /** All fingerprint hashes found in the bot note (primary + secondary). */\n hashes: string[];\n}\n\nconst RISK_RANK: Record<'Low' | 'Medium' | 'High', number> = { Low: 0, Medium: 1, High: 2 };\n\nfunction positionFile(note: DiscussionNote): string | null {\n return note.position?.new_path ?? note.position?.old_path ?? null;\n}\n\nfunction positionLine(note: DiscussionNote): number | null {\n return note.position?.new_line ?? note.position?.old_line ?? null;\n}\n\nfunction noteHashes(body: string): string[] {\n const hashes: string[] = [];\n for (const match of body.matchAll(FINGERPRINT_MARKER_GLOBAL_RE)) {\n if (match[1]) hashes.push(match[1]);\n }\n return hashes;\n}\n\nconst HEADER_RE = /^\\s*([a-z]+(?:\\s+\\([^)]+\\))?)\\s*:\\s*(.*)$/i;\n\n/** Parse the Conventional Comment header from a finding body's first line. */\nfunction parseHeader(body: string): { header: string; subject: string } {\n const first = body.replace(FINGERPRINT_MARKER_GLOBAL_RE, '').trim().split('\\n', 1)[0] ?? '';\n const match = first.match(HEADER_RE);\n if (!match) return { header: '', subject: first.trim() };\n return { header: match[1]?.trim() ?? '', subject: (match[2] ?? '').trim() };\n}\n\n/** Map a Conventional Comment header back to the severity tier it encodes. */\nfunction severityFromHeader(header: string): Severity {\n if (/^issue\\s*\\(blocking\\)/i.test(header)) return 'critical';\n if (/^issue$/i.test(header)) return 'warn';\n return 'info';\n}\n\nfunction riskForSeverity(severity: Severity): 'Low' | 'Medium' | 'High' {\n if (severity === 'critical') return 'High';\n if (severity === 'warn') return 'Medium';\n return 'Low';\n}\n\n/**\n * Extract still-open (unresolved) bot-posted inline findings from prior MR\n * discussions. A discussion qualifies when its first bot note carries fingerprint\n * markers, sits on a file position, and no note in it is resolved.\n */\nexport function extractOpenBotFindings(discussions: Discussion[]): CarryOverFinding[] {\n const findings: CarryOverFinding[] = [];\n for (const discussion of discussions) {\n const notes = discussion.notes ?? [];\n const botNote = notes.find(isBotNote);\n if (!botNote) continue;\n // Only carry forward threads that are still open.\n if (notes.some((n) => n.resolved === true)) continue;\n const file = positionFile(botNote);\n if (!file) continue;\n const body = botNote.body ?? '';\n const hashes = noteHashes(body);\n if (hashes.length === 0) continue;\n const { header, subject } = parseHeader(body);\n findings.push({\n file,\n line: positionLine(botNote),\n severity: severityFromHeader(header),\n header,\n subject,\n hashes,\n });\n }\n return findings;\n}\n\n/**\n * From the still-open prior findings, keep only those the current run did NOT\n * re-emit — i.e. none of their fingerprints appear in `currentFingerprints`.\n * A re-emitted finding is already represented (posted or deduplicated), so it\n * must not be double-listed.\n */\nexport function selectCarryOver(\n openFindings: CarryOverFinding[],\n currentFingerprints: Set<string>,\n): CarryOverFinding[] {\n return openFindings.filter((f) => !f.hashes.some((h) => currentFingerprints.has(h)));\n}\n\nfunction carryOverBullet(f: CarryOverFinding): string {\n const loc = f.line !== null ? `\\`${f.file}:${f.line}\\`` : `\\`${f.file}\\``;\n const label = f.header ? `**${f.header}** — ` : '';\n const subject = f.subject ? ` — ${f.subject}` : '';\n return `- ${label}${loc}${subject}`;\n}\n\n/**\n * Fold carried-over findings into a summary so unresolved prior threads stay\n * visible and the risk line never drops below a still-open finding's level:\n *\n * 1. Bump the `**Risk: …**` line up (never down) if a carry-over outranks it.\n * 2. Append a `**Still open from earlier reviews (N):**` block listing them.\n *\n * Returns the summary unchanged when there is nothing to carry over, so\n * single-run / first-run output is byte-identical.\n */\nexport function applyCarryOverToSummary(summary: string, carryOvers: CarryOverFinding[]): string {\n if (carryOvers.length === 0) return summary;\n\n let result = summary;\n\n // 1. Monotonic risk: bump the level up if a carry-over outranks the stated one.\n const maxLevel = carryOvers\n .map((f) => riskForSeverity(f.severity))\n .reduce((a, b) => (RISK_RANK[b] > RISK_RANK[a] ? b : a), 'Low' as 'Low' | 'Medium' | 'High');\n result = result.replace(/^(\\s*\\*\\*Risk:\\s*)(Low|Medium|High)\\b/im, (whole, prefix, current) => {\n const cur = current as 'Low' | 'Medium' | 'High';\n return RISK_RANK[maxLevel] > RISK_RANK[cur] ? `${prefix}${maxLevel}` : whole;\n });\n\n // 2. Append the still-open block.\n const noun = carryOvers.length === 1 ? 'finding' : 'findings';\n const block = [\n `**Still open from earlier reviews (${carryOvers.length} ${noun}):**`,\n ...carryOvers.map(carryOverBullet),\n ].join('\\n');\n\n return `${result.trimEnd()}\\n\\n${block}`;\n}\n\n/**\n * Convenience wrapper: extract still-open prior findings, drop the ones the\n * current run re-emitted, and fold the rest into the summary.\n */\nexport function withCarriedOverFindings(\n summary: string,\n discussions: Discussion[],\n currentFingerprints: Set<string>,\n): string {\n const carryOvers = selectCarryOver(extractOpenBotFindings(discussions), currentFingerprints);\n return applyCarryOverToSummary(summary, carryOvers);\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { Config } from './config.js';\nimport {\n applyDefaultCacheRetention,\n applyCodeReviewEnvPrefix,\n resolveConfig,\n validateConfig,\n} from './config.js';\nimport {\n createDiagnosticRunId,\n traceDiagnosticPhase,\n type DiagnosticContext,\n type DiagnosticPhase,\n} from './diagnostics.js';\nimport { formatError, isQuotaExceededError, ParseError, RuntimeError } from './errors.js';\nimport { extractExistingFingerprints } from './fingerprints.js';\nimport { getMergeCommitLog, getMergeDiff, prepareGitHistory, summarizeDiff } from './git.js';\nimport type { ReviewUsage } from './gitlab-review.js';\nimport { runReview } from './gitlab-review.js';\nimport { createLogger } from './logger.js';\nimport type { OtelBridge } from './otel.js';\nimport { startOtelBridge } from './otel.js';\nimport { parseReviewMarkdownWithWarnings } from './parser.js';\nimport { createPlatform, type ScmResponseInfo } from './platform.js';\nimport type { SummaryResult } from './posting.js';\nimport { findExistingReviewedCommitSha } from './posting.js';\nimport { extractChangedFiles, extractPriorThreads } from './prior-threads.js';\nimport { withCarriedOverFindings } from './summary-carryover.js';\nimport type { GeneratedComment, Severity, ThinkingLevel } from './types.js';\n\nexport type {\n DiagnosticContext,\n DiagnosticError,\n DiagnosticPhase,\n DiagnosticUsage,\n DiagnosticUsageBreakdown,\n} from './diagnostics.js';\nexport {\n DIAGNOSTIC_CHANNEL_NAMES,\n DIAGNOSTIC_CHANNEL_PREFIX,\n createDiagnosticContext,\n createDiagnosticRunId,\n diagnosticChannels,\n traceDiagnostic,\n traceDiagnosticPhase,\n} from './diagnostics.js';\nexport type { OtelBridge, OtelBridgeOptions, OtelRuntime } from './otel.js';\nexport { isOtelEnabled, startOtelBridge } from './otel.js';\n\nconst HELP = `Usage: code-review [options]\n\nRun code-review in GitLab CI and post deduplicated merge request discussions.\n\nOptions:\n --project <id> GitLab project ID/path (default: CI_PROJECT_ID)\n --mr <iid> Merge request IID (default: CI_MERGE_REQUEST_IID)\n --gitlab-url <url> GitLab URL (default: CI_SERVER_URL or CI_SERVER_HOST)\n --gitlab-token <token> GitLab token (default: GITLAB_TOKEN, GLAB_CLI_TOKEN, CI_JOB_TOKEN, GITLAB_PRIVATE_TOKEN)\n --api-key <key> AI API key. Required, except for providers with ambient\n credentials or local endpoints (e.g. Ollama). Resolved from the\n provider's standard env var (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY,\n GEMINI_API_KEY, OPENROUTER_API_KEY) or this flag.\n --model <provider/id> Model to use. Format: provider/modelId. Multi-slash IDs such as\n openrouter/anthropic/claude-3-opus are supported by splitting on the\n first slash. Use ollama/<model> for local Ollama models.\n (required; env: CODE_REVIEW_MODEL)\n --model-pool <list> Comma-separated provider/modelId list for heterogeneous full-depth\n review. Angles map to pool members (angle i → member i % pool size)\n and each finding is verified by a model other than its author. Each\n member resolves its own provider key; members without a key are\n dropped with a warning. Empty (default) = single model = no change.\n (env: CODE_REVIEW_MODEL_POOL)\n --base-url <url> Override the provider base URL (e.g. a custom OpenAI-compatible\n endpoint). For Ollama, set OLLAMA_HOST instead.\n (env: CODE_REVIEW_BASE_URL)\n --max-tokens <n> Override maximum output tokens for the model. 0 = model default.\n (env: CODE_REVIEW_MAX_TOKENS)\n --max-diff-chars <n> Cumulative diff char budget sent to the reviewer. Files past this\n budget are dropped and surfaced as a size-skip callout. (default: 100000)\n (env: CODE_REVIEW_MAX_DIFF_CHARS)\n --decompose-hint-lines <n>\n When > 0, an MR whose reviewed diff changes more lines than this\n threshold gets a \"consider decomposing this MR\" note in the summary.\n 0 = off (default). (env: CODE_REVIEW_DECOMPOSE_HINT_LINES)\n --diff-context <n> Lines of surrounding context per diff hunk (git --unified). More\n context aids reasoning but inflates tokens and fits fewer files in\n the budget; less fits more. 0 = built-in default (20).\n (env: CODE_REVIEW_DIFF_CONTEXT)\n --retrieve-skipped Stage diffs for files dropped by the size budget on disk so the\n reviewer can read them on demand instead of losing them.\n (env: CODE_REVIEW_RETRIEVE_SKIPPED=true)\n --min-severity <level> info, warn, or critical (default: info)\n --thinking <level> off, minimal, low, medium, high, or xhigh (default: off).\n Higher levels add billable thinking tokens at the model output rate.\n --review-depth <depth> single (one pass), verify (adversarial re-check of each\n severe finding), or full (multi-angle finders → triage →\n verify). (default: single; env: CODE_REVIEW_DEPTH)\n --verify-model <p/id> Model for the Verify stage (verify/full depth). Pairs a cheap\n finder with a strong, high-precision verifier. Warns if it looks\n cheaper than --model. Empty (default) = pool selection.\n (env: CODE_REVIEW_VERIFY_MODEL)\n --posting-mode <mode> direct (sequential discussions) or draft (atomic bulk publish)\n (default: direct)\n --review-file <path> Raw code-review output file (default: code-review.md)\n --output <path> Generated payload artifact (default: review-comments.json)\n --cwd <path> Working directory (default: process.cwd())\n --dry-run Generate artifacts and skip posting\n --no-post Generate artifacts and skip posting\n --no-summary Skip posting/updating the MR-level summary note\n (env: CODE_REVIEW_POST_SUMMARY=false)\n --force-review Run even when the current commit was already reviewed\n (env: CODE_REVIEW_FORCE_REVIEW=true)\n --verbose Enable debug-level logging\n (env: CODE_REVIEW_VERBOSE=true)\n --help, -h Show help\n --version, -v Show version\n`;\n\nexport interface RunResult {\n generated: GeneratedComment[];\n posted: number;\n usage: ReviewUsage;\n summary: SummaryResult | null;\n skipped?: boolean;\n}\n\ndeclare const __PKG_NAME__: string;\ndeclare const __PKG_VERSION__: string;\n\nfunction assertNodeVersion(): void {\n const major = Number(process.versions.node.split('.')[0]);\n if (!Number.isInteger(major) || major < 24) {\n throw new RuntimeError(\n `Node.js >=24 is required; current version is ${process.versions.node}.`,\n {\n hint: 'Use a Node 24 image/runtime in GitLab CI.',\n },\n );\n }\n}\n\nexport interface RunBridges {\n /** Pre-started OTel bridge for per-turn and per-tool-call agent telemetry. */\n otel?: OtelBridge;\n}\n\n/**\n * Write the empty/skip artifacts (comment JSON, usage, and a one-line review\n * note) for a run that produced no review — a re-reviewed commit or a provider\n * credit/quota skip. Keeps the dry-run/no-post contract: artifacts only, no\n * posting.\n */\nasync function writeSkipArtifacts(\n config: Config,\n usage: ReviewUsage,\n reviewNote: string,\n): Promise<void> {\n const outputPath = resolve(config.cwd, config.output);\n const usagePath = resolve(config.cwd, 'review-usage.json');\n const reviewPath = resolve(config.cwd, config.reviewFile);\n await mkdir(dirname(outputPath), { recursive: true });\n await mkdir(dirname(reviewPath), { recursive: true });\n await writeFile(outputPath, JSON.stringify([], null, 2), 'utf8');\n await writeFile(usagePath, JSON.stringify(usage, null, 2), 'utf8');\n await writeFile(reviewPath, reviewNote, 'utf8');\n}\n\nexport async function run(config: Config, bridges?: RunBridges): Promise<RunResult> {\n validateConfig(config);\n\n const logger = createLogger(config.verbose ? 'debug' : 'info');\n const runId = createDiagnosticRunId();\n return traceDiagnosticPhase('run', config, runId, async (runContext) => {\n // The platform owns all source-control API access (target identification,\n // reading existing comments, posting). `run()` talks only to this seam, so\n // it is agnostic to whether the backend is GitLab or GitHub.\n const platform = createPlatform(config);\n // Wraps a single-request (or paginated) platform read so the phase span gets\n // HTTP attributes on both success and error paths. The platform exposes its\n // most recent HTTP response; a phase compares the holder against its pre-call\n // value and only stamps when its own request actually produced a response.\n const tracedRead = <T>(phase: DiagnosticPhase, fn: () => Promise<T>): Promise<T> =>\n traceDiagnosticPhase(\n phase,\n config,\n runId,\n withHttpStamping(\n () => platform.lastResponse(),\n () => fn(),\n ),\n );\n\n logger.info('Fetching MR info...');\n const mr = await tracedRead('scm.get_merge_request', () => platform.getMergeRequest());\n const refs = await tracedRead('scm.get_latest_version', () => platform.getRefs());\n const initialDiscussions = await tracedRead('scm.get_discussions', () =>\n platform.getDiscussions(),\n );\n\n const reviewedCommitSha = findExistingReviewedCommitSha(initialDiscussions);\n if (\n !config.forceReview &&\n !config.dryRun &&\n !config.noPost &&\n reviewedCommitSha === refs.head_sha\n ) {\n const usage = zeroReviewUsage(config.model, config.thinkingLevel);\n runContext.usage = usage;\n runContext.generated = 0;\n runContext.newComments = 0;\n runContext.duplicateComments = 0;\n runContext.posted = 0;\n runContext.summaryAction = 'skipped';\n\n await traceDiagnosticPhase('artifact.write_output', config, runId, async (context) => {\n await writeSkipArtifacts(\n config,\n usage,\n `Skipped review: commit ${refs.head_sha} was already reviewed.\\n`,\n );\n context.generated = 0;\n context.newComments = 0;\n context.duplicateComments = 0;\n context.posted = 0;\n });\n\n console.log(\n `Skipping review: commit ${refs.head_sha} was already reviewed. Use --force-review to run again.`,\n );\n return { generated: [], posted: 0, usage, summary: null, skipped: true };\n }\n\n logger.info('Fetching diff...');\n await traceDiagnosticPhase('git.prepare_history', config, runId, () =>\n prepareGitHistory(mr.source_branch, mr.target_branch, { cwd: config.cwd }),\n );\n const diff = await traceDiagnosticPhase(\n 'git.get_merge_diff',\n config,\n runId,\n async (context) => {\n const merged = await getMergeDiff(mr.target_branch, {\n cwd: config.cwd,\n ...(config.diffContext > 0 ? { context: config.diffContext } : {}),\n });\n const summary = summarizeDiff(merged);\n context.diffFilesChanged = summary.filesChanged;\n context.diffLinesAdded = summary.linesAdded;\n context.diffLinesRemoved = summary.linesRemoved;\n return merged;\n },\n );\n const commitLog = await traceDiagnosticPhase('git.get_commit_log', config, runId, () =>\n getMergeCommitLog(mr.target_branch, { cwd: config.cwd }),\n );\n const changedFiles = extractChangedFiles(diff);\n const priorThreads = extractPriorThreads(initialDiscussions, changedFiles);\n if (priorThreads.length > 0) {\n logger.info(\n `Found ${priorThreads.length} prior thread(s) with developer replies — including as context.`,\n );\n }\n\n logger.info('Running review...');\n let usage: ReviewUsage;\n try {\n usage = await traceDiagnosticPhase('reviewer.run', config, runId, async (context) => {\n const result = await runReview(config, {\n cwd: config.cwd,\n diff,\n commitLog,\n priorThreads,\n intent: { title: mr.title, description: mr.description },\n logger,\n // Subscribe the OTel bridge to the agent's event stream so per-turn\n // and per-tool-call spans/metrics fire in real time.\n attachTelemetry: bridges?.otel?.createAgentTelemetry(runId),\n });\n context.usage = result;\n return result;\n });\n } catch (error) {\n // A provider credit/quota exhaustion (e.g. HTTP 402) means the review\n // could not run for reasons outside the MR's control. Warn and skip\n // rather than failing the pipeline, so a billing dead-end does not block\n // every MR. Any other error still propagates and fails the job.\n if (!isQuotaExceededError(error)) throw error;\n const skipUsage = zeroReviewUsage(config.model, config.thinkingLevel);\n runContext.usage = skipUsage;\n runContext.generated = 0;\n runContext.newComments = 0;\n runContext.duplicateComments = 0;\n runContext.posted = 0;\n runContext.summaryAction = 'skipped';\n await traceDiagnosticPhase('artifact.write_output', config, runId, async (context) => {\n await writeSkipArtifacts(\n config,\n skipUsage,\n 'Skipped review: model provider out of credits/quota.\\n',\n );\n context.generated = 0;\n context.newComments = 0;\n context.duplicateComments = 0;\n context.posted = 0;\n });\n const detail = error instanceof Error ? error.message : String(error);\n console.warn(\n `[code-review] Skipping review: model provider out of credits/quota — not failing the pipeline. (${detail})`,\n );\n return { generated: [], posted: 0, usage: skipUsage, summary: null, skipped: true };\n }\n runContext.usage = usage;\n\n const reviewPath = resolve(config.cwd, config.reviewFile);\n const { parsed } = await traceDiagnosticPhase(\n 'review.parse',\n config,\n runId,\n async (context) => {\n const review = await readFile(reviewPath, 'utf8');\n const result = parseReviewMarkdownWithWarnings(review);\n context.generated = result.comments.length;\n context.warnings = result.warnings.length;\n if (result.malformed) {\n context.malformedReason = result.malformed.reason;\n throw new ParseError(\n `The reviewer output in ${config.reviewFile} contains a JSON block that could not be parsed [${result.malformed.reason}] (commonly an unescaped quote, backslash, or newline inside a string value). Preview: ${result.malformed.preview}`,\n {\n hint: `Inspect the ${config.reviewFile} artifact for invalid JSON and re-run the review. Failing here avoids marking the job successful with an empty review.`,\n },\n );\n }\n return { parsed: result };\n },\n );\n for (const warning of parsed.warnings) console.warn(`[code-review] ${warning}`);\n\n const discussions = await tracedRead('scm.get_discussions', () => platform.getDiscussions());\n const existing = extractExistingFingerprints(discussions);\n const generated = await traceDiagnosticPhase(\n 'comments.build',\n config,\n runId,\n async (context) => {\n const comments = platform.buildComments(parsed.comments, diff, refs, existing);\n recordCommentCounts(context, comments);\n return comments;\n },\n );\n\n const outputPath = resolve(config.cwd, config.output);\n const usagePath = resolve(config.cwd, 'review-usage.json');\n await traceDiagnosticPhase('artifact.write_output', config, runId, async (context) => {\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, JSON.stringify(generated, null, 2), 'utf8');\n await writeFile(usagePath, JSON.stringify(usage, null, 2), 'utf8');\n recordCommentCounts(context, generated);\n });\n\n console.log(formatUsageLine(usage));\n const perModel = formatPerModelUsage(usage);\n if (perModel) console.log(perModel);\n\n const newCount = generated.filter((item) => !item.duplicate).length;\n recordCommentCounts(runContext, generated);\n bridges?.otel?.logComments(generated, runId);\n logger.info(`Posting ${newCount} new comment(s)...`);\n if (config.dryRun || config.noPost) {\n console.log(`Generated ${generated.length} comments, ${newCount} new. Posting disabled.`);\n if (config.postSummary && parsed.summary) {\n console.log('Summary note generated but not posted (posting disabled).');\n }\n runContext.posted = 0;\n return { generated, posted: 0, usage, summary: null };\n }\n\n let summary: SummaryResult | null = null;\n if (config.postSummary && parsed.summary) {\n summary = await traceDiagnosticPhase(\n 'scm.upsert_summary',\n config,\n runId,\n withHttpStamping(\n () => platform.lastResponse(),\n async (context) => {\n // Carry still-open prior findings into the summary so an unresolved\n // inline thread never vanishes from the issue list when this run\n // didn't re-emit it, and the risk line never drops below one (#92).\n const currentFingerprints = new Set<string>();\n for (const g of generated) {\n currentFingerprints.add(g.fingerprints.primary);\n currentFingerprints.add(g.fingerprints.secondary);\n }\n const summaryBody = withCarriedOverFindings(\n parsed.summary as string,\n discussions,\n currentFingerprints,\n );\n const result = await platform.upsertSummary(summaryBody, discussions, {\n costFooter: [formatUsageLine(usage), formatPerModelUsage(usage)]\n .filter(Boolean)\n .join('\\n\\n'),\n skillsFooter: formatSkillsFooter(usage.skills),\n reviewedCommitSha: refs.head_sha,\n runId,\n sizeNotice: usage.sizeNotice,\n });\n context.summaryAction = result.action;\n context.summaryNoteId = result.noteId;\n return result;\n },\n ),\n );\n runContext.summaryAction = summary.action;\n runContext.summaryNoteId = summary.noteId;\n console.log(\n summary.action === 'updated'\n ? `Updated MR summary note (id ${summary.noteId}).`\n : `Posted MR summary note (id ${summary.noteId}).`,\n );\n } else if (config.postSummary && !parsed.summary) {\n console.log('No summary returned by the reviewer; skipping summary note.');\n }\n\n let draftsPublishFailed = 0;\n let raceLost = 0;\n const posted = await traceDiagnosticPhase(\n 'scm.post_comments',\n config,\n runId,\n withHttpStamping(\n () => platform.lastResponse(),\n async (context) => {\n const result = await platform.postComments(generated, config.postingMode);\n recordCommentCounts(context, generated);\n context.posted = result.posted;\n if (result.drafts) {\n context.draftsAbandoned = result.drafts.abandoned;\n context.draftsCreated = result.drafts.created;\n context.draftsDeletedPrePublish = result.drafts.deletedPrePublish;\n context.draftsPublished = result.drafts.published;\n context.draftsPublishFailed = result.drafts.publishFailed;\n draftsPublishFailed = result.drafts.publishFailed;\n raceLost = result.drafts.deletedPrePublish;\n }\n return result.posted;\n },\n ),\n );\n const duplicates = generated.length - newCount;\n const raceExtra = raceLost > 0 ? `, ${raceLost} dropped by pre-publish re-check` : '';\n console.log(\n `Posted ${posted} new review comment(s) (${duplicates} duplicates skipped${raceExtra}).`,\n );\n if (draftsPublishFailed > 0) {\n console.warn(\n `[code-review] ${draftsPublishFailed} comment(s) could not be published individually after bulk_publish failed and were dropped.`,\n );\n }\n runContext.posted = posted;\n runContext.draftsPublishFailed = draftsPublishFailed;\n if (posted > 0) runContext.postedBySeverity = countPostedBySeverity(generated);\n\n return { generated, posted, usage, summary };\n });\n}\n\nfunction zeroReviewUsage(model: string, thinkingLevel: ThinkingLevel): ReviewUsage {\n return {\n model,\n thinkingLevel,\n tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n skills: [],\n sizeNotice: { sizeSkippedFiles: [] },\n };\n}\n\nexport function formatSkillsFooter(skills: string[]): string | undefined {\n if (skills.length === 0) return undefined;\n return `Skills: ${skills.map((s) => `\\`${s}\\``).join(', ')}`;\n}\n\nexport function formatUsageLine(usage: ReviewUsage): string {\n const formatter = new Intl.NumberFormat('en-US');\n const billableInput = usage.tokens.input + usage.tokens.cacheRead + usage.tokens.cacheWrite;\n const inputLabel =\n usage.tokens.cacheRead > 0\n ? `${formatter.format(billableInput)} in (${formatter.format(usage.tokens.cacheRead)} cached)`\n : `${formatter.format(billableInput)} in`;\n const output = formatter.format(usage.tokens.output);\n const cost = usage.cost.total.toFixed(4);\n // The cost/token figures are run totals across every model that ran. Label\n // them with the single model only when one model ran; with a heterogeneous\n // pool (e.g. a cheap finder + a strong `--verify-model`), attributing the\n // total to `usage.model` reads as if the other models were free. Show the\n // count instead and let the per-model breakdown carry the split.\n const modelLabel =\n usage.byModel && usage.byModel.length >= 2 ? `${usage.byModel.length} models` : usage.model;\n // Always record the reasoning effort the run used, including the `off` default,\n // so the footer is an unambiguous record of the run config.\n const thinkingLabel = `, thinking: ${usage.thinkingLevel ?? 'off'}`;\n return `Review usage: ${inputLabel} / ${output} out tokens — $${cost} (${modelLabel}${thinkingLabel})`;\n}\n\n/**\n * Render the per-model usage breakdown for heterogeneous `full`-depth runs: one\n * indented line per pool member with its billable input / output tokens and\n * cost. Returns `undefined` when there is no breakdown (single-model runs), so\n * the caller prints nothing extra. Surfaces model ids and numbers only — never\n * any key or secret.\n */\nexport function formatPerModelUsage(usage: ReviewUsage): string | undefined {\n if (!usage.byModel || usage.byModel.length < 2) return undefined;\n const formatter = new Intl.NumberFormat('en-US');\n const lines = usage.byModel.map((entry) => {\n const billableInput = entry.tokens.input + entry.tokens.cacheRead + entry.tokens.cacheWrite;\n const input = formatter.format(billableInput);\n const out = formatter.format(entry.tokens.output);\n const cost = entry.cost.total.toFixed(4);\n return ` - ${entry.model}: ${input} in / ${out} out tokens — $${cost}`;\n });\n return ['Per-model usage:', ...lines].join('\\n');\n}\n\nfunction recordCommentCounts(context: DiagnosticContext, generated: GeneratedComment[]): void {\n context.generated = generated.length;\n context.newComments = generated.filter((item) => !item.duplicate).length;\n context.duplicateComments = generated.length - context.newComments;\n}\n\n/**\n * Wraps a phase operation so the phase context gets HTTP semantic-convention\n * attributes stamped from the most recent platform response — on both success\n * and error paths. `readLastHttp` returns the platform's latest response holder;\n * the wrapper snapshots it before the operation and only stamps when the\n * operation's own request produced a *new* response, so a phase that threw\n * before any HTTP call never inherits a previous phase's URL/status.\n *\n * Used by every traced platform phase, including the write phases\n * (`scm.post_comments`, `scm.upsert_summary`) so a failure like a 500 on\n * `bulk_publish` carries http.response.status_code / url.full / server.address.\n */\nexport function withHttpStamping<T>(\n readLastHttp: () => ScmResponseInfo | undefined,\n operation: (context: DiagnosticContext) => Promise<T>,\n): (context: DiagnosticContext) => Promise<T> {\n return async (context) => {\n const before = readLastHttp();\n try {\n return await operation(context);\n } finally {\n const last = readLastHttp();\n if (last !== before) applyHttpContext(context, last);\n }\n };\n}\n\n/**\n * Stamp HTTP semantic-convention fields from a captured platform response onto a\n * diagnostic phase context. The OTel bridge maps these to http.* / url.full /\n * server.address span attributes. No-op when no response was captured.\n */\nfunction applyHttpContext(context: DiagnosticContext, info: ScmResponseInfo | undefined): void {\n if (!info) return;\n context.httpRequestMethod = info.method;\n context.httpUrl = info.url;\n context.httpStatusCode = info.status;\n if (info.responseContentLength !== undefined) {\n context.httpResponseBodySize = info.responseContentLength;\n }\n try {\n context.serverAddress = new URL(info.url).hostname;\n } catch {\n // Malformed URL — leave server.address unset rather than throwing in telemetry.\n }\n}\n\n/**\n * Count the comments posted to the MR, grouped by severity. Duplicates are\n * excluded since they are never posted. The total equals the new-comment count;\n * in `draft` mode a concurrent run can race-delete some drafts before publish,\n * so the breakdown reflects posted intent and may slightly exceed the published\n * count in that rare case.\n */\nexport function countPostedBySeverity(\n generated: GeneratedComment[],\n): Partial<Record<Severity, number>> {\n const counts: Partial<Record<Severity, number>> = {};\n for (const item of generated) {\n if (item.duplicate) continue;\n const severity = item.comment.severity;\n counts[severity] = (counts[severity] ?? 0) + 1;\n }\n return counts;\n}\n\nexport async function main(argv = process.argv.slice(2)): Promise<void> {\n if (argv.includes('--help') || argv.includes('-h')) {\n console.log(HELP);\n return;\n }\n if (argv.includes('--version') || argv.includes('-v')) {\n console.log(__PKG_VERSION__);\n return;\n }\n\n process.stderr.write(`[code-review] ${__PKG_NAME__} v${__PKG_VERSION__}\\n`);\n assertNodeVersion();\n // Expose any CODE_REVIEW_<NAME> provider/infra vars as <NAME> before\n // resolving config: getEnvApiKey and pi-ai's request-time reads both read\n // process.env directly, so this must mutate the live env first.\n applyCodeReviewEnvPrefix();\n // Prefer long (24h) prompt-cache retention unless the caller set it explicitly,\n // so repeated reviews reuse the cached system-prompt prefix. Runs after the\n // prefix shim so CODE_REVIEW_PI_CACHE_RETENTION still wins.\n applyDefaultCacheRetention();\n const config = resolveConfig(argv);\n const otel = await startOtelBridge();\n try {\n await run(config, { otel: otel ?? undefined });\n } finally {\n await otel?.shutdown();\n }\n}\n\nfunction isDirectRun(): boolean {\n const entry = process.argv[1];\n return Boolean(entry) && import.meta.url === pathToFileURL(resolve(entry)).href;\n}\n\nif (isDirectRun()) {\n main().catch((error) => {\n console.error(formatError(error));\n process.exitCode = 1;\n });\n}\n"],"x_google_ignoreList":[10,11,12],"mappings":"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"}