/** * Batch task creation with TRUE single-transaction atomicity. * * All N `addTask` inserts are wrapped in a SINGLE `dataAccessor.transaction()` * call. This works via two coordinated changes in the store layer (T9814): * * 1. `DataAccessor.transaction()` in sqlite-data-accessor.ts tracks nesting * depth: outer call (depth=0) uses `BEGIN IMMEDIATE` (preserves RESERVED * lock semantics); nested calls (depth>0) use `SAVEPOINT _cleo_tx_` * which nests inside the already-open outer transaction. * * 2. `allocateNextTaskId()` in sequence/index.ts uses `SAVEPOINT` instead of * `BEGIN IMMEDIATE`, so it nests correctly inside the outer batch transaction * when called during an `addBatchTasks` run, and works standalone otherwise. * * When `addBatchTasks` opens the outer `dataAccessor.transaction()` (BEGIN * IMMEDIATE), all `addTask` calls within it share the same SQLite transaction. * If ANY call throws, the outer BEGIN IMMEDIATE transaction is rolled back, * reverting ALL inserts. No intermediate state is ever visible. * * Closes the CORE gap exposed by T9813: the CLI `add-batch` command was a * for-loop calling `tasks.add` N times with NO rollback on failure. * * @task T9814 * @epic T9813 */ import { type EngineResult } from '../engine-result.js'; import type { DataAccessor } from '../store/data-accessor.js'; import { type AddTaskOptions, type AddTaskResult } from './add.js'; /** * Options for `addBatchTasks`. */ export interface AddBatchOptions { /** List of task specs to insert. Must be non-empty. */ tasks: AddTaskOptions[]; /** Optional default parent ID applied when a task spec omits `parentId`. */ defaultParent?: string; /** * When true, validate and predict IDs without writing to the database. * All tasks in the batch receive a synthetic ID of the form * `T???` (matching the single-add dry-run convention). */ dryRun?: boolean; } /** * Result of `addBatchTasks`. * * Dry-run semantics (T10599): * - `created` is always 0 in dry-run mode. * - `wouldCreate` carries the predicted creation count. * - `wouldAffect` carries the generic predicted affected-entity count. * - `insertedCount` is the durable write count (0 in dry-run, N in live). * - `validatedCount` counts specs that passed validation. * - `validationFindings` surfaces per-spec warnings for agent inspection. */ export interface AddBatchResult { /** Number of tasks that were created (0 on dry-run). */ created: number; /** Individual results for each input task spec (in order). */ tasks: AddTaskResult[]; /** Whether this was a dry run. */ dryRun?: boolean; /** * Number of tasks that would be created if this dry run were executed live. * Only present when `dryRun` is `true`. * * @task T10599 */ wouldCreate?: number; /** * Generic dry-run affected count. For add-batch this equals `wouldCreate`. * * @task T10599 */ wouldAffect?: number; /** * Number of task specs that successfully passed validation. * In a dry-run equals `wouldCreate` when all specs validate. * * @task T10599 */ validatedCount?: number; /** * Number of tasks durably written to the database. * - Live run: equals `created`. * - Dry run: always `0`. * * @task T10599 */ insertedCount?: number; /** * Per-spec non-blocking validation warnings (dry-run only). * Only populated when at least one spec produced a warning. * * @task T10599 */ validationFindings?: Array<{ index: number; warnings: string[]; }>; } /** * Add multiple tasks in a single atomic transaction. * * All inserts execute inside ONE `dataAccessor.transaction()` call. Any * failure inside the transaction causes the entire SAVEPOINT to be rolled * back — no partial writes are ever visible. The batch is atomic in the * strict SQL sense. * * @param opts - Batch options (task specs + optional defaultParent + dryRun). * @param dataAccessor - Pre-opened DataAccessor (caller manages lifecycle). * @param cwd - Optional project root (passed through to `addTask`). * @returns AddBatchResult with per-task results and aggregate count. * * @throws Error when any task spec fails validation. The error message * identifies the failing task index and title. All prior inserts are * rolled back before the error surfaces. * * @example * ```ts * import { addBatchTasks } from './add-batch.js'; * import { getTaskAccessor } from '../store/data-accessor.js'; * * const accessor = await getTaskAccessor('/project'); * const result = await addBatchTasks( * { * tasks: [ * { title: 'Task A', description: 'First task' }, * { title: 'Task B', description: 'Second task', parentId: 'T001' }, * ], * }, * accessor, * '/project', * ); * console.log(result.created); // 2 * ``` */ export declare function addBatchTasks(opts: AddBatchOptions, dataAccessor: DataAccessor, cwd?: string): Promise; /** * Wire-format task spec accepted by the `tasks.add-batch` dispatch operation. * Uses `parent` (ADR-057 D2 canonical wire field) instead of `parentId`. */ export interface AddBatchTaskSpec { title: string; description?: string; /** Canonical wire field for parent task ID (ADR-057 D2). */ parent?: string; depends?: string[]; priority?: string; labels?: string[]; type?: string; acceptance?: string[]; phase?: string; size?: string; notes?: string; files?: string[]; kind?: string; scope?: string; severity?: string; forceDuplicate?: boolean; } /** * Normalized wrapper for {@link addBatchTasks}. * ADR-057 D1 shape: (projectRoot: string, params: TasksAddBatchParams) * * Returns `EngineResult` so the dispatch layer can call * `wrapCoreResult(await tasksAddBatchOp(...), 'add-batch')` — the same * pattern used by every other Core op in tasks.ts. * * Maps wire-format `parent` → internal `parentId` before calling Core. * * @param projectRoot - Absolute path to the project root. * @param params - Batch operation parameters (wire format). * @returns EngineResult wrapping AddBatchResult on success, error on failure. * * @task T9814 */ export declare function tasksAddBatchOp(projectRoot: string, params: { tasks: AddBatchTaskSpec[]; defaultParent?: string; dryRun?: boolean; }): Promise>; //# sourceMappingURL=add-batch.d.ts.map