/** * Task decomposition — turn a leaf task defined by its own free-text acceptance * criteria into a container defined by its children. * * PM-Core V2 design-point 3 requires a `task` to be EITHER a leaf with text ACs * OR a container with children, never both, and `addTask` enforces that by * refusing the first child of a text-AC-bearing task. The rule is right; what * was missing was a sanctioned way to cross it. Agents met the guard precisely * when they did the correct thing — decompose a task they had just been told to * break down — and its three suggested remedies were all manual multi-step * surgery, one of which (`cleo update --acceptance ""`) the acceptance * enforcement layer rejects outright whenever `enforcement.acceptance.mode` is * `block` and the task's priority is in `requiredForPriorities`, which is the * default. So the documented escape hatch did not work either. * * `decomposeTask` performs the move as one operation: the parent's text ACs are * lifted onto a new first child, leaving the parent a pure container. * * @task T12281 */ import { type EngineResult } from '../engine-result.js'; import type { DataAccessor } from '../store/data-accessor.js'; /** Options for {@link decomposeTask}. */ export interface DecomposeTaskOptions { /** Task whose text acceptance criteria become a new child's. */ taskId: string; /** * Title for the child that inherits the criteria. * Defaults to the parent's own title, which is almost always what the * criteria described. */ childTitle?: string; /** Description for the child. Defaults to the parent's description. */ childDescription?: string; /** Preview without writing. Mirrors `addTask`'s `dryRun`. */ dryRun?: boolean; } /** Result of {@link decomposeTask}. */ export interface DecomposeTaskResult { /** The task that is now a pure container. */ parentId: string; /** The created child, or `null` on a dry run. */ childId: string | null; /** The criteria texts moved from parent to child, in ordinal order. */ movedAcceptance: string[]; /** True when nothing was written. */ dryRun: boolean; } /** * Move a task's free-text acceptance criteria onto a new first child. * * Ordering is load-bearing and is the reason this is not two CLI calls: * * 1. **Preflight** the child through `addTask({ dryRun: true })`. That runs the * real depth, containment, sibling and duplicate checks and writes nothing, * so a placement that could never succeed is refused while the parent still * holds its criteria. * 2. **Strip** the parent's text AC rows, via `planAcUpdate`/`applyAcPlan` * inside a transaction rather than `updateTask` — the enforcement layer * refuses an empty acceptance list, so the high-level path cannot express * "this task is now a container". * 3. **Create** the child. The parent no longer has text ACs, so design-point 3 * is satisfied honestly rather than bypassed, and `addTask` writes the * parent's `child_task` projection as it normally would. * * The strip MUST precede the create. Doing it the other way round means the * parent already carries a `child_task` projection row when the criteria are * cleared, and `planAcUpdate` with an empty incoming list takes the shrink path * and deletes every row — including that projection. * * If step 3 fails the parent's criteria are restored before the error * propagates, so a failed decompose is a no-op rather than silent criteria loss. * * @param options - Decomposition inputs; see {@link DecomposeTaskOptions}. * @param cwd - Project root. Defaults to the resolved CLEO project. * @param accessor - Optional pre-bound data accessor (tests, batch callers). * @returns The parent/child ids and the criteria that moved. * @throws {@link CleoError} `E_NOT_FOUND` when the task does not exist. * @throws {@link CleoError} `E_VALIDATION` when the task is not a decomposable * leaf: wrong tier, no text criteria, already a container, or carrying * non-text criteria this operation will not silently rewrite. * * @example * ```ts * // T473 is a task with three text ACs and no children. * const r = await decomposeTask({ taskId: 'T473' }); * // r.childId now holds those three ACs; T473 holds one child_task projection. * ``` */ export declare function decomposeTask(options: DecomposeTaskOptions, cwd?: string, accessor?: DataAccessor): Promise; /** * Dispatch-layer wrapper for {@link decomposeTask}. * * Mirrors `taskDelete`/`taskArchive`: converts thrown {@link CleoError}s into an * {@link EngineResult} so the gateway can render a LAFS envelope, preserving the * original LAFS code rather than blanket-labelling every failure. * * @param projectRoot - Absolute path to the CLEO project root. * @param options - Decomposition inputs; see {@link DecomposeTaskOptions}. * @returns Engine result carrying the parent/child ids and moved criteria. */ export declare function taskDecompose(projectRoot: string, options: DecomposeTaskOptions): Promise>; //# sourceMappingURL=decompose.d.ts.map