/** * FAF DNA — the lifecycle of AI context. The "first heartbeat". * * Every project.faf gets a `.faf-dna` lineage record (separate file, NOT * embedded in the .faf — so it's compatible with the clean dialect): * - Birth Certificate: the honest first score (even 0%) — the "before" picture * - Growth Record: version history as the score improves * - Journey: the one-line story, e.g. "22% → 85% → 99% ← 92%" * * Birth DNA = the raw slot-based score at init. The growth from Birth DNA to * the current score is the demonstrated value of FAF. * * Restored 2026-05-21 — silently dropped in the v6.0 clean-architecture rewrite * (it was never knowingly removed). Ported from v5 (sync, v6-native, load-compatible). * * Other shapes. `.faf-dna` is committed lineage, and other tools have written * it in their own shape (claude-faf-mcp's faf_dna wrote `milestones` at the top * level and no `versions` or `growth`). Reading never throws on such a file: it * reads what it can, and missing parts are derived from what is there. Writing * is different: recordGrowth rewrites the file only when faf can prove it wrote * every byte — the file is in faf's shape AND its text is exactly faf's own * serialisation of what it holds (`JSON.stringify(data, null, 2)` and a final * newline), and nothing faf would replace carries a note of the user's. Hand * formatting, CRLF, reordered keys, a repeated key or a number JSON cannot hold * exactly (a 20-digit id) would all be lost in a rewrite, so such a file — like * one in another shape — is read and left exactly as it is, and * `readOnlyReason()` says why in one line. `faf init --force` starts a fresh * lineage when you ask it to. */ export interface BirthCertificate { born: string; birthDNA: number; birthDNASource: 'init' | 'legacy'; projectDNA: string; certificate: string; } export interface VersionEntry { version: string; timestamp: string; score: number; changes: string[]; growth: number; } /** Journey markers only — not score tiers. * Tiers live solely in tiers.ts (Trophy 100 · Gold 99 · Silver 95 · Bronze 85 · …). * There is no championship / elite / perfect milestone. */ export interface Milestone { type: 'birth' | 'doubled' | 'peak' | 'current'; score: number; date: string; version: string; label: string; emoji: string; } export interface FafDNA { birthCertificate: BirthCertificate; versions: VersionEntry[]; current: { version: string; score: number; lastSync: string; }; growth: { totalGrowth: number; daysActive: number; milestones: Milestone[]; }; lastModified: string; format: 'faf-dna-v1'; } /** * The `.faf-dna` lineage of one project: birth, growth and the journey line. * This is the API `faf init` (birth), `faf auto` / `faf refresh` (recordGrowth) * and `faf dna` (getJourney, getBirthDNADisplay, getLog) use. * * - `birth(score)` starts a new lineage and writes it, replacing any * `.faf-dna` there (check `exists()` first unless a fresh start is meant — * `faf init` births only when there is none, or with `--force`). * - `recordGrowth(score, changes)` adds a version when the score changed. * It adds only to a file faf can prove it wrote (see the file header); * for any other file it writes nothing and returns null, and * `readOnlyReason()` says why. * - Reads never throw: a missing, unreadable, non-UTF-8 or non-JSON file, * or one with no usable birth certificate, reads as null / '' / []. * * Every write is atomic and stays inside the project: a `.faf-dna` link that * leads out of the project, dangles or leads to a file with another name is * refused (and reads as absent). A write is refused, too, when the file * changed on disk after this manager read it (another `faf` process recorded * growth meanwhile, say): SafePathError `changed`, nothing written. */ export declare class FafDNAManager { private readonly projectPath; private readonly dnaPath; private dna; /** True when the loaded file is one faf wrote (so it may be added to). */ private own; /** The text read from `.faf-dna` (or last written), for the write's check. */ private text; /** Why the file is read only, when it is; null when faf may add to it. */ private reason; /** True once this manager found no `.faf-dna` (exists / load / birth) and * has not written one since: a birth then refuses a file that appeared * meanwhile instead of writing over it. */ private sawNoFile; constructor(projectPath: string); exists(): boolean; /** Birth — the first heartbeat. Writes the birth certificate with the honest first score. * When this manager found no `.faf-dna` (now, or at an earlier exists() / * load()), a file that appeared since is refused (SafePathError `changed`) * rather than written over; over a `.faf-dna` that is there, birth starts a * fresh lineage (`faf init --force`). */ birth(birthDNA: number): FafDNA; /** True when `.faf-dna` exists and is one faf wrote — in faf's shape, and * exactly faf's own text — so recordGrowth may add to it. Any other file is * read, not written. */ isFafShape(): boolean; /** Why faf leaves this `.faf-dna` as it is, in one plain line — or null when * there is none, or recordGrowth may add to it. */ readOnlyReason(): string | null; /** Record growth — a new score on the journey. Returns null when there is no * DNA yet, or when the file is in another tool's shape (nothing is written). */ recordGrowth(newScore: number, changes: string[]): FafDNA | null; /** The one-line journey: e.g. "22% → 85% → 99% ← 92%". */ getJourney(): string; getBirthDNADisplay(): { current: number; birthDNA: number; growth: number; born: string; } | null; /** Complete version history, newest last. */ getLog(): string[]; /** Load `.faf-dna`. Never throws: missing, unreadable, not UTF-8, not JSON, a * refused link, or no usable birth certificate → null. A file faf did not * write (another shape, or not exactly faf's text) loads as a readable view * (see the file header). */ load(): FafDNA | null; /** The file's text (strict UTF-8) and its JSON, or null with the reason noted. */ private read; private save; private generateProjectDNA; private generateCertificate; private incrementVersion; private daysSince; private updateMilestones; }