/** * Task creation logic. * @task T4460 * @epic T4454 * @task T1633 — BRAIN-powered duplicate detection */ import type { Task, TaskKind, TaskPriority, TaskScope, TaskSeverity, TaskSize, TaskStatus, TaskType, TaskVerification } from '@cleocode/contracts'; import type { DataAccessor } from '../store/data-accessor.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; size?: TaskSize; phase?: string; labels?: string[]; files?: string[]; acceptance?: string[]; depends?: 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; } /** Result of adding a task. */ export interface AddTaskResult { 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[]; } /** Normalize AC arrays once so legacy JSON, AC rows, and projections stay aligned. */ export declare function normalizeAcceptance(acceptance: readonly string[] | undefined): string[] | undefined; /** * 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`). * * @task T4460 * @task T9824 — uppercase task-ID label acceptance */ 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; /** * Infer task type from parent context. * @task T4460 * * @example * ```ts * const now = new Date().toISOString(); * const tasks = [ * { id: 'T001', title: 'My Epic', description: '', type: 'epic', * status: 'pending', priority: 'medium', size: 'medium', parentId: null, * position: 1, positionVersion: 0, createdAt: now, updatedAt: now }, * { id: 'T002', title: 'Child Task', description: '', type: 'task', * status: 'pending', priority: 'medium', size: 'medium', parentId: 'T001', * position: 1, positionVersion: 0, createdAt: now, updatedAt: now }, * ] as Task[]; * * // No parent → standalone task * console.assert(inferTaskType(null, tasks) === 'task', 'no parent → task'); * * // Epic parent → child is a task * console.assert(inferTaskType('T001', tasks) === 'task', 'epic parent → task'); * * // Task parent → child is a subtask * console.assert(inferTaskType('T002', tasks) === 'subtask', 'task parent → subtask'); * ``` */ 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