import { parseFieldLine } from './document.js'; import type { ProfileChange, ProfileProjection, ProfileProvenance, ProfileSupersededLine } from './types.js'; /** Longest line a MACHINE may write. A hand-written line of any length loads fine. */ export declare const MAX_MACHINE_LINE_CHARS = 4096; /** Longest value a MACHINE may write. Same asymmetry, same reason. */ export declare const MAX_MACHINE_VALUE_CHARS = 2000; /** The outcome of one edit: new lines plus what changed, or a refusal. */ export interface ProfileEditResult { readonly ok: boolean; readonly reason: string | null; /** The new raw line array. Identical to the input when `ok` is false. */ readonly lines: readonly string[]; readonly changes: readonly ProfileChange[]; } export interface SetFieldInput { readonly fieldId: string; readonly value: string; readonly provenance: ProfileProvenance | null; /** The date written into a `(superseded …)` marker; defaults to the provenance date. */ readonly supersededOn?: string | undefined; } /** * Write a mechanical field. * * Existing field: its line is rewritten IN PLACE, keeping the label exactly * as the owner capitalised it, and the previous line, provenance suffix and * all, moves into a history comment. Missing field: one line is inserted into * the field block. Missing section: the canonical heading is appended at the * end of the document, because guessing which of the owner's own headings * they meant is worse than adding one. */ export declare function setField(projection: ProfileProjection, input: SetFieldInput): ProfileEditResult; export interface AppendProseInput { /** A heading, canonical or one of the owner's own. */ readonly section: string; readonly text: string; readonly provenance: ProfileProvenance | null; } /** * Add a prose bullet at the end of a section. * * Prose is never superseded: a new bullet is a new bullet, and the owner * removes the old one if they want it gone. Nothing here turns a notes * section into records. */ export declare function appendProse(projection: ProfileProjection, input: AppendProseInput): ProfileEditResult; export interface ForgetInput { /** A mechanical field, or a raw line index for a prose bullet. */ readonly fieldId?: string | undefined; readonly lineIndex?: number | undefined; } /** * Delete a line, and for a field every `` comment it left behind. * * No tombstone, no `deleted: true`, no retention window, a delete that leaves * the record on disk is the dishonesty `docs/decisions/2026-07-06-delete-means- * delete.md` removed. Forgetting something that was not there says so; it does * not report success. */ export declare function forget(projection: ProfileProjection, input: ForgetInput): ProfileEditResult; /** * The most recent superseded value: latest date, and among equal dates the one * furthest down the document, since history is appended in order. */ export declare function mostRecentSuperseded(entries: readonly ProfileSupersededLine[]): ProfileSupersededLine | undefined; /** * Promote the most recent superseded value back to an active line. * * The promoted line is restored EXACTLY as it read, provenance suffix included, * and its history comment is removed. The value being undone is not itself * recorded as history: undo exists to reverse a wrong correction, and a version * that wrote a new comment every time would make repeated undo oscillate * between two values instead of getting back to where the owner was. */ export declare function undo(projection: ProfileProjection, fieldId: string): ProfileEditResult; /** The file operations persistence needs, injected so a test can interrupt one. */ export interface ProfilePersistIo { readonly mkdir: (dir: string) => Promise; readonly writeFile: (path: string, content: string) => Promise; readonly rename: (from: string, to: string) => Promise; readonly remove: (path: string) => Promise; } /** * Write the document atomically: temp file, then `rename()` over the target. * * Same shape as `PersistentStore.persist()` with a text join instead of * `JSON.stringify`. On POSIX the rename is atomic, so an interrupted write * leaves either the old complete file or the new one, never half a profile. * The daemon is the single writer, which is what makes this sufficient with no * lock. */ export declare function persistProfileText(path: string, text: string, io?: ProfilePersistIo): Promise; /** Join a raw line array into the text to persist. */ export declare function profileTextFromLines(lines: readonly string[]): string; /** Exported for the store's own duplicate-label check; keeps the grammar in one place. */ export { parseFieldLine }; /** * Delete one prose line, matched by its exact text within one section. * * End whitespace and the leading LIST MARKER are ignored on both sides; nothing * else is. The marker is syntax, not content: the owner says "forget that I'm * allergic to shellfish", and the `- ` in front of it is a Markdown artefact * they never uttered. Requiring it back would be asking a model to guess at * our storage format, and it would fail closed in the least useful direction, * a delete that silently matches nothing. * * Normalising cannot widen a match onto the WRONG line, because ambiguity is * refused rather than resolved: if both `- Foo` and a bare `Foo` sit in the * same section they now both match, and that is two matches, which is a * refusal. A near-miss delete on the file that holds the owner's address is * worse than a refusal, so an unmatched text removes nothing and says the * line is not there any more, which is true, and the useful thing to tell * them: their file changed under the answer they were working from. * * Two byte-identical lines in one section refuse rather than guess. Removing * "one of them" would report a deletion while the same text stayed in the file, * which is the false-receipt class §9.2 exists to prevent. */ export declare function forgetProseByText(projection: ProfileProjection, section: string, text: string): ProfileEditResult; //# sourceMappingURL=writer.d.ts.map