/** * Task creation logic. * @task T4460 * @epic T4454 * @task T1633 — BRAIN-powered duplicate detection */ import type { Task, TaskKind, TaskPriority, TaskScope, TaskSeverity, TaskSize, TaskStatus, TasksAddParams, TaskType, TaskVerification } from '@cleocode/contracts'; import type { DataAccessor } from '../store/data-accessor.js'; export { normalizeAcceptance } from './acceptance-input.js'; /** * Options for creating a task. * * `description` is **required** per CLEO's anti-hallucination rules — * every task must have both a title and a description, and they must differ. */ export interface AddTaskOptions { title: string; description?: string; status?: TaskStatus; priority?: TaskPriority; type?: TaskType; parentId?: string | null; /** * Where {@link parentId} came from. * * @remarks * T12136 (GH #1232/#1238): `E_CLEO_DEPTH_EXCEEDED` named a parent the caller * had never mentioned — it was inherited from the session's `current` * pointer — and the message read as though the caller had asked to file * under it. The suggested remedy then pointed at the parent epic of a task * the caller never intended to be under at all, and that advice was the only * actionable-looking thing in the message. * * Knowing the provenance lets the error describe the world the caller is in * rather than the checker's state. Defaults to `'explicit'` so a caller that * genuinely passed `--parent` is never told it was inferred. * * @defaultValue 'explicit' */ parentSource?: 'explicit' | 'session-inference'; size?: TaskSize; phase?: string; labels?: string[]; files?: string[]; acceptance?: string[]; depends?: string[]; /** Critical-priority justification committed with task_created audit provenance. */ dependsWaiver?: string; notes?: string; position?: number; addPhase?: boolean; dryRun?: boolean; /** RCASD-IVTR+C pipeline stage to assign. Auto-resolved if not provided. @task T060 */ pipelineStage?: string; /** * Task kind axis — intent of work, orthogonal to {@link type}. * Defaults to `'work'` at the DB level. DB column named `role` (T9067 deferral). * @task T944 * @task T9072 */ kind?: TaskKind; /** * Task scope axis — granularity of work, orthogonal to {@link type} and {@link kind}. * Defaults to `'feature'` at the DB level. * @task T944 */ scope?: TaskScope; /** * Severity level. Valid for any kind (widened from bug-only by T9073). OWNER-WRITE-ONLY. * @task T944 * @task T9073 */ severity?: TaskSeverity; /** * Bypass the E_DUPLICATE_TASK_LIKELY rejection guard. * * When true, task creation proceeds even when BRAIN similarity scoring * determines the incoming task is very likely a duplicate (score >= 0.92). * The bypass is audited to `.cleo/audit/duplicate-bypass.jsonl`. * * @task T1633 */ forceDuplicate?: boolean; /** * Bypass the T11811 write-time containment invariant (orphan / terminal-parent * / wrong-tier rejection). * * MUST be set ONLY by historical data-movement paths — exodus migration, * `cleo import`, restore/JSON-merge replay — which legitimately re-insert * rows that predate the strict-spine rule (the fleet still carries legacy * orphans). Agent-facing `cleo add` / `add-batch` MUST NOT set this; the * invariant is the whole point of the guard for net-new work. * * @task T11811 */ skipContainmentInvariant?: boolean; /** * Suppress ONLY the PM-Core V2 design-point 3 guard (a `task`/`subtask` * holding free-text ACs may not gain a child). Every other check still runs. * * MUST be set ONLY by {@link decomposeTask}, and there only on its `dryRun` * preflight: that preflight asks "would this child be placeable?" while the * parent still holds the criteria it is about to hand over, so design-point 3 * is the one rule that is legitimately not yet satisfied. The real write that * follows sets nothing — by then the parent has been stripped and the guard * passes honestly. Agent-facing `cleo add` / `add-batch` MUST NOT set this. * * @task T12281 */ skipMixedAcParentGuard?: boolean; /** * Resolve PM-Core V2 design-point 3 in-line instead of refusing. * * When the parent is a `task`/`subtask` carrying free-text ACs, run * {@link decomposeTask} first — lifting those criteria onto a new first child * so the parent becomes a pure container — and then proceed with this add. * * OPT-IN by design. Without it the guard still fires unchanged, because * decomposing rewrites a task the caller only named as `--parent`, and doing * that silently would mutate a row nobody asked about. The result reports * what moved in {@link AddTaskResult.autoDecomposed}. * * @task T12298 */ autoDecompose?: boolean; } /** Result of adding a task. */ export interface AddTaskResult { /** * Set when `autoDecompose` resolved design-point 3 before this add: the * parent's criteria were lifted onto this child first. Absent otherwise. * * @task T12298 */ autoDecomposed?: { childId: string; movedAcceptance: string[]; }; task: Task; duplicate?: boolean; dryRun?: boolean; /** IDs created by this mutation, suitable for WorkGraph/projection consumers. */ createdIds?: { tasks: string[]; acceptanceCriteria: string[]; }; /** Non-blocking warnings emitted during validation. @task T089 */ warnings?: string[]; /** * IDs of done ancestors that were reopened because this child was added under * a `done` parent (PM-Core V2 design-point 5 — a done parent must never * silently hold an unsatisfied child). Empty/absent when nothing was reopened. * * @saga T10538 (PM-Core V2 agent-trust) */ reopenedAncestors?: string[]; } /** * Translate canonical creation input into core options without dropping fields. * @param params - Canonical task creation input. * @returns Core options with the parent alias and enum boundaries resolved. * @remarks Entry points share this boundary; CLI parsing does not establish authorization. * @example * ```ts * toTaskAddOptions({ title: 'Fix persistence', description: 'Verify durable task writes' }); * ``` */ export declare function toTaskAddOptions(params: TasksAddParams): AddTaskOptions; /** * Enforce the established explicit-critical dependency policy before mutation. * @param priority - Explicit requested priority; waivers require critical. * @param justification - Original reason, retained verbatim in transaction audit details. * @param dependencies - Effective dependencies after applying the requested changes. * @throws CleoError for invalid waivers or critical mutations without dependencies. * @remarks Entry points share this boundary; CLI parsing does not establish authorization. * @example * ```ts * validateDependencyWaiver('critical', 'Independent restoration', []); * ``` */ export declare function validateDependencyWaiver(priority: string | undefined, justification: string | undefined, dependencies: readonly string[]): void; /** * Build the default verification metadata applied to every new task. * Gates are initialized to false (not yet passed). `passed` starts false * because no gates have been verified yet. * @task T061 * * @example * ```ts * const ts = '2026-04-27T00:00:00.000Z'; * const v = buildDefaultVerification(ts); * * console.assert(v.passed === false, 'starts not-passed'); * console.assert(v.round === 1, 'starts at round 1'); * console.assert(v.gates.implemented === false, 'implemented gate starts false'); * console.assert(v.gates.testsPassed === false, 'testsPassed gate starts false'); * console.assert(v.gates.qaPassed === false, 'qaPassed gate starts false'); * console.assert(v.initializedAt === ts, 'stores initializedAt timestamp'); * ``` */ export declare function buildDefaultVerification(initializedAt: string): TaskVerification; /** * Validate a task title. * @task T4460 */ export declare function validateTitle(title: string): void; /** * Validate task status. * @task T4460 */ export declare function validateStatus(status: string): asserts status is TaskStatus; /** Valid string priority values. */ export declare const VALID_PRIORITIES: readonly TaskPriority[]; /** * Normalize priority to canonical string format. * Accepts both string names ("critical","high","medium","low") and numeric (1-9). * Returns the canonical string format per todo.schema.json. * @task T4572 * * @example * ```ts * // String form — returned as-is (lowercased) * const p1 = normalizePriority('high'); * console.assert(p1 === 'high', 'string priority passthrough'); * * // Numeric form — mapped to canonical string * const p2 = normalizePriority(1); // 1-2 → 'critical' * console.assert(p2 === 'critical', 'numeric 1 → critical'); * * const p3 = normalizePriority(5); // 5-6 → 'medium' * console.assert(p3 === 'medium', 'numeric 5 → medium'); * * const p4 = normalizePriority(9); // 7-9 → 'low' * console.assert(p4 === 'low', 'numeric 9 → low'); * ``` */ export declare function normalizePriority(priority: string | number): TaskPriority; /** * Validate task priority. * @task T4460 * @task T4572 */ export declare function validatePriority(priority: string): asserts priority is TaskPriority; /** * Validate task type. * @task T4460 */ export declare function validateTaskType(type: string): asserts type is TaskType; /** * Validate task size. * @task T4460 */ export declare function validateSize(size: string): asserts size is TaskSize; /** * Validate label format. * * Labels must be one of: * - Lowercase alphanumeric with hyphens/periods (`^[a-z][a-z0-9.-]*$`) * — e.g. `my-label`, `v1.0`, `bug`, `security` * - A canonical task-ID-shaped label (`^T\d{3,}$`) — case preserved per ADR-073 * so agents can tag tasks with their canonical parent task ID (e.g. `T9813`). * - A gate profile label `profile:` (T13474), from {@link GATE_PROFILES} * only and at most one per set. * * @task T4460 * @task T9824 — uppercase task-ID label acceptance * @task T13474 */ export declare function validateLabels(labels: string[]): void; /** * Validate phase slug format. * @task T4460 */ export declare function validatePhaseFormat(phase: string): void; /** * Validate dependency IDs exist. * @task T4460 */ export declare function validateDepends(depends: string[], tasks: Task[]): void; /** * Validate parent hierarchy constraints. * @task T4460 */ export declare function validateParent(parentId: string, tasks: Task[], maxDepth?: number, maxSiblings?: number): void; /** * Get the depth of a task in the hierarchy. * @task T4460 * * @example * ```ts * const now = new Date().toISOString(); * const tasks = [ * { id: 'T001', title: 'Epic', description: '', type: 'epic', status: 'pending', * priority: 'medium', size: 'medium', parentId: null, * position: 1, positionVersion: 0, createdAt: now, updatedAt: now }, * { id: 'T002', title: 'Task', description: '', type: 'task', status: 'pending', * priority: 'medium', size: 'medium', parentId: 'T001', * position: 1, positionVersion: 0, createdAt: now, updatedAt: now }, * { id: 'T003', title: 'Subtask', description: '', type: 'subtask', status: 'pending', * priority: 'medium', size: 'medium', parentId: 'T002', * position: 1, positionVersion: 0, createdAt: now, updatedAt: now }, * ] as Task[]; * * console.assert(getTaskDepth('T001', tasks) === 0, 'root epic depth = 0'); * console.assert(getTaskDepth('T002', tasks) === 1, 'child task depth = 1'); * console.assert(getTaskDepth('T003', tasks) === 2, 'grandchild subtask depth = 2'); * ``` */ export declare function getTaskDepth(taskId: string, tasks: Task[]): number; export declare function inferTaskType(parentId: string | null | undefined, tasks: Task[]): TaskType; /** * Get the next position for a task within a parent scope. * @task T4460 * * @example * ```ts * const now = new Date().toISOString(); * const tasks = [ * { id: 'T001', title: 'First', description: '', type: 'task', status: 'pending', * priority: 'medium', size: 'medium', parentId: 'T000', * position: 1, positionVersion: 0, createdAt: now, updatedAt: now }, * { id: 'T002', title: 'Second', description: '', type: 'task', status: 'pending', * priority: 'medium', size: 'medium', parentId: 'T000', * position: 3, positionVersion: 0, createdAt: now, updatedAt: now }, * ] as Task[]; * * // Next position = max existing position + 1 * const next = getNextPosition('T000', tasks); * console.assert(next === 4, 'next position after max(1,3) is 4'); * * // No siblings → position 1 * const first = getNextPosition('T999', tasks); * console.assert(first === 1, 'first child gets position 1'); * ``` */ export declare function getNextPosition(parentId: string | null | undefined, tasks: Task[]): number; /** * Log an operation to the audit log. * @task T4460 */ export declare function logOperation(operation: string, taskId: string, details: Record, accessor?: import('../store/data-accessor.js').DataAccessor): Promise; /** * Check for recent duplicate task. * @task T4460 */ export declare function findRecentDuplicate(title: string, phase: string | undefined, tasks: Task[], windowSeconds?: number): Task | null; /** * Add a new task to the todo file. * @task T4460 */ export declare function addTask(options: AddTaskOptions, cwd?: string, accessor?: DataAccessor): Promise; //# sourceMappingURL=add.d.ts.map