/**
* 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):
*
* #
*
*
* ## 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] · `, 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 = "";
/** 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 `, 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