/** * Block markers for the faf-managed front section. * * Markdown files (AGENTS.md, CLAUDE.md, GEMINI.md) use HTML comments; non-markdown * files (.cursorrules) pass hash-comment markers via the `start`/`end` args. */ export declare const FAF_START = ""; export declare const FAF_END = ""; /** Split into lines keeping each line's own terminator (\r\n, \r or \n). */ export declare function linesWithEnds(text: string): string[]; export declare const stripEnd: (line: string) => string; /** * faf's marked range in `text`: in each reading, the last START line before * the first END line after it (the innermost pair — text between an earlier * START and the last one is kept), both outside every Markdown region. The * two readings must find the same pair; otherwise there is no range. Returns * the char range covering both lines (the END line's terminator excluded), or * null. A leading BOM on line 1 stays outside the range. `isStart` and * `isEnd` are asked about each line shown as text in at least one reading, * once, in order, on the line with its terminator and that BOM removed. Past * MAX_OPEN_CONTAINERS open containers the CommonMark reading stops, and there * is no range (a pair found before that point still counts). Time grows with * the text's length, not with how deep it nests. */ export declare function findMarkedRange(text: string, isStart: (line: string) => boolean, isEnd: (line: string) => boolean): { start: number; end: number; } | null; /** * Locate the faf-managed block in `text`: a START marker line and the first * END marker line after it, both outside fenced and indented code, raw HTML * blocks and multi-line HTML comments, under both readings (see * {@link findMarkedRange}). Returns the char range covering both marker lines * (terminator of the END line excluded), or null when there is no complete * block both readings agree on. * * Markers are matched as WHOLE LINES at column 0 — exactly the marker text, * nothing after it — never as substrings. Substring search was a real bug * (7.1.4–7.11.0): renderAgentsMd quoted the marker tokens in its own * blockquote, so on every re-run `indexOf(end)` hit the quote, cut the old * block in half and appended its stale tail below the new block — `faf * export --agents` grew AGENTS.md by ~49 lines per run. The same happened to * users who documented the markers in a code fence above the block. * * Two START lines before an END: the pair is the last START and that END. * faf's own body never holds a column-0 START (see {@link wrapFafBlock}), so * the text between the two STARTs is the user's and stays. A START with no * END is not a block: the caller treats "no block" as a user file and * prefixes — it never reclaims. faf's own block is always found again where * faf put it (see {@link placeFafBlock}). */ export declare function findFafBlock(text: string, start?: string, end?: string): { start: number; end: number; } | null; /** No line of faf's own body leaves more block quotes, lists and list items * open than this (as CommonMark reads it) — far below the reader's cap, * MAX_OPEN_CONTAINERS, so the next run always reads the block to its end, * quoted (one quote more) or not. See {@link capDepth}. */ export declare const BODY_MAX_CONTAINERS = 16; /** * `head`, faf's block (`wrapped`, from {@link wrapFafBlock}) and `tail` as one * text in which {@link findFafBlock} finds the block exactly where it was put * — so the next write updates it in place and never stacks a second one. A * body that would not be found there (it leaves a region open in only one * reading, or the text before it holds a raw HTML block the body continues) * is quoted line by line instead. If even that is not found, it throws a * SafePathError (`unplaceable`) naming `path` (the file being written): * ": faf could not place its block where the next run finds it again — * faf left it unchanged" — one line, and nothing is written. */ export declare function placeFafBlock(head: string, wrapped: string, tail: string, start?: string, end?: string, path?: string): string; /** The managed block: START, the body (guarded — see guardBody), END. */ export declare function wrapFafBlock(block: string, start?: string, end?: string): string; /** The file's new text: `existing` (null when there is no file) with `wrapped` * as its managed block, placed where the next scan finds it again (see * {@link placeFafBlock}; `path` names the file in its refusal). */ export declare function withFafBlock(existing: string | null, wrapped: string, start?: string, end?: string, path?: string): string; /** Read a resolved path, or null when nothing is there yet. Only ENOENT reads * as "no file"; any other error is thrown, so a file faf could not read is * never treated as empty and written fresh. The text is decoded strictly: a * file that is not UTF-8 (a UTF-16 file, cp1252 bytes) is refused * (SafePathError `not-utf8`) and left as it is — see readUtf8. */ export declare function readIfPresent(path: string): string | null; export interface InjectOptions { /** The project folder the file must stay inside. Default: the file's own * folder. Pass it when the file sits in a subfolder (`.github/…`), so a * linked subfolder cannot carry the write out of the project. */ root?: string; } /** * Non-destructively write a faf-managed block into a file. * * - file does not exist → create it containing just the block * - file has the markers → replace ONLY the content between them (update in place) * - anything else → PREFIX the block; everything already there is preserved * (a leading BOM stays at byte 0) * * faf replaces only text it can prove it wrote: what sits between its own * marker lines. A file with no marker lines is never reclaimed, whatever it * starts or ends with. Idempotent: re-running updates the managed block in * place and never duplicates it. Enhance, never replace. * * The file is resolved inside its project first: a link that leads outside, * a dangling link, a link to a file with another name (CLAUDE.md → README.md) * and anything in `.git` are refused (SafePathError) and nothing is written; a * link to a file of the same name, or between AI context files (CLAUDE.md → * AGENTS.md), is written through and stays a link. A file that is not UTF-8 * is refused and left as it is. The write is atomic — a failure leaves the * original exactly as it was — and is refused if the file changed on disk * after faf read it. A block faf cannot place where its next run finds it * again is refused too (SafePathError `unplaceable`), and nothing is written. */ export declare function injectFafBlock(path: string, block: string, start?: string, end?: string, opts?: InjectOptions): void; /** * The one line the CLI prints when faf's block goes on top of a file that has * no block of its own but holds older faf text — or null: * - the file's first line is faf's old metastamp (``): faf * never reclaims such a file (it cannot prove it wrote the text), so the * old faf text stays below the new block; * - a whole-line START marker sits inside a code fence, a raw HTML block * or an HTML comment (in either reading — see {@link findFafBlock}): the * older block there is an example to faf, and stays below the new one; * - a whole-line START marker sits past more than MAX_OPEN_CONTAINERS * nested lists or quotes: faf did not read that far, and the older block * stays below the new one. * `label` names the file (`CLAUDE.md`). `existing` is the file's text before * the write (null when there was none). faf-mcp and claude-faf-mcp print the * same line through this export. */ export declare function legacyStampNote(label: string, existing: string | null, start?: string, end?: string): string | null; /** {@link legacyStampNote} for the file at `path`, read the way * injectFafBlock reads it — before the write. Null when there is no note, or * when the file cannot be read (the write itself then says why). */ export declare function legacyStampNoteAt(path: string, label: string, start?: string, end?: string, opts?: InjectOptions): string | null;