/** * Decides whether an issue is "done" based on the final verification verdict and * an acceptance-criteria comparison. * * An issue is "done" ONLY if the verdict is PASS AND every acceptance criterion * is matched-and-checked by the verifier. Otherwise it is "needs-human" with the * reason and the unmet/unmatched criteria listed. * * Two matching modes: * * 1. STRICT mode (AC IDs present): when issue criteria carry stable AC-### IDs, * matching is by ID. This is robust to LLM rewording of criterion text. * * 2. LEGACY mode (no AC IDs): matching is by NORMALIZED TEXT. The verifier's * `CRITERIA:` checklist is free-form LLM output; it may reorder, reword * slightly, or add extra lines. Each issue criterion is matched to a verifier * entry by a normalized key (lowercased, collapsed whitespace, stripped * trailing punctuation), so a reorder or cosmetic difference does not produce * a false "needs-human". * * Two robustness properties (issue 06): * * 1. Matching is robust to reorder/cosmetic differences (by ID in strict mode, * by normalized key in legacy mode). * * 2. "No criteria" is distinguished from "criteria failed to parse". A trivial * ticket with NO acceptance-criteria section is a deliberate weak gate * (warn-and-proceed → done on PASS). But a criteria SECTION that was present * yet parsed to zero items is a malformed spec — you cannot verify what did * not parse — so it is needs-human, never a silent done. The caller passes * `sectionPresent` to make that distinction; omitted, it defaults to the * trivial-ticket reading for back-compat. * * Pure (no I/O) so it is unit-tested directly. */ import type { Verdict } from "./verdict.ts"; import type { AcceptanceCriterion, AcceptanceParseIssue } from "./acceptance.ts"; export type DoneDecision = { status: "done"; } | { status: "needs-human"; unmet: string[]; reason: string; }; export interface DecideDoneOpts { /** * Whether the issue markdown actually had an "## Acceptance criteria" section. * true + zero parsed criteria = a malformed/unparseable spec (needs-human); * false = a trivial ticket with no criteria (weak gate → done on pass). * Defaults to false (trivial-ticket reading) for back-compat. */ sectionPresent?: boolean; /** * Diagnostics from the acceptance criteria parser (e.g. duplicate AC IDs). * When present and non-empty, the done gate holds as needs-human because * the spec itself is ambiguous. */ parseIssues?: AcceptanceParseIssue[]; } export declare function decideDone(verdict: Verdict, issueCriteria: AcceptanceCriterion[], verifierResults: AcceptanceCriterion[], opts?: DecideDoneOpts): DoneDecision;