/** * Grill-Gate Predicate — E3-GRILL-GATE (T11495) * * Decides whether a task unit is ready to proceed autonomously (`proceed`) * or needs human/orchestrator clarification before work starts (`grill`). * * The predicate is pure: it operates on pre-loaded task data + optional * supporting signals and never opens a DB or calls the network directly. * Callers (e.g. `cleo go`) supply the unit + signals; the predicate * focuses solely on classification logic. * * ## Grill triggers (AC2) * 1. Missing acceptance criteria — task has no `acceptance` entries. * 2. Owner-decision required — task carries an `owner-decision` label OR * `blockedBy` contains "owner" / "decision" text. * 3. IVTR max-retries exhausted — any phase in `ivtrLoopBackCount` has * reached {@link MAX_LOOP_BACKS_PER_PHASE} (requires HITL escalation). * 4. Release/publish gate active — task `pipelineStage` is `'release'` and * no HITL approval token is present (reuses existing `orchestrate approve` * / `reject` / `pending` surface — no new HITL introduced). * 5. Scope ambiguity — epic-type task with no children AND no attached spec * or research artifact (blob list) — cannot be safely autonomously worked. * * ## Auto-proceed condition (AC2) * - Implementation-or-later epic (`pipelineStage` ∈ `implementation-stages`) * that has a non-empty ready frontier (at least one child task satisfies * all dependencies), or any concrete task/subtask that clears all GRILL * triggers. * * @module orchestration/classify-readiness * @task T11495 E3-GRILL-GATE classifyReadiness predicate * @epic T11492 SG-AUTOPILOT * @see {@link classifyReadiness} */ import type { Task } from '@cleocode/contracts'; import type { IvtrState } from '../lifecycle/ivtr-loop.js'; /** * Verdict returned by {@link classifyReadiness}. * * - `'proceed'` — the task is ready for autonomous execution. * - `'grill'` — the task needs clarification or escalation before work * can safely begin. Callers surface this as * `AskUserQuestion` (agent mode) or a pending-row * (--headless mode). */ export type ReadinessVerdict = 'proceed' | 'grill'; /** * Grill trigger codes. One or more codes appear in * {@link ReadinessResult.triggers} when `verdict === 'grill'`. */ export type GrillTrigger = /** Task has no acceptance criteria — work scope is undefined. */ 'MISSING_AC' /** Task carries an `owner-decision` label or its `blockedBy` field mentions owner/decision. */ | 'OWNER_DECISION_REQUIRED' /** IVTR loop has exhausted loop-back retries for at least one phase. */ | 'IVTR_MAX_RETRIES' /** Task is in `release` pipeline stage and no HITL approval has been granted. */ | 'RELEASE_GATE' /** Epic with no children and no spec/research artifact — ambiguous scope. */ | 'AMBIGUOUS_SCOPE'; /** * Result returned by {@link classifyReadiness}. */ export interface ReadinessResult { /** Whether the task should proceed or be grilled. */ verdict: ReadinessVerdict; /** * Human-readable summary of the verdict. * * For `proceed`: a brief confirmation. * For `grill`: a concise description of what needs clarification. */ reason: string; /** * Active grill triggers (empty when `verdict === 'proceed'`). * * Multiple triggers may fire simultaneously; callers should surface all of * them in the question/pending-row so they can be resolved in one pass. */ triggers: GrillTrigger[]; } /** * Optional signals that require async resolution by the caller. * * Pass a populated `ReadinessSignals` to {@link classifyReadiness} so the * predicate can incorporate live state without opening any I/O itself. * Every field is optional; omitting a field disables the corresponding check. */ export interface ReadinessSignals { /** * Child tasks of the unit under evaluation. * * Used to determine whether an epic has a non-empty ready frontier * (at least one non-terminal child with all deps satisfied) and * to suppress {@link GrillTrigger.AMBIGUOUS_SCOPE} when children exist. */ children?: Task[]; /** * IVTR state for the task as returned by `getIvtrState()`. * * When present, the predicate checks whether any phase's `loopBackCount` * has reached {@link MAX_LOOP_BACKS_PER_PHASE}. When absent the * IVTR-max-retries check is skipped. */ ivtrState?: IvtrState | null; /** * Names of blob attachments associated with the task (from `blobList()`). * * Used to detect whether an undecomposed epic has a spec or research * artifact that justifies autonomous execution. When absent the check * falls back to the children-count alone. */ blobNames?: string[]; /** * Whether a HITL approval token has already been granted for the task. * * When `true` the {@link GrillTrigger.RELEASE_GATE} trigger is suppressed * even if the task is in the `release` pipeline stage (the gate was already * cleared via `cleo orchestrate approve`). * * Defaults to `false` when omitted. */ hitlApproved?: boolean; } /** * Classify whether a task unit is ready to proceed autonomously or needs * grilling (clarification / escalation) before work starts. * * **Pure function**: does not open any DB, file, or network connection. * All live state (children, IVTR, blobs) is pre-fetched by the caller and * passed via {@link ReadinessSignals}. * * ## Grill triggers (checked in order) * 1. {@link GrillTrigger.MISSING_AC} — `task.acceptance` is empty or absent. * 2. {@link GrillTrigger.OWNER_DECISION_REQUIRED} — `owner-decision` label or blockedBy text. * 3. {@link GrillTrigger.IVTR_MAX_RETRIES} — IVTR loop-back count ≥ MAX per any phase. * 4. {@link GrillTrigger.RELEASE_GATE} — `pipelineStage` ∈ {release, publish} without approval. * 5. {@link GrillTrigger.AMBIGUOUS_SCOPE} — epic with no children and no spec artifact. * * ## Auto-proceed * - All triggers clear. * - OR: implementation-or-later epic with a non-empty ready frontier * (only possible when MISSING_AC is absent — an epic without ACs still grills). * * @param task - Full task record to classify. * @param signals - Optional pre-fetched supporting state. When omitted, checks * that require live state are skipped gracefully. * @returns {@link ReadinessResult} with `verdict`, `reason`, and `triggers`. * * @example Basic usage (no async signals): * ```typescript * const result = classifyReadiness(task); * if (result.verdict === 'grill') { * console.warn(`Grill: ${result.reason}`); * } * ``` * * @example With pre-fetched signals: * ```typescript * const children = await accessor.getChildren(task.id); * const ivtrState = await getIvtrState(task.id); * const blobNames = (await blobList(task.id)).map(b => b.name); * * const result = classifyReadiness(task, { children, ivtrState, blobNames }); * ``` * * @task T11495 E3-GRILL-GATE * @epic T11492 SG-AUTOPILOT */ export declare function classifyReadiness(task: Task, signals?: ReadinessSignals): ReadinessResult; //# sourceMappingURL=classify-readiness.d.ts.map