/** * ADR-111 Stage-1 Extract — the live CLI `ReviewThreadSource` adapter (slice 5a, * mmnto-ai/totem#2201). * * Core defines the `ReviewThreadSource` port and stays network-free + LLM-free + * deterministic; THIS file is the IO-at-the-edge implementation that wraps the * GitHub GraphQL `reviewThreads` API behind that port. It fetches a PR's merge * commit + its review threads (each carrying `isResolved` / `isOutdated`), maps * them to the provider-neutral `ReviewThreadContent`, and SURFACES the per-thread * resolution flags to core. It does NOT filter resolved/outdated threads — the * contract-owner ruling is "surface, don't filter": core decides eligibility and * drop-ledgers every rejection (§8 "every rejection ledgered"). A server-side or * client-side `isResolved:false` pre-filter is FORBIDDEN here. * * Error contract (the discriminated `FetchResult`, §6): a per-PR failure is NEVER * thrown — the orchestrator iterates the whole train slice and must not abort on * one bad PR. Network / not-found → `{kind:'unreachable'}`; a malformed or * unmappable payload → `{kind:'unparseable'}`. The mining run continues; the loud * drop is core's job. * * Reuses the established `gh` CLI exec pattern (Tenet-21 — no bespoke GraphQL * client). The `exec` seam is injectable so the query-spy + mapping tests run * fully offline (no network, CI-locked). */ import { z } from 'zod'; import type { AuthorKind, FetchResult, ReviewThread, ReviewThreadComment, ReviewThreadSource } from '@mmnto/totem'; declare const GqlReviewThreadSchema: z.ZodObject<{ isResolved: z.ZodBoolean; isOutdated: z.ZodBoolean; path: z.ZodString; comments: z.ZodObject<{ pageInfo: z.ZodObject<{ hasNextPage: z.ZodBoolean; }, "strip", z.ZodTypeAny, { hasNextPage: boolean; }, { hasNextPage: boolean; }>; nodes: z.ZodArray>; body: z.ZodString; }, "strip", z.ZodTypeAny, { body: string; author: { login: string; } | null; }, { body: string; author: { login: string; } | null; }>, "many">; }, "strip", z.ZodTypeAny, { pageInfo: { hasNextPage: boolean; }; nodes: { body: string; author: { login: string; } | null; }[]; }, { pageInfo: { hasNextPage: boolean; }; nodes: { body: string; author: { login: string; } | null; }[]; }>; }, "strip", z.ZodTypeAny, { path: string; comments: { pageInfo: { hasNextPage: boolean; }; nodes: { body: string; author: { login: string; } | null; }[]; }; isResolved: boolean; isOutdated: boolean; }, { path: string; comments: { pageInfo: { hasNextPage: boolean; }; nodes: { body: string; author: { login: string; } | null; }[]; }; isResolved: boolean; isOutdated: boolean; }>; /** * Build the `reviewThreads` GraphQL query. It REQUESTS `isResolved` and * `isOutdated` per thread (so core HAS the signal to decide on) — it deliberately * does NOT add an `isResolved: false` server-side filter (the "surface, don't * filter" ruling; the query-spy test asserts both halves). Exported for the * query-spy test. */ export declare function buildReviewThreadsQuery(owner: string, name: string, pr: number): string; /** * The injectable command-exec seam. Matches the relevant part of `safeExec`'s * signature; the tests pass a fake that intercepts the outgoing GraphQL query * (no network). When not injected, the adapter lazy-loads `safeExec` (below). */ export type GhExec = (command: string, args: string[]) => string; /** * The two CORE-homed comment classifiers `mapThreads` stamps onto each comment * (slice β). Injected — the LOGIC + version live in core (`classifyAuthorKind`, * `normalizeReviewChrome`; panel OQ-β1/β3), the adapter is only the mapping- * boundary call site, so the inputKey can never drift from the provenance. The * real `fetch` resolves these by lazy-loading core (the barrel is loaded anyway * for `safeExec`); tests pass the real core fns directly. */ export interface CommentEnrichers { classifyAuthorKind: (author: string) => AuthorKind; normalizeReviewChrome: (body: string) => string; } /** * Slice-β comment enrichment (the SHARED single home): stamp `authorKind` (via core * `classifyAuthorKind`) + `normalizedBody` (the de-chromed text the extractor * consumes — `normalizeReviewChrome(body)` for a recognized review bot, else the raw * body verbatim; chrome is stripped ONLY for bot comments so human prose is never * altered). BOTH the live `mapThreads` AND the replay-time `frozenSourceFrom` loader * (`spine-cert-run-corpus`) call THIS, so the de-chromed body they feed the * extractor — hence the `extractorInputKey` — can never diverge between record and * replay (a divergence would be indistinguishable from model drift). */ export declare function enrichComment(enrich: CommentEnrichers, author: string, body: string): ReviewThreadComment; /** * Map a validated GraphQL response to the surviving `ReviewThread[]` with their * resolution flags surfaced and each comment slice-β-ENRICHED (see `enrichComment`). * Pure given `enrich` — exported for the mapping test. A null author (deleted/ghost) * coerces to '' (`reviewBotIdentity('')` is false → `authorKind: 'human'`, and * `isBotIdentity('')` is false, so core's count keeps it; the body still gates * inclusion). */ export declare function mapThreads(nodes: z.infer[], enrich: CommentEnrichers): ReviewThread[]; export interface ReviewThreadSourceAdapterOptions { /** The repo to query, as `owner` + `name`. */ owner: string; name: string; /** Working directory for the `gh` invocation (real adapter). */ cwd?: string; /** Injectable exec seam (tests). Defaults to a `gh`-backed `safeExec`. */ exec?: GhExec; /** * Injectable slice-β comment enrichers (tests). Defaults to a lazy-load of core's * `classifyAuthorKind` + `normalizeReviewChrome` (the barrel is loaded anyway for * `safeExec`). Tests pass the real core fns so the mapping is exercised end-to-end. */ enrich?: CommentEnrichers; } /** * The live `ReviewThreadSource`. Constructible + exported now (slice 5a); wiring * it into the spine orchestrator's `run` command is slice 5c. */ export declare class ReviewThreadSourceAdapter implements ReviewThreadSource { private readonly owner; private readonly name; private readonly cwd; /** Injected exec seam (tests); when absent the default is lazy-loaded once via `execPromise`. */ private readonly injectedExec; /** * Memoized lazy-load of the default `gh`-backed exec (real runs). Memoizes the * PROMISE, not the resolved value, so concurrent `fetch()` calls on one adapter * instance share a single `loadDefaultExec` instead of racing a `!this.exec` * guard (CR #2207 — was benign since dynamic import is idempotent, now explicit). */ private execPromise; /** Injected slice-β enrichers (tests); when absent the default lazy-loads core once via `enrichPromise`. */ private readonly injectedEnrich; /** Memoized lazy-load of core's `classifyAuthorKind` + `normalizeReviewChrome` (real runs) — see `execPromise`. */ private enrichPromise; constructor(opts: ReviewThreadSourceAdapterOptions); /** Resolve the exec seam: the injected one (tests), else the memoized lazy-loaded default. */ private resolveExec; /** Resolve the slice-β enrichers: the injected ones (tests), else a memoized lazy-load of core. */ private resolveEnrich; fetch(pr: number): Promise; } export {}; //# sourceMappingURL=spine-review-thread-source.d.ts.map