/** * Task 176 — `Tasks.md` as a machine format. * * The file is managed by the gateway and by agents writing through the schema * prescribed inside the file itself; humans read it, they do not hand-edit it. * * Structure (in order): * * # * <!-- cumulus:tasks v1 --> * * ## Format ← verbatim instructions. NOT parsed. ALWAYS sent to the model. * ## Queue ← the build order, top to bottom * ## Done ← finished work. Parsed, never sent to the model. * * A row is `- [status] <id> · <title>`, indented two spaces per depth level. * A row indented under another is BLOCKED BY the nearest row above it one * level shallower, so a blocker is always above what it blocks and the first * row of the queue can never be blocked. * * Fields are indented `key value` lines under their row. Exactly five keys * parse; anything else in a parsed section is a hard error rather than a * silent skip, because a silent skip is how a drag-reorder loses data. */ import { TASKS_FORMAT_SECTION } from './tasks-format.js'; export { TASKS_FORMAT_SECTION }; /** * Where a project's tasks file lives, relative to the project directory. The * whole resolution rule (task 183): the drawer, Refresh and the prompt all use * exactly this, and nothing is configured. */ export declare const TASKS_FILE_NAME = "Tasks.md"; /** Presence of this line is what makes a `Tasks.md` a schema file. */ export declare const TASKS_FILE_MARKER = "<!-- cumulus:tasks v1 -->"; /** Field keys the parser accepts. Nothing else parses. */ export declare const TASK_FIELD_KEYS: readonly ["doc", "phase", "note", "dep", "done"]; export type TaskFieldKey = (typeof TASK_FIELD_KEYS)[number]; /** Task 178 — a row that left the queue having worked. */ export declare const TASK_STATUS_CLOSED = "\u2705"; /** * Task 178 — a row that left the queue WITHOUT working: dropped, superseded, or * gone stale. Distinct from `❌` (implemented, then failed its test), which is a * verdict on work that happened; this is the absence of a verdict. */ export declare const TASK_STATUS_ARCHIVED = "\u2298"; /** What the drawer's two disposition buttons do to a row. */ export type TaskDisposition = 'closed' | 'archived'; export interface TaskRow { /** Digits, unique across the whole file. */ id: string; /** Raw contents of the status box: ' ', '◔', '◑', '◕', '✅', '❌'. */ status: string; title: string; /** 0 = not blocked. n = blocked by the nearest preceding row of depth n-1. */ depth: number; doc?: string; phase?: string; notes: string[]; /** Completion date. Only meaningful in `## Done`. */ done?: string; /** True when this row's blocked-by edge was proposed, not confirmed. */ depProposed: boolean; } export interface TasksParseError { /** 1-indexed line number in the source file. */ line: number; text: string; reason: string; } export interface TasksFile { /** Lines before the first `##` heading, including the `#` title and the marker. */ preamble: string[]; /** The `## Format` section, verbatim and including its heading. Never parsed. */ format: string[]; queue: TaskRow[]; done: TaskRow[]; errors: TasksParseError[]; } /** Thrown by the mutation helpers when a requested change would break the schema. */ export declare class TasksFileError extends Error { } /** * A new project's `Tasks.md`. Scaffolding emits the schema rather than a bare * title, so a freshly created thread's drawer works on its first open — a * scaffold that produced the old free-form shape would leave two formats alive. */ export declare function emptyTasksFile(projectName: string): string; /** * Is this a schema tasks file? The marker must stand alone on its own line * within the first {@link MARKER_MAX_LINE} lines — a mention inside a sentence, * a code span or a quoted copy of the manual is not a marker. * * Task 177: this was a substring test, so writing the marker into a sentence of * ordimor's `RULES.md` made that file parse as a task list and cost it 85% of * its content on every turn. */ export declare function hasTasksMarker(content: string): boolean; /** * Parse a schema `Tasks.md`. Always returns a file — errors are collected, not * thrown, so the drawer can show a broken line instead of an empty panel. Writes * are refused while `errors` is non-empty (see {@link assertWritable}). */ export declare function parseTasksFile(content: string): TasksFile; /** Round-trips a parsed file. Rewrites field order and spacing to canonical form. */ export declare function serializeTasksFile(file: TasksFile): string; /** Last index of the subtree rooted at `i` (every following row deeper than it). */ export declare function subtreeEnd(rows: TaskRow[], i: number): number; /** Index of the row that blocks `i`, or -1 when `i` is not blocked. */ export declare function parentIndexOf(rows: TaskRow[], i: number): number; /** Index of the depth-0 row whose cluster contains `i`. */ export declare function rootIndexOf(rows: TaskRow[], i: number): number; /** * Move `dragId` relative to `targetId`. Three cases, one rule — the cluster is * the unit of movement, and a drag can never separate a task from its blocker: * * - same parent → sibling reorder, dragged row's subtree travels * - different cluster → the dragged row's WHOLE cluster moves, blocker first * - same cluster, other parent → illegal, nothing is written */ export declare function moveTask(rows: TaskRow[], dragId: string, targetId: string, position: 'before' | 'after'): TaskRow[]; /** * Accept or reject a proposed dependency edge. Accepting keeps the nesting and * drops the marker; rejecting outdents the row and its subtree to depth 0 and * lands them after the cluster they just left. */ export declare function resolveProposedDep(rows: TaskRow[], id: string, accept: boolean): TaskRow[]; /** * Task 178 — take rows out of the queue and put them at the top of `## Done`. * * `closed` stamps `[✅]`, `archived` stamps `[⊘]`; both stamp `done <date>`, which * is exactly what the manual tells an agent to do by hand. Ordering is * newest-first, matching how the file has been kept since 176. * * A dispositioned row's children are PROMOTED one level rather than removed: the * thing that blocked them is finished, so they are now blocked by whatever blocked * it — or by nothing. That keeps {@link assertValidQueue} true without a special * case, and it is the only edit that does not silently drop work. */ export declare function dispositionTasks(file: TasksFile, ids: string[], disposition: TaskDisposition, today: string): TasksFile; /** Structural invariants every write must preserve. */ export declare function assertValidQueue(rows: TaskRow[]): void; /** Refuses writes to a file the parser did not fully understand. */ export declare function assertWritable(file: TasksFile): void; /** * What the model sees. The `## Format` section is verbatim (it is the * instructions for writing the file back), the task in hand is in full, the * rest of the queue is title lines, and `## Done` is one summary line. * * Returns null when `content` is not a schema tasks file, so an un-migrated * `Tasks.md` in any other project is passed through untouched — and equally * when it is one the parser could not fully read. * * Task 177: that second case used to project anyway and append a note counting * the bad lines, which meant everything under an unrecognised heading left the * prompt without trace. A file that does not parse is passed through whole: it * costs its full size, which is visible, instead of losing content, which is * not. */ export declare function projectTasksFile(content: string): string | null; //# sourceMappingURL=tasks-file.d.ts.map