import type { CodegraphConfigModel } from './codegraph-project-config.js'; import { type ReadCodegraphExcludeConfig, type ReadTrackedFiles } from './codegraph-exclude-reconciler.js'; /** * Exit code `peaks codegraph status` uses when the index itself is * incomplete or stale — distinct from `CODEGRAPH_INTEGRITY_EXIT_CODE` (74, * the exclude-rule gate) and from `CODEGRAPH_INIT_CONFLICT_EXIT_CODE` (73). * * Why distinct rather than reusing 74: the two gates have different * REMEDIATIONS. 74 means "these `exclude` rules must be dropped", and a CI * job keyed on 74 already knows to run `repair-exclude`. 75 means "the * index content does not match the repository — a supported tracked file * is missing from it, or it holds rows for files that are gone". Folding * that into 74 would silently re-point an existing consumer at a different * fix. When BOTH gates fire, 74 wins (the exclude gap is the upstream * cause; repairing it and rebuilding also clears staleness). * * NOTE (2026-09-16 policy change): 75 is only reachable in STRICT mode. * See `isCodegraphIndexStrictMode`. */ export declare const CODEGRAPH_INDEX_INTEGRITY_EXIT_CODE = 75; /** * Exit code for "the index axis could not be measured at all" — the db is * present but unreadable, or its schema no longer has the `files` table * (an upstream bump), or `git ls-files` / the config read failed. * * Distinct from BOTH 0 and 75 on purpose, and it applies in every mode * including the advisory default: * * - 0 would say "I checked and it is fine". It does not. * - 75 would say "I checked and the index does not cover the * repository", which asserts a measurement that never happened. * * Why it is not merely advisory (the user's option C covers `gap`, not * this): option C exists so that upgrading peaks-loop cannot turn a * *healthy* project red — the three downstream triggers it names (a * tracked `.mjs`, an un-purged dead row, a deliberately narrowed * `include`) are all `gap`-class. "Could not read my own input" is never a * healthy-project condition: it requires `.codegraph/codegraph.db` to be * present but unreadable, and `.codegraph/` is gitignored, so a downstream * CI checkout normally has no index at all and reports `not-applicable` * (exit 0, no output). This is the exact failure class * `codegraph-exclude-reconciler.ts:76-82` documents as the reason that * module exists. */ export declare const CODEGRAPH_INDEX_UNEVALUABLE_EXIT_CODE = 76; /** * The one command that repairs what this axis detects (slice-002). Exported * as a constant rather than spelled out at each site so the human line, the * doctor message and the JSON `nextActions` cannot name three different * things: the renderer below, the doctor check and the CLI envelope all * interpolate this value. * * It repairs BOTH axes in one run — `include` normalization, `exclude` rule * drops against the normalized include, then a FORCED index rebuild (the * only upstream path that drops rows for files deleted in an earlier * commit; see `CodegraphExcludeRepairOptions.reindex`). */ export declare const CODEGRAPH_REPAIR_INDEX_COMMAND = "peaks codegraph repair-index --project "; /** * Opt-in switch for the user's option C decision (advisory by default, * blocking on request). Set `PEAKS_CODEGRAPH_INDEX_STRICT=1` (or `true`) * to make a detected index gap block: `status` exits 75 and the doctor * check loses its `severity: 'warning'` tag and flips the doctor exit code. * * Why an environment variable rather than a config key or a CLI flag: * * - ONE mechanism covers both consumers. `peaks codegraph status` and * `peaks doctor` are separate command surfaces; a CLI flag would have * to be threaded through the doctor's option plumbing as well, and the * default doctor probe takes no arguments. * - CI is where the need lives, and CI sets environment variables. * - A config key would have to live in the gitignored * `.codegraph/config.json` (upstream's file, which this slice must * never write) — so it would not survive a clone and could not * configure a CI job at all. * - It writes no state, so it does not weaken the read-only contract. * * Discoverability: the advisory warning text names this variable * verbatim, so an operator who wants blocking is told how to get it at * the moment they see the finding. */ export declare const CODEGRAPH_INDEX_STRICT_ENV_VAR = "PEAKS_CODEGRAPH_INDEX_STRICT"; /** True when the project opted in to blocking index-gap verdicts. */ export declare function isCodegraphIndexStrictMode(env?: Readonly>): boolean; /** * The four outcomes of the index axis, kept apart so that no consumer * that CAN carry four outcomes collapses two: * * - `clean` — measured, nothing wrong. * - `gap` — measured, the index does not cover the repository. * - `not-evaluated` — the axis was attempted and FAILED (unreadable db, * schema drift, no git work tree, malformed/missing * config). The index is NOT known to be fine. * - `not-applicable`— there is no index here at all (no `codegraph.db`): * the pre-init / dangling state, which is not a defect. * * `not-evaluated` and `not-applicable` both used to be `null` in the * machine envelope, which is how "I could not check" came to be reported * identically to "there is nothing to check". * * SCOPE OF THE CLAIM (narrowed under R12-2 — the earlier wording, "no * consumer can collapse two of them", was overbroad). This module keeps * all four apart, and so do `peaks codegraph status`'s two channels (the * `[FAIL]`/`[WARN]` tags and `indexIntegrityVerdict` + the exit codes 0 / * 75 / 76). The DOCTOR does not: its check shape is `{ok, severity?}` * (legacy `DoctorCheck`, kept back-compatible on purpose), so * `not-evaluated` and an advisory `gap` are BOTH `ok:false, * severity:'warning'` and are told apart by message text alone — see * `doctor-service/checks/codegraph-index-integrity.ts`. That collapse is * recorded, not fixed: neither state reads as `ok:true`, so the invariant * this axis exists for ("could not evaluate" must never pass as "verified * clean") still holds there, and in the advisory default neither state * moves the doctor exit code — so no exit-code consumer is misled by it. * Under `PEAKS_CODEGRAPH_INDEX_STRICT=1` the two ARE separated in the * machine fields (`severity` survives on `not-evaluated`, is dropped from * `gap`), which is pinned by a test. */ export type CodegraphIndexIntegrityVerdict = 'clean' | 'gap' | 'not-evaluated' | 'not-applicable'; /** * Fold the inspected report (null when there is no index to inspect) and * the caught failure (null when nothing threw) into one verdict. This is * the single place the four outcomes are distinguished, so a caller * cannot re-derive them differently. */ export declare function resolveCodegraphIndexIntegrityVerdict(report: CodegraphIndexIntegrityReport | null, warning: string | null): CodegraphIndexIntegrityVerdict; /** * The exit code the index axis contributes, or `null` when it * contributes none (clean / not-applicable → the caller's exit code is * left alone). */ export declare function codegraphIndexIntegrityExitCode(verdict: CodegraphIndexIntegrityVerdict, strict: boolean): number | null; export type CodegraphIndexIntegrityReport = { /** Absolute path of the inspected config file — see `codegraph-project-config.ts`. */ readonly configPath: string; /** Absolute path of the inspected index database. */ readonly databasePath: string; /** * Which config model the include axis was evaluated under. Reported rather * than implied, because it decides whether an empty `includeGap` means * "measured, nothing withheld" or "this upstream's include list cannot * withhold anything" — two different claims that must not read alike. */ readonly admissionModel: CodegraphConfigModel; /** True when either axis is non-empty. `[OK]` must not be printed then. */ readonly gap: boolean; /** Git-tracked files upstream's extractor would ingest (any extension). */ readonly trackedSourceCount: number; /** Of those, the ones the config's `include` globs admit. */ readonly admittedTrackedCount: number; /** Class ① — supported tracked files `include` does not admit. */ readonly includeGap: readonly string[]; /** Rows in the index's `files` table. */ readonly indexedFileCount: number; readonly deadRows: readonly string[]; }; /** * Everything `inspectCodegraphIndexIntegrityFrom` needs, already resolved. * Pure input: no fs, no spawn, no clock — the adapters below supply it. */ export type CodegraphIndexIntegrityInput = { readonly configPath: string; readonly databasePath: string; readonly trackedFiles: readonly string[]; /** The config's `include` globs. Only consulted under `include-whitelist`. */ readonly include: readonly string[]; /** How this upstream's config treats `include` — see `codegraph-project-config.ts`. */ readonly admissionModel: CodegraphConfigModel; /** Project-relative paths carried by the index's `files` table. */ readonly indexedPaths: readonly string[]; /** True when upstream's extractor supports `filePath`'s language. */ readonly supportsPath: (filePath: string) => boolean; /** True when a project-relative index path still exists on disk. */ readonly pathExists: (projectRelativePath: string) => boolean; }; /** * Pure fold over already-resolved data. Both axes are set differences, so * neither can be satisfied by a self-consistency assertion: axis ① compares * git's tracked set against the `include` matcher, axis ② compares the * index's own rows against the filesystem. */ export declare function inspectCodegraphIndexIntegrityFrom(input: CodegraphIndexIntegrityInput): CodegraphIndexIntegrityReport; /** The upstream decision, verbatim: detect the language, then ask if it has a grammar. */ export declare function upstreamSupportsPath(filePath: string): boolean; /** * Per-call, bounded replacement for the per-row `existsSync` above. * * WHY: the fold calls `pathExists` once per index row (`inspectCodegraph * IndexIntegrityFrom`), so the gate is O(rows) full-path stats at ~13.5 µs * each — 35 ms at 1.2k rows, 1,923 ms at 100x. It is unbounded in the size * of the index. What is replaced is the COST, not the authority: a row is * confirmed PRESENT by looking its basename up in one cached listing of its * parent directory, so the number of filesystem calls is bounded by the * number of DISTINCT DIRECTORIES the index names — which is bounded by the * repository, not by the index. * * A listing may only answer `true`, and only for an entry whose dirent is * positively a plain file or a plain directory. For exactly those entries, * "the name is in `readdir`" and "`existsSync` on the joined path is true" * cannot disagree. Everything else falls through to * `codegraphIndexPathExists` — the old implementation, verbatim — so the * classes this cache cannot decide are decided exactly as they always were: * * - a symlink or junction entry (`isSymbolicLink()`, Windows included: * a junction reports `isDirectory() === false`), because a BROKEN link * is listed by `readdir` and is absent from disk; * - a case-differing path on a case-insensitive filesystem — the listing * carries the on-disk spelling, so `SRC/OK.TS` misses and the stat * decides (true on Windows, false on Linux, exactly as before); * - a path naming a directory, or any non-file special entry; * - a parent that is missing, unreadable, or not a directory at all * (`readdir` throws → `null`, memoized so the failure costs one call); * - a trailing-slash or empty row, and a project root that does not exist. * * The cache lives for ONE `inspectCodegraphIndexIntegrity` call, so a * second command re-reads the tree and cannot be served a stale listing. * Within a call, a listing taken before a concurrent delete is the only * divergence from the old code — both implementations race the filesystem, * and neither holds a snapshot. */ export declare function createCodegraphIndexPathExists(): (projectRoot: string, projectRelativePath: string) => boolean; /** * Injection seam for `inspectCodegraphIndexIntegrity`. * * `trackedFiles` / `config` are the ALREADY-READ shared inputs (perf audit * F1) — the CLI reads them once and hands the same values to both codegraph * axes, so the `git ls-files` spawn, the config read and the `include` * glob compilation happen once per command instead of twice. They are * values, not reader functions, because that is what the caller has: the * read has already happened by the time either axis runs. * * Both are READ-MARKED (code review R4-1): only a value returned by the * readers is admissible. An explicit `[]` — which the seam used to accept * and which silently meant "nothing is tracked", erasing every include-gap * — is now a compile error and a run-time throw. `undefined`/omitted still * means "read it yourself", exactly as before. * * The three remaining fields are genuine per-call adapters, used by tests * to drive the fold without touching a real db or the real oracle. */ export type CodegraphIndexIntegrityDeps = { readonly trackedFiles?: ReadTrackedFiles | undefined; readonly config?: ReadCodegraphExcludeConfig | undefined; readonly readIndexedPaths?: (projectRoot: string) => readonly string[]; readonly supportsPath?: (filePath: string) => boolean; readonly pathExists?: (projectRoot: string, projectRelativePath: string) => boolean; }; /** * Read-only entry point: resolve git's tracked files, the config's * `include` globs and the index's own rows from disk, then fold them into * one report. Throws (never silently degrades) when the project is not a * git work tree, the config is missing/malformed, or the index is absent — * callers that must stay alive (`status`, doctor) catch and surface it. */ export declare function inspectCodegraphIndexIntegrity(projectRoot: string, deps?: CodegraphIndexIntegrityDeps): CodegraphIndexIntegrityReport; export declare function renderCodegraphIndexIntegrityLines(report: CodegraphIndexIntegrityReport, blocking: boolean): readonly string[];