/** * Task 179 — the Refresh button's actual work. * * Karl's three answers set the shape of this file: * * 1. *"Write it straight away, but commit to git first so I can undo."* — so a * snapshot commit is a PRECONDITION, and a file outside a git repo is * refused outright. The permission to write without asking was bought with * the undo; without a repo there is no undo, so the permission lapses. * 2. *"Both — convert to the new format AND re-check every task's state."* — one * model call does both, because they are the same rewrite. * 3. *"Build it as a general facility."* — which is `worker.ts`; this module is * only its first caller. * * The load-bearing part is not the prompt. A model handed a whole file and asked * for a whole file back will, sooner or later, quietly drop a row — so the answer * is checked before it is written, and a run that loses a task id, fails to * parse, or comes back without the marker is refused WHOLE. The git snapshot is * the belt; these checks are the braces. * * Task 180, after the first live press: `## Done` is now split off before the * prompt is built and re-attached after, and an empty queue is refused outright. * See {@link splitOffDone} — the model was rewriting the one section the format * says is never sent to a model, because it was the only thing left to touch. */ import { type TasksPayload } from './tasks.js'; import { type RunWorkerOptions } from './worker.js'; /** A refusal with a reason the drawer shows verbatim. */ export declare class TasksRefreshError extends Error { } /** * Every task id the file currently carries. * * Deliberately permissive, and matched against the RAW text rather than a parse: * the un-migrated files this exists to convert do not parse at all, and they are * the ones with the most to lose. Both shapes seen in the wild are covered — * `- [ ] 179 · title` (schema) and `- [◕] 178: title` (what came before). * * The `## Format` section is skipped, and that is not a nicety: the manual * carried inside every migrated file demonstrates the grammar with EXAMPLE rows * (`- [ ] 073 · StorePool reaping`, `- [⊘] 084 · Creative-writing benchmark`). * Counting those as tasks would demand the model preserve four ids that do not * exist, so every already-migrated file would fail the drop check and Refresh * would work only on un-migrated ones. Found by test, not by reading. */ export declare function taskIdsIn(content: string): string[]; export interface DoneSplit { /** Everything above `## Done`. The only part a model ever sees of a migrated file. */ head: string; /** The `## Done` heading and everything under it, or null if the file has no such section. */ done: string | null; } /** * Split a tasks file at its `## Done` heading. * * Task 180: `## Done` is the record of work that has left the queue, and the * format's own rule is that it is never sent to a model. The first live Refresh * proved why that has to be enforced with scissors rather than with a sentence in * the prompt: handed the whole file and an empty queue, the model spent its one * call appending "the reload has happened" to twelve finished tasks. * * The LAST heading wins, because the manual carried inside every migrated file * quotes `## Done` while describing the grammar. */ export declare function splitOffDone(content: string): DoneSplit; /** * Re-attach the withheld record to the model's answer. * * The original section goes back byte-for-byte. If the model emitted a `## Done` * of its own — it is told not to — those rows are spliced in ABOVE the preserved * ones rather than discarded: dropping them would lose their ids, and the drop * check would then refuse an answer whose only sin was disobedience. */ export declare function stitchDone(answer: string, originalDone: string): string; /** Strip a ```-fenced wrapper, which a chat-tuned model adds however firmly it is told not to. */ export declare function unfence(answer: string): string; /** * Commit the file as it stands, and return whether there was anything to commit. * * Scoped by pathspec on BOTH commands, so a refresh never sweeps up whatever else * the user happened to have staged. "Nothing to commit" is success, not failure: * the file is already identical to HEAD, so HEAD *is* the undo point. * * Not being in a git repo is the one hard refusal here — see the module comment. * * A repo with no author identity is NOT such a refusal (task 190). Cumulus's own * checkout configures one locally and every other project on this machine does * not, so the whole feature worked exactly where it was developed and nowhere * else. The answer is `commitAll`'s, in worktree.ts: supply a fallback identity * ONLY when the repo has none, and pass it with `-c` so nothing is written into * the user's config and a configured author still authors their own snapshot. */ export declare function snapshotBeforeRefresh(filePath: string): boolean; /** * Is there nothing in this file but headings and blank lines? The March-era * scaffold wrote `# Tasks` and nothing else; converting that through a * model would be an invitation to invent tasks (task 183). */ export declare function isBlankTasksFile(content: string): boolean; export interface RefreshPromptInput { content: string; migrated: boolean; docs: string[]; version: string; /** True when `## Done` was split off and will be re-attached unchanged (task 180). */ doneWithheld: boolean; } export declare function buildRefreshPrompt(input: RefreshPromptInput): string; export interface RefreshResult { payload: TasksPayload; /** False when the file was already identical to HEAD — HEAD is then the undo point. */ committed: boolean; /** True when there was nothing to convert and the empty schema was written instead (task 183). */ created?: boolean; /** Ids the model dropped. Non-empty only on the refusal path, for the message. */ droppedIds: string[]; } export interface RefreshOptions extends RunWorkerOptions { /** Test seam: run this instead of the model. Production leaves it unset. */ runModel?: (prompt: string) => Promise; } /** * Convert and re-check a thread's tasks file in place. * * Order matters and is the design: resolve → snapshot → ask → CHECK → write. * Nothing between the snapshot and the write touches disk, so every refusal below * leaves the file byte-identical to what was just committed. */ export declare function refreshTasksFile(threadName: string, opts?: RefreshOptions): Promise; //# sourceMappingURL=tasks-refresh.d.ts.map