/** * Plan proposal types + pure assembler. * * This module turns a loose decomposition (produced by an LLM planning agent, * or handed in directly by a caller) into a validated, typed `PlanProposal`. * It is intentionally free of I/O and LLM calls: no filesystem access, no * agent spawning, no network. It exists so `AdaptivePlanner` can stay a * synchronous, deterministic, side-effect-free scorer while still producing * a rich, structured artifact that an orchestration engine can render for * human approval and (once approved) instantiate. * * A `PlanProposal` is DATA. It is never instantiated on its own, approval * flows through the existing `ProjectPlanningState.executionApproved` gate * (see `planProposalToPlanningState` below), and durable persistence reuses * `ExecutionPlanManager` (see `planProposalToExecutionPlanItems` below). * Neither adapter performs any I/O itself; they only shape data for callers * that do. */ import type { ExecutionStrategy } from './adaptive-planner.js'; import type { PlanItem } from './execution-plan.js'; import type { ProjectPlanningState } from '../knowledge/project-planning/types.js'; /** Archetypes a work item can suggest for its executing agent. Open-ended by design. */ export type WorkItemArchetype = 'engineer' | 'reviewer' | 'tester' | 'researcher' | 'integrator' | string; /** * A single unit of work inside a proposal. * * Field shapes deliberately mirror three existing types so downstream bridges * are near-1:1 maps rather than translations: * - knowledge `ProjectPlanningTask` (approval-render bridge) * - knowledge `ProjectWorkPlanTask` (phaseId/dependency naming) * - core `PlanItem` (persistence bridge) */ export interface WorkItem { id: string; title: string; brief: string; phaseId: string; dependsOn: string[]; suggestedArchetype?: WorkItemArchetype | undefined; likelyFiles?: string[] | undefined; verification?: string[] | undefined; canRunConcurrently?: boolean | undefined; needsReview?: boolean | undefined; /** * Best-of-N: run this item as N sibling attempts in isolated worktrees and * HOLD the merge for a winner pick instead of auto-merging (the orchestration * engine's best-of-N, platform/orchestration/attempts.ts). Previously the * plan format constrained this OUT (every item was single-attempt); the engine * now supports it, so a planner may propose it. A best-of-N item may be * NON-LEAF: it may declare `dependsOn` (each attempt inherits it) and other * items may depend on it, a dependent gates on the group's picked-and-merged * winner (the losing attempts are cleaned first). Omitted/1 ⇒ an ordinary single * item. Only honored under `worktree` workstream isolation. */ attempts?: number | undefined; /** Best-of-N: allow a judge proposal to auto-pick this item's winner (opt-in; default: a human picks). */ autoAcceptWinner?: boolean | undefined; } export interface Phase { id: string; title: string; description?: string | undefined; order: number; } /** Where a proposal's decomposition came from. */ export type PlanProposalSource = 'planner-agent' | 'single-item-fallback' | 'caller-supplied'; /** * Honest provenance for how a proposal's work items were produced. * * - `'agent'` , a read-only planning agent decomposed the goal, and its * output validated cleanly (possibly after one repair attempt). * - `'heuristic'`, the deterministic single-item path produced the proposal, * either because `planner.decomposition` is configured to `'heuristic'`, the * planner's gate declined to decompose, or the agent path failed and fell * back. `fallbackReason` is set only in the failure case, never when the * heuristic path was chosen deliberately by config or gate. */ export type DecomposedBy = 'agent' | 'heuristic'; /** Token usage reported by a planning-decomposition agent run. */ export interface DecompositionAgentUsage { readonly inputTokens: number; readonly outputTokens: number; readonly cacheReadTokens?: number | undefined; readonly cacheWriteTokens?: number | undefined; readonly totalTokens: number; } export interface PlanProposal { id: string; task: string; strategy: ExecutionStrategy; rationale: string; phases: Phase[]; workItems: WorkItem[]; createdAt: number; source: PlanProposalSource; /** * Provenance overlay set by the decomposition service (plan-decomposition.ts). * Absent on proposals produced directly by `assemblePlanProposal` / * `singleItemProposal`, which stay byte-compatible with pre-provenance tests. */ decomposedBy?: DecomposedBy | undefined; /** Token usage of the planning agent (present when the agent path ran, even on fallback). */ agentUsage?: DecompositionAgentUsage | undefined; /** Estimated dollar cost of the planning agent run, when a pricing lookup was available. */ agentCostUsd?: number | undefined; /** Wall-clock time the planning agent ran, in ms (present when the agent path ran). */ elapsedMs?: number | undefined; /** * Why the agent path fell back to the heuristic path. Set ONLY on honest * failure fallbacks (spawn error, timeout, cancellation, or output that was * still malformed after one repair attempt), never when `'heuristic'` was * chosen by config or by the planner's decompose gate. */ fallbackReason?: string | undefined; } /** The loose JSON shape a planning agent (or a caller) emits, pre-validation. */ export interface RawDecompositionPhase { title: string; description?: string | undefined; } export interface RawDecompositionWorkItem { title: string; brief: string; phase: string; dependsOn?: string[] | undefined; suggestedArchetype?: string | undefined; likelyFiles?: string[] | undefined; verification?: string[] | undefined; canRunConcurrently?: boolean | undefined; needsReview?: boolean | undefined; /** Best-of-N sibling attempts (see WorkItem.attempts). Omitted/1 ⇒ single item. */ attempts?: number | undefined; /** Best-of-N: allow a judge proposal to auto-pick this item's winner (opt-in). */ autoAcceptWinner?: boolean | undefined; } export interface RawDecomposition { phases: RawDecompositionPhase[]; workItems: RawDecompositionWorkItem[]; } /** Kinds of honest-partial issues `assemblePlanProposal` can flag without throwing. */ export type PlanProposalIssueKind = 'dangling-dependency' | 'dependency-cycle' | 'unresolved-phase'; export interface PlanProposalIssue { readonly kind: PlanProposalIssueKind; readonly workItemTitle: string; readonly message: string; } /** * Turn a raw decomposition into a validated, typed `PlanProposal`. * * Never throws. Malformed input degrades to an honest partial result: an * unresolved phase reference lands its work item in a synthesized "Unphased" * bucket; an unresolved dependency reference is dropped; a dependency cycle * is flagged but left in place (no silent edge removal). Every problem is * reported via the returned `issues` list rather than an exception. * * Dependency resolution mirrors `ExecutionPlanManager.replaceItems` exactly: * a dep that already looks like a UUID passes through unchecked, otherwise * it is resolved by case-insensitive title match, otherwise it is dropped. * * `source` defaults to `'planner-agent'` (the expected majority caller, * `AdaptivePlanner.proposeWorkstream`). Pass `'caller-supplied'` when a * non-agent caller hands in its own decomposition directly. */ export declare function assemblePlanProposal(task: string, strategy: ExecutionStrategy, raw: RawDecomposition, source?: Extract): { proposal: PlanProposal; issues: PlanProposalIssue[]; }; /** * The honest fallback: one phase ("Execute"), one work item whose title and * brief both equal the task text, no dependencies. Used whenever decomposition * was not warranted (see `AdaptivePlanner.shouldDecompose`) or no raw * decomposition is available yet. */ export declare function singleItemProposal(task: string): PlanProposal; /** * Project a `PlanProposal` into the existing `/plan approve` seam. This is * data-only, no store is written here. The caller feeds the result into * `ProjectPlanningService.upsertState` (or an equivalent), which is what * actually persists it and evaluates readiness. * * Direction convention for the emitted `ProjectPlanningDependency` records * (this type has no prior producer in the codebase to inherit a convention * from, so it is fixed here): `fromTaskId` depends on `toTaskId`, i.e. the * edge reads "fromTaskId depends on toTaskId" the same way `WorkItem.dependsOn` * reads "this item depends on these ids". * * `ProjectPlanningTask` has no first-class phase concept, so phase * membership is preserved in `metadata` rather than dropped silently. * * `executionApproved` is always `false` here, approval is a separate, * explicit step owned by `/plan approve` (planning-runtime.ts), never implied * by proposing. */ export declare function planProposalToPlanningState(proposal: PlanProposal): Partial; /** * Project a `PlanProposal` into the plain items `ExecutionPlanManager` deals * in. This is a deliberately lossy projection: `PlanItem` has no room for * verification/likelyFiles/suggestedArchetype, and those stay in the * full-fidelity `ProjectPlanningState` render above. It exists purely so a * caller can hand a proposal's items to `ExecutionPlanManager` without * re-deriving the phase/description/dependency shape by hand. * * Dependencies are expressed as WORK ITEM TITLES, not ids, because * `ExecutionPlanManager.create()` does not resolve dependency references, * only `ExecutionPlanManager.replaceItems()` does, by case-insensitive * description match. The intended call sequence at instantiation is * therefore: * * const plan = planManager.create(title, []); * const { items } = planProposalToExecutionPlanItems(proposal); * planManager.replaceItems(plan.id, items); * * which reuses `replaceItems`' existing dependency resolver end-to-end * instead of re-implementing dependency resolution here. This module defines * no scheduler of its own, ready-to-run item selection stays * `ExecutionPlanManager.getNextItems`'s job. */ export declare function planProposalToExecutionPlanItems(proposal: PlanProposal): { title: string; items: Array>; }; //# sourceMappingURL=plan-proposal.d.ts.map