import type { CadenceConfig, RetroDigest, Summary } from '@manehorizons/cadence-types'; import type { CommandIO } from './io.js'; import type { Prompter } from '../verify/prompter.js'; import type { Interactivity } from '../gates/interactivity.js'; /** * Phase 174: friction digest purely derived from an already-assembled * `Summary` — no extra I/O. Gate bypasses, tasks whose terminal status * wasn't a clean DONE, and any present code-review / security-audit / * boundary-scan findings. */ export declare function buildRetroDigest(summary: Summary): RetroDigest; export type RetroFindingCategory = 'codeReview' | 'securityAudit' | 'boundaryScan'; /** * Which of the three finding categories are genuinely non-empty on this * digest. Presence isn't enough — `RetroFindings` permits schema-valid-but- * empty shapes (`codeReview: {}`, `securityAudit: []`, * `boundaryScan: { offenders: [] }`), all truthy in JS despite representing * "gate ran and found nothing" rather than an actual finding (see * `buildRetroDigest`'s comment). Single source of truth for that per- * category emptiness check — reused by `isDigestEmpty` and by * `computeRetroRollup`'s `findingCategories` frequency bucket, so neither * can drift from the other. */ export declare function nonEmptyFindingCategories(digest: RetroDigest): RetroFindingCategory[]; export declare function isDigestEmpty(digest: RetroDigest): boolean; /** Flat friction count across every populated digest field, for the issue title. */ export declare function retroFrictionCount(digest: RetroDigest): number; export interface RetroWriteContext { cwd: string; activePhase: string; draftId: string; io: CommandIO; } /** * Writes `-RETRO.json` then `.md` under `.cadence/phases//`, * mirroring the SUMMARY.json/.md pattern. JSON first: it's the machine * artifact a future rollup would consume, so a crash between the two writes * leaves the more load-bearing file in place. */ export declare function writeRetroArtifacts(digest: RetroDigest, ctx: RetroWriteContext): Promise; /** * Minimal shape of a spawned child process this module needs — narrowed from * `node:child_process`'s `ChildProcess` so tests can inject a lightweight * fake, mirroring `verify/host-cli-client.ts`'s `SpawnedProcessLike`/`SpawnFn` * seam (not imported directly: that module's version and its capture helper * are private to it, and this module's failure handling is simpler — every * `gh` failure here is caught and turned into a stderr notice, never a typed * error propagated to a caller). */ export interface SpawnedProcessLike { stdout: NodeJS.ReadableStream | null; stderr: NodeJS.ReadableStream | null; on(event: 'error', listener: (err: NodeJS.ErrnoException) => void): unknown; on(event: 'close', listener: (code: number | null) => void): unknown; } export type SpawnFn = (bin: string, args: string[]) => SpawnedProcessLike; /** Best-effort: what repo would `gh` file an issue against right now? `undefined` on any failure. */ export declare function resolveIssueTarget(spawnImpl?: SpawnFn): Promise; /** * `--repo` is always passed explicitly (the already-resolved target), which * makes this call fully non-interactive — `gh` never needs to prompt for * anything we haven't supplied. No `--label` here: a repo without that label * would fail issue creation entirely (see `addIssueLabel`). */ export declare function createGithubIssue(target: string, title: string, body: string, spawnImpl?: SpawnFn): Promise<{ url: string; } | { error: string; }>; /** Best-effort, separate call — a missing label must not undo the already-created issue. */ export declare function addIssueLabel(target: string, issueUrl: string, label: string, spawnImpl?: SpawnFn): Promise<{ ok: true; } | { error: string; }>; /** Same shape as `gates/approve.ts`'s `askApproveVerdict`: 3 retries, y/yes/n/no, defaults to no. */ export declare function askRetroIssueVerdict(prompter: Prompter, target: string): Promise<'yes' | 'no'>; export interface RetroOfferContext { cwd: string; activePhase: string; draftId: string; io: CommandIO; interactivity: Interactivity; /** * Genuine `process.stdin.isTTY`, independent of `interactivity` — required * in addition to `interactivity !== 'bypass'` before this offer will spawn * `gh` or prompt at all. `resolveInteractivity` resolves to `'interactive'` * whenever `CADENCE_PROMPTER_SCRIPT` is set, even off a real TTY — that env * var is this codebase's *test-only* seam for scripting gate prompt * answers deterministically (`CLAUDE.md`: "Tests never call real * providers... plus the CADENCE_PROMPTER_SCRIPT seam for interactive * flows"), not a license to spawn a real external process. Without this * flag, any existing (or future) test/script that drives the interactive- * verdict gate via `CADENCE_PROMPTER_SCRIPT` and happens to also produce * settle-time friction (e.g. a `force-used` bypass from a failed AC * verdict) would trigger a real, unmocked `gh repo view` spawn — this is * exactly what caused a ~71s hang on Windows CI in * `tests/cli/settle-interactive.test.ts`'s `--force bypasses interactive * refusal` test, discovered post-implementation, not by design. */ isRealTTY: boolean; createPrompter: () => Prompter; cadenceConfig?: CadenceConfig; spawn?: SpawnFn; } /** * The resolve-target → maybe-prompt → maybe-create-and-label sequence. * Best-effort throughout — every failure is a stderr notice, never a throw * that could propagate to the caller (`settle.ts` still wraps the call, but * nothing here is expected to reach it). */ export declare function runRetroOffer(digest: RetroDigest, ctx: RetroOfferContext): Promise; //# sourceMappingURL=retro.d.ts.map