/** * Machine-level content-addressed cache (Proposal 006 B3 / 001 §5) for the pure * per-file extractor/scanner results (A6 INV-1 scan, B1 fact extraction). * * Key = sha256 over caller-supplied parts — for the fact extractor, * (adapter_version, repo-relative path, content_hash). Every part is * worktree-INDEPENDENT: the content hash is by definition, and the * repo-relative path is the same in every worktree of the repo. So identical * bytes at the same in-repo path share ONE entry across the user's many * worktrees (the cross-worktree sharing 001 §5 calls for), while the path stays * in the key so two files with the same bytes at DIFFERENT paths don't collide * (the cached value embeds its path). Content-addressing also makes writes * idempotent: a given key's value is deterministic, so a concurrent write of * the same key is harmless (both write identical bytes; temp+rename just makes * each atomic). * * Correctness has ZERO cache dependency (001 §6 red line): the value is exactly * `compute()` — a cold run (empty cache) and a warm run are byte-identical, and * a missing/corrupt/disabled cache just recomputes. `cacheDir: undefined` * disables it entirely (the path CI can take to prove independence). * * Not modeled (documented deferral): the git-index fast-path for reading a * clean tracked file's blob OID without re-hashing. Hashing the content * directly is always correct and worktree-identical; the index lookup is only a * micro-optimization, so B3 ships the always-correct content-hash path. */ export declare function defaultCacheDir(): string; /** sha256 of a file's content — the worktree-independent part of the cache key. */ export declare function contentHash(content: string): string; /** The cache key: a hash over the ordered key parts (adapter version, content hash, config hash). */ export declare function cacheKey(parts: string[]): string; /** * Read-or-compute. On a hit, returns the parsed cached value; on a miss (or * disabled/corrupt cache), computes, atomically writes (temp + rename), and * returns. `compute` must be a PURE function of the same inputs the key was * derived from — that's what makes the cache sound. `compute` may be sync or * async (`await` handles both). */ /** * Recorded in `deps` for a path the computation TRIED to read and could not. * * A dependency is not only "a file I read" — it is equally "a file I looked for * and did not find", because the answer would have been different had it * existed. Recording only successful reads leaves the mirror-image of the bug * this whole mechanism exists to prevent: a path that resolved to nothing gets * no entry, so the result looks dependency-free, and the file later APPEARING * never invalidates anything. The entry then keeps serving a fact that is * missing rather than one that is stale — same silent wrongness, opposite * direction. * * Cannot collide with a real value: `contentHash` returns 64 lowercase hex * chars, and this is neither. Prefer {@link depMark} over writing it yourself — * the sentinel is an in-band encoding (entries are JSON on disk, so it has to * be a string), and one shared writer is what keeps it from becoming a * convention each caller re-implements slightly differently. */ export declare const MISSING_DEP_SENTINEL = "\0missing"; declare const depMarkBrand: unique symbol; /** * A `deps` value. Branded so it can only come from {@link depMark} — a plain * string won't typecheck, which is what makes "one writer" a property of the * code rather than a convention in a comment. Erased at runtime; entries are * still plain strings on disk. */ export type DepMark = string & { readonly [depMarkBrand]: true; }; /** * The value to record in `deps` for one consulted path: its content hash when * read, the missing-sentinel when not. The single writer for both cases. */ export declare function depMark(content: string | null): DepMark; /** * A cached value plus the OTHER files it was derived from. * * A content-addressed entry keyed only by its own file's hash is correct right * up until an extractor reads a second file — after that, editing that second * file alone leaves the entry stale while its key still matches. Recording the * dependency hashes lets the reader detect exactly that and recompute. */ export interface CachedWithDeps { value: T; /** * repo-relative path → {@link depMark} of that path at the time the value was * computed. EVERY path the computation consulted appears here, including ones * that could not be read. */ deps: Record; } export declare function withCache(cacheDir: string | undefined, key: string, compute: () => T | Promise): Promise; /** * `withCache` for computations that may read other files. * * `compute` returns both the value and the dependency hashes it consulted. On * read, an entry is only a HIT when every recorded dependency still matches — * `verifyDeps` is asked to re-check them, returning `true` for FRESH (serve the * cached value) and `false` for stale (recompute). A dependency that changed, * disappeared, or newly APPEARED makes it a miss, so neither the stale-fact nor * the missing-fact hazard above can happen. * * `verifyDeps` is optional only because a dependency-free computation genuinely * doesn't need one. Producing dependencies WITHOUT supplying a verifier is a * caller bug and throws: without it every such entry would be written and then * never be able to match, degrading silently into "cache disabled for exactly * the entries that most need it" — the kind of quiet failure this module is * built to avoid. */ export declare function withCacheDeps(cacheDir: string | undefined, key: string, compute: () => Promise>, verifyDeps?: (deps: Record) => Promise): Promise>; /** * Bounds the cache to `maxEntries` most-recently-modified entries (the LRU cap * from 001 §5). Cheap and best-effort — called once per run, not per file — so * an occasional over-count between prunes is fine. */ export declare function pruneCache(cacheDir: string, maxEntries: number): Promise; export {}; //# sourceMappingURL=content-cache.d.ts.map