import { tracingChannel, type TracingChannel } from 'node:diagnostics_channel'; import type { Config } from './config.js'; import type { Severity } from './types.js'; export type DiagnosticPhase = 'run' | 'scm.get_merge_request' | 'scm.get_latest_version' | 'git.prepare_history' | 'git.get_merge_diff' | 'git.get_commit_log' | 'reviewer.run' | 'review.parse' | 'scm.get_discussions' | 'comments.build' | 'artifact.write_output' | 'scm.post_comments' | 'scm.upsert_summary'; export interface DiagnosticError { name?: string; message: string; code?: string; /** True when the failure was a deadline/abort, so the bridge can label it `timeout`. */ timeout?: boolean; /** * HTTP status code when the failure came from a GitLab API response (e.g. a * 500 on `bulk_publish`). Propagates up the phase chain so the OTel bridge can * refine `error.type` and label the error counter with the status. */ status?: number; } /** * Value-based secret redaction: removes the *known* secret values this run holds * (the GitLab token and the provider API key) from a string, in every encoding * they might appear under. Unlike pattern matching it cannot over-redact ordinary * text, cannot miss a token because of its format, and uses literal replacement * (no regex), so there is no catastrophic-backtracking risk on large messages. */ export declare function scrubSecrets(input: string, secretValues: readonly string[]): string; /** Collects the run's secret values (non-empty) for {@link scrubSecrets}. */ export declare function collectSecrets(config: Config): string[]; export interface DiagnosticUsageBreakdown { input: number; output: number; cacheRead: number; cacheWrite: number; total: number; } /** * One MCP server's outcome, as carried on {@link DiagnosticUsage.mcp}. A * deliberately minimal subset of `McpServerUsage` (`src/gitlab-review.ts`) — * only the fields the OTel bridge needs for the per-server call-count * attribute — kept local so `diagnostics.ts` does not depend on the * higher-level review module. */ export interface DiagnosticMcpUsage { name: string; status: 'connected' | 'unavailable'; calls: number; } /** * One loaded skill's outcome, as carried on {@link DiagnosticUsage.skills}. The * same minimal-subset rule as {@link DiagnosticMcpUsage}: just what the OTel * bridge needs to report whether the reviewer actually read the skill. */ export interface DiagnosticSkillUsage { name: string; reads: number; } export interface DiagnosticUsage { model: string; tokens: DiagnosticUsageBreakdown; cost: DiagnosticUsageBreakdown; /** MCP servers connected for the review, with their call counts. */ mcp?: DiagnosticMcpUsage[]; /** Skills loaded for the review, with the number of files read under each. */ skills?: DiagnosticSkillUsage[]; } export interface DiagnosticContext { version: 1; runId: string; phase: DiagnosticPhase; project: string; mr: string; /** * The SCM server URL — the GitLab instance URL or the GitHub server URL, * whichever platform this run targets. Its host is emitted as `server.address`. * (Named `gitlabUrl` for backwards compatibility with earlier channel payloads.) */ gitlabUrl: string; /** Review platform (`gitlab` | `github`); emitted as the `vcs.provider.name` label. */ platform?: string; cwd: string; model: string; minSeverity: string; dryRun: boolean; /** * True when no prior bot summary note exists on the MR/PR — i.e. this is the * first review of it (vs a re-review after new pushes). Emitted as the * `code_review.first_review` label so distinct MRs/PRs can be counted from * `code_review_runs_total{code_review_first_review="true"}` without putting the * high-cardinality change id on metrics. Set at runtime, not from config. */ firstReview?: boolean; noPost: boolean; startedAt: string; completedAt?: string; durationMs?: number; generated?: number; newComments?: number; duplicateComments?: number; posted?: number; /** * Breakdown of posted comments by severity, populated on the `run` context so * the OTel bridge can split `code_review_comments_total` by severity. Counts * the non-duplicate (posted-intent) comments; absent on dry-run/skip paths. */ postedBySeverity?: Partial>; warnings?: number; /** Why the reviewer JSON could not be parsed, on the `review.parse` phase when it fails. */ malformedReason?: string; reviewFile?: string; output?: string; /** Number of files in the merge diff (set on the `git.get_merge_diff` phase). */ diffFilesChanged?: number; /** Added content lines in the merge diff. */ diffLinesAdded?: number; /** Removed content lines in the merge diff. */ diffLinesRemoved?: number; /** HTTP method of the GitLab API call traced by this phase. */ httpRequestMethod?: string; /** Full URL of the (last) GitLab API request in this phase; carries no secrets. */ httpUrl?: string; /** HTTP status code of the (last) GitLab API response in this phase. */ httpStatusCode?: number; /** Response Content-Length in bytes, when present. */ httpResponseBodySize?: number; /** Host of the GitLab server the request targeted. */ serverAddress?: string; draftsAbandoned?: number; draftsCreated?: number; draftsDeletedPrePublish?: number; draftsPublished?: number; /** Drafts that could not be published on the per-draft fallback path (silent comment drops). */ draftsPublishFailed?: number; summaryAction?: 'created' | 'updated' | 'skipped'; summaryNoteId?: number; /** * Review outcome whose labels were applied to the MR/PR (`--labels-on-findings` * / `--labels-on-clean`), on the `run` context. Absent when labels are not * configured, posting is disabled, or the label update failed. */ labelsOutcome?: 'findings' | 'clean'; usage?: DiagnosticUsage; errorInfo?: DiagnosticError; } export declare const DIAGNOSTIC_CHANNEL_PREFIX = "@weareikko/code-review"; export declare const DIAGNOSTIC_CHANNEL_NAMES: { readonly run: "@weareikko/code-review:run"; readonly getMergeRequest: "@weareikko/code-review:scm.get_merge_request"; readonly getLatestVersion: "@weareikko/code-review:scm.get_latest_version"; readonly prepareGitHistory: "@weareikko/code-review:git.prepare_history"; readonly getMergeDiff: "@weareikko/code-review:git.get_merge_diff"; readonly getCommitLog: "@weareikko/code-review:git.get_commit_log"; readonly runReviewer: "@weareikko/code-review:reviewer.run"; readonly parseReview: "@weareikko/code-review:review.parse"; readonly getDiscussions: "@weareikko/code-review:scm.get_discussions"; readonly buildComments: "@weareikko/code-review:comments.build"; readonly writeOutput: "@weareikko/code-review:artifact.write_output"; readonly postComments: "@weareikko/code-review:scm.post_comments"; readonly upsertSummary: "@weareikko/code-review:scm.upsert_summary"; }; export declare const diagnosticChannels: Record>>; export declare function createDiagnosticRunId(): string; export declare function createDiagnosticContext(phase: DiagnosticPhase, config: Config, runId: string, overrides?: Partial): DiagnosticContext; export declare function traceDiagnostic(channel: TracingChannel, context: DiagnosticContext, operation: (context: DiagnosticContext) => Promise, secretValues?: readonly string[]): Promise; export declare function traceDiagnosticPhase(phase: DiagnosticPhase, config: Config, runId: string, operation: (context: DiagnosticContext) => Promise, overrides?: Partial): Promise; /** One MCP bridge operation traced independently of the phase channels above. */ export type McpDiagnosticOp = 'mcp.connect' | 'mcp.tools' | 'mcp.call'; /** * Safe payload for an MCP connect/list-tools/call-tool operation. Deliberately * excludes tool arguments and results — only the server/tool identity and * result size (`resultChars`, already capped and never the content itself) * are carried, matching the no-secrets/no-content rule the phase channels * follow for GitLab tokens and API keys. */ export interface McpDiagnosticContext { version: 1; runId: string; op: McpDiagnosticOp; /** Configured MCP server name. */ server: string; /** Tool name; set on `mcp.call` only. */ tool?: string; startedAt: string; completedAt?: string; durationMs?: number; /** Number of read-only tools exposed; set on `mcp.tools` only. */ toolCount?: number; /** Characters returned to the agent, after truncation; set on `mcp.call` only. */ resultChars?: number; errorInfo?: DiagnosticError; } export declare const MCP_DIAGNOSTIC_CHANNEL_NAMES: { readonly connect: "@weareikko/code-review:mcp.connect"; readonly tools: "@weareikko/code-review:mcp.tools"; readonly call: "@weareikko/code-review:mcp.call"; }; export declare const mcpDiagnosticChannels: Record>>; /** * Traces one MCP bridge operation on its own `mcp.connect` / `mcp.tools` / * `mcp.call` channel. Mirrors {@link traceDiagnosticPhase} (context built, * `tracePromise`d, `durationMs`/`completedAt` stamped, errors sanitized) but * is keyed by `runId` + server name instead of a `Config`, since the MCP * bridge (`src/mcp.ts`) has no `Config` of its own. * * Node's `tracingChannel.tracePromise` assigns the traced function's return * value onto the published context as `.result` — fine for the phase channels * above (their payloads are small counts/flags), but `mcp.call`'s return value * is the MCP tool's arguments/result content, which must never reach a * diagnostics payload. The traced function here always resolves to * `undefined`; the operation's real result is captured in a closure and * returned to the caller separately, after tracing completes. */ export declare function traceMcpDiagnostic(op: McpDiagnosticOp, runId: string, server: string, operation: (context: McpDiagnosticContext) => Promise, overrides?: Partial): Promise; //# sourceMappingURL=diagnostics.d.ts.map