/** * The interactive-review seam (DES-artifact-editor-plugins §7.6 "Reviews", EP-C2) — the sibling of * `edit-events.ts` and `theme-events.ts`, same subscriber shape, ledger and filter pattern. * * 1. The page / document editor's "Review this page" makes the host post * `wicked.interactive.review.requested {document_id, version, reviewers:[…]}`. * 2. Crew answers with ONE governed `interactive-review` run — one read-only phase that reviews the * saved version once per requested reviewer (Intent, A11y, Copy, Quality) and reports per * reviewer. One council per review, not four. The request is deduped on * `:review::v:`: an answered request is re-announced from the * record, a running one is left alone, a failed one may be asked again. * 3. NOT THE AUTHOR'S SEAT. The seats that wrote the document (the creator units of its draft, * edit and chat runs) are removed from the review run's roster AND passed as the launch's * `excludeSeats` (EP-K3), which the engine unions into its judge exclusion — so neither the * reviewer nor its judge is a seat that wrote what is under review. When no authoring run is * on record (the operator typed the version), nothing is excluded and every verdict says so. * 4. The phase is READ-ONLY (the engine gives a non-creator, non-code phase no write tools), so it * cannot write its own verdict rows: it ends with one `REVIEW-REPORT- {json}` line in its * engine-captured output, and crew records ONE wicked-ledger verdict row per reviewer, stamped * with `crew_run_id`, in the document's review root (`review-ledger.ts`) — there is no crew * review ledger. The rows are READ BACK, and only what was read back is announced: * `wicked.interactive.review.completed {document_id, version, reviewer, verdict, passed, findings}`. * 5. A finding never edits the document. Fixing one is the operator's choice (rule 2). * * Replay dedup and the handoff directory ride the edit seam's ledger and `interactive-edits` root * (keys `:review:…` beside `:v` and `:theme:`), so no new state-home entry is * introduced — the worker fence is unchanged. */ import type { CoreAdapter } from '../core/adapter.js'; import type { SessionView, WorkflowDef } from '../core/types.js'; import type { InteractiveBridgePool } from './bridge-pool.js'; import { InteractiveHandoffLedger } from './ledger.js'; import { type ReviewFinding, type ReviewVerdict } from './review-ledger.js'; export declare const REVIEW_REQUESTED = "wicked.interactive.review.requested"; export declare const REVIEW_COMPLETED = "wicked.interactive.review.completed"; export declare const INTERACTIVE_REVIEW_BUS_FILTER = "wicked.interactive.review.requested@wicked-interactive"; export declare const INTERACTIVE_REVIEW_WORKFLOW = "interactive-review"; /** The reviewers, by the `review.requested` schema id (interactive's enum). */ export type ReviewerId = 'match' | 'a11y' | 'copy' | 'qe'; export interface ReviewerSpec { /** What the operator calls it. */ title: string; /** The garden skill the phase loads for it, when the published snapshot holds it. */ skill: string; /** What to look for when the skill is not installed — one line. */ rubric: string; } /** DES §7.6's table. Order = the order results are announced in. */ export declare const REVIEWERS: Readonly>; export declare const REVIEWER_IDS: ReviewerId[]; /** * The line the phase ends with: ` {json}`, where the marker is `REVIEW-REPORT-` — a * per-run nonce minted AFTER the version was saved and handed to the reviewer in the handoff only. * The reviewed page is attacker-influenced; a report it carries (and the reviewer quotes) cannot * know the nonce, so it is never this run's report. */ export declare const REPORT_MARKER_PREFIX = "REVIEW-REPORT-"; export declare const reportMarker: () => string; /** * The review run's def. One phase, read-only by the engine's own posture rule (not a creator, not a * code phase). It is `neutral`, not `evaluator`: an engine Evaluator must end `VERDICT: PASS|FAIL` * and a FAIL parks the run at a human gate — but a review that finds problems is a FINISHED review, * not a failed gate. The verdicts are the per-reviewer rows, not the run's status. */ export declare function interactiveReviewWorkflowDef(allowedSkills?: string[]): WorkflowDef; export interface ReviewRequest { documentId: string; version: number; /** Deduped, in {@link REVIEWER_IDS} order. */ reviewers: ReviewerId[]; projectId?: string; } /** * Parse a bus frame into a {@link ReviewRequest}, or `null` when it is not an actionable request: * wrong type, malformed payload, slug-invalid `document_id`, no integer `version` (interactive * stamps the head when the browser omits it, EP-I1), or a `reviewers` list naming none of the four. * An ABSENT `reviewers` means all four. */ export declare function parseReviewRequested(eventType: string, payload: unknown): ReviewRequest | null; /** * The dedupe unit: one review per document version per reviewer set — per project, because two * projects may each hold a document of the same name (docs roots are partitioned per project). * It starts `:` like every handoff key, so the doc↔run index and the delete sweep see it. */ export declare function reviewHandoffKey(documentId: string, version: number, reviewers: readonly ReviewerId[], projectId?: string): string; /** * Is `key` a review row of `documentId` in a project OTHER than `projectId`? The delete sweep keeps * those (codex r2): deleting one project's document must not make the same-named document of * another project forget that it was reviewed. */ export declare function isAnotherProjectsReviewKey(key: string, documentId: string, projectId: string | undefined): boolean; export declare function reviewProblem(req: ReviewRequest, handoffPath: string): string; /** * This run's report: the object after the LAST occurrence of `marker` in a unit's output. `null` when * the marker is absent, or when what follows its last occurrence is not one JSON object — an * earlier occurrence is never revived (the last word of the reviewer decides, and a broken last * word is no report). */ export declare function extractReviewReport(output: string | null, marker: string): Record | null; export interface ReviewerResult { reviewer: ReviewerId; verdict: ReviewVerdict; findings: ReviewFinding[]; reason: string; } /** * One result per REQUESTED reviewer, in request order. A reviewer the report leaves out, whose * verdict is neither `pass` nor `changes`, or whose findings cannot be read, reads `error` — never a * guessed pass. A `pass` that still * lists findings is recorded as `changes`: "nothing needs changing" and a list of changes cannot both * be true, and the stricter one is kept. */ export declare function resultsFromReport(report: Record | null, requested: readonly ReviewerId[]): ReviewerResult[]; /** The same result for every requested reviewer when the run itself did not finish. */ export declare function errorResults(requested: readonly ReviewerId[], reason: string): ReviewerResult[]; export type ReadDocVersion = (documentId: string, projectId: string | undefined, version: number) => Promise; /** The default reader: the doc's bridge ← `GET /d/:doc/doc/:version` (interactive stays the reader of its files). */ export declare function readDocVersionViaBridge(pool: Pick, resolveDocsRoot: (projectId: string | undefined) => string): ReadDocVersion; /** `claude#2` → `claude`: the cli key of a seat instance. */ export declare const seatKey: (seat: string) => string; /** * The seats that wrote in `runIds`: every creator unit's assigned seat, as cli keys, sorted. A run * that is not in `views`, or has no assigned creator unit, contributes nothing. */ export declare function authorSeatsOf(views: readonly SessionView[], runIds: readonly string[]): string[]; /** * The edit runs on record that produced a version at or below `version`: a `:v` row names * the PARENT of the version its run made, so `n < version` is the only row that can have written * what is under review. The default {@link InteractiveReviewOptions.authoringRuns} (the edit ledger * alone); the daemon passes one that also reads the draft and chat ledgers. */ export declare function editRunsBefore(ledger: InteractiveHandoffLedger, documentId: string, version: number): string[]; /** One seam's ledger, as the doc-delete sweep and the doc↔run index name it. */ export interface AuthoringLedgerSource { /** `draft` | `edit` | `chat`. */ name: string; ledger?: InteractiveHandoffLedger | undefined; path: string; } /** * Every run on record that can have written what is under review: the document's draft run * (``, or `:draft:` for a filed document), its edit runs whose parent version is below `version` (`:v`), and its chat * revisions (`:m:…` / `:e:…` — a chat ask carries no version, so all of them count: the * list is a SUPERSET on purpose, because it can only narrow who reviews). Throws when a ledger * cannot be read — the caller then refuses to pick a reviewer blindly. */ export declare function authoringRunsFromLedgers(sources: readonly AuthoringLedgerSource[], documentId: string, version: number): string[]; /** `roster` without the seats in `excluded` (by cli key). Entries with no string `key` are kept. */ export declare function rosterWithout(roster: readonly unknown[], excluded: readonly string[]): unknown[]; export interface InteractiveReviewOptions { /** Spike 3: whether a document is a run's working document (kind `phase-report`): never answered by a run. */ isWorkingDoc?: (projectId: string | undefined, documentId: string) => boolean; dbPath?: string; pollIntervalMs?: number; heartbeatMs?: number; /** Shared with the edit seam (one file, one instance); default: the edit ledger path. */ ledger?: InteractiveHandoffLedger; ledgerPath?: string; /** Handoff root (default `/interactive-edits`, shared with the edit seam). Reviews are recorded under `/_reviews//`. */ editDir?: string; clisJson?: string; roster?: () => unknown[]; /** Reads one saved version through interactive. Required: without it there is nothing to review. */ readDocVersion: ReadDocVersion; /** The runs that wrote this document up to `version` (default: the edit ledger's rows below it). */ authoringRuns?: (documentId: string, version: number) => string[]; /** Does the published skills snapshot hold (and enable) this skill? Absent = none are held. */ skillHeld?: (name: string) => boolean; /** crew#1145: the daemon's background skills ladder — a review waits for it before it reads the held skills. Absent = read now. */ skillsSettled?: Promise; onRunFiled?: (runId: string, projectId: string) => void; log?: (message: string) => void; logError?: (message: string) => void; } export interface InteractiveReviewSubscription { stop(): Promise | void; ledger: InteractiveHandoffLedger; /** Where every document's review root lives (`/_reviews`). */ reviewsDir: string; inFlightDocs(): string[]; } /** "Intent passed · A11y asks for 2 changes · Copy could not run". */ export declare function summaryLine(rows: ReadonlyArray<{ reviewer: string; reviewVerdict: string; findings: unknown[]; }>): string; export declare function startInteractiveReviewSubscriber(adapter: CoreAdapter, opts: InteractiveReviewOptions): Promise; //# sourceMappingURL=review-events.d.ts.map