/** * The partial first-run index (change: refine-first-run-partial-serving). * * Until this existed, the first `analyze` on a repository was all-or-nothing: every tool * answered "no index found" for the whole build, even though the pipeline finishes mapping, * the dependency graph, and the inventory extractors long before the call-graph pass — the * phase that dominates the wall clock. OpenLore already had the right contract for a STALE * index (serve what exists, disclose that a refresh is running, never present it as fresh); * this module extends that contract to the ABSENT case, which is also the onboarding case. * * Three properties decide the whole design: * * 1. **A partial index is never an analysis artifact.** It is written to its own directory * under `.openlore/runtime/`, never into `.openlore/analysis/`. So the published * generation, the fingerprint, the SQLite store, the build attestation, and every * exporter/importer/attester that reads the analysis directory cannot see it — not by * remembering to check, but because it is not there. The explicit refusals elsewhere are * a second lock on a door that is already shut. * * 2. **It carries its own integrity commit.** The partial directory publishes its own * generation manifest through the same {@link publishGeneration} primitive the real * analysis uses, so a reader binds to a content digest rather than to whatever bytes * happen to be on disk mid-write. * * 3. **It is read as untrusted repository content, like every other `.openlore/` artifact.** * A hostile repository can ship a `.openlore/runtime/partial-analysis/` directory and no * `.openlore/analysis/`, which is precisely the state this module serves in. Every read * here therefore goes through the same bounded, no-symlink, regular-files-only descriptor * path the analysis-artifact caches use. * * A partial index is local serving state and nothing else. It is deleted when the build that * owns it completes, and a reader ignores one whose owner is gone. */ /** Directory name, under the runtime directory, that holds the partial index. */ export declare const PARTIAL_INDEX_SUBDIR = "partial-analysis"; /** The completeness receipt's file name inside {@link PARTIAL_INDEX_SUBDIR}. */ export declare const PARTIAL_STAMP_FILE = "partial-index.json"; /** * The artifacts a partial index holds. * * Deliberately a SUBSET of `REQUIRED_ANALYSIS_ARTIFACTS`, and deliberately without * `fingerprint.json`: the fingerprint is the freshness key that says "this tree is analyzed". * A partial index must never be able to answer that question. * * Every entry here is READ by something — see {@link partialArtifactPathIfLive}'s callers. * An artifact nothing reads is cost with no benefit, paid by exactly the large first builds * this feature exists to improve. */ export declare const PARTIAL_REQUIRED_ARTIFACTS: readonly ["repo-structure.json", "llm-context.json", "dependency-graph.json"]; export type PartialArtifactName = (typeof PARTIAL_REQUIRED_ARTIFACTS)[number]; /** * How long a partial index whose owning process is still alive may go unrefreshed before a * reader stops trusting it. The writer re-stamps on the artifact-phase heartbeat (15s), so * ten minutes of silence means the build is wedged, not slow. */ export declare const PARTIAL_STAMP_MAX_AGE_MS: number; /** * How far in the FUTURE a stamp may be dated before it is refused. * * Without a lower bound, `Date.now() - Date.parse(updatedAt)` is negative for a * future-dated stamp — finite, under the max age, and therefore accepted forever. A * repository can ship a `partial-index.json` dated 2099 with `pid: 1` (always alive) and be * permanently "mid-build". A minute of tolerance covers real clock skew between the analyzing * process and the serving one; anything beyond it is not skew. */ export declare const PARTIAL_STAMP_MAX_SKEW_MS: number; /** * What a partial index does not hold, named by the READER rather than read from the file. * * These strings are rendered into an `[openlore index]` line, which reads to an agent as * OpenLore's own voice — the highest-trust channel the server has. The stamp is untrusted * repository content, so taking this text from it would let a repository put words in that * voice. The writer produces exactly this list anyway, so nothing is lost by owning it here. */ export declare const PARTIAL_INDEX_ABSENT_FACTS: readonly ["the call graph (function-to-function edges, fan-in/fan-out, hubs)", "function signatures and the searchable symbol corpus", string]; /** * The build phase a set of facts was flushed at. * * `phase` describes the FACTS in the index. It is deliberately separate from * {@link PartialIndexStamp.buildPhase}, which describes what the build is doing now: the * heartbeat advances the latter, and if it advanced the former the index would advertise a * completeness its bytes do not have. */ export type PartialPhase = 'extractors'; /** * What a partial index knows about itself. * * `filesExtracted` counts files whose CALL-GRAPH facts are in this index — zero for every * flush this lane currently takes, because the call-graph pass is the phase still running. It * is reported rather than omitted precisely so the gap is legible: a reader that sees * `filesExtracted: 0` against a four-figure `filesTotal` cannot mistake this for an index that * merely missed a few files. */ export interface PartialIndexStamp { partial: true; /** The phase whose facts are in this index. Fixed at flush time. */ phase: PartialPhase; /** What the build is doing now. Advanced by the heartbeat; never feeds a completeness claim. */ buildPhase: string; /** Files whose call-graph facts are present in this index. */ filesExtracted: number; /** Files the mapping phase saw, including the ones it permanently skipped. */ filesTotal: number; /** Files in the analyzed corpus, and so in the flushed structure. */ filesMapped: number; startedAt: string; updatedAt: string; /** The analyzing process. A partial index outliving its owner is abandoned. */ pid: number; /** * The analysis directory this index was written for, resolved. * * Nothing else binds a partial index to the tree it describes — it has no fingerprint by * design. A `.openlore` copied between repositories (a template repo, `cp -r`, a shared home) * would otherwise serve one tree's structure as another's for the whole liveness window. */ analysisDir: string; } export declare function partialIndexDirOf(analysisDir: string): string; export declare function partialStampPathOf(analysisDir: string): string; /** * How far through the BUILD this index's owner has got, 0-100. * * Named for what it is. It is the pipeline's stage number, not a fraction of the index that * exists: the call-graph pass is one stage and most of the wall clock. Nothing renders this as * "N% complete" — see {@link describePartialIndex}, which says what the index holds instead. */ export declare function partialBuildStagePercent(stamp: PartialIndexStamp): number; /** * The one paragraph a partial answer is disclosed with. * * Says what the index HOLDS, what it does not hold, and that the difference is invisible to * this answer rather than absent from the repository. It deliberately does NOT report a * completeness percentage: the honest denominator would be the call-graph pass, which has not * started, and any percentage in that position reads as "how much of the index exists". */ export declare function describePartialIndex(stamp: PartialIndexStamp): string; /** The artifact set a flush writes, in the caller's own shapes. */ export interface PartialIndexFlush { repoStructure: unknown; llmContext: unknown; dependencyGraph: unknown; stamp: PartialIndexStamp; } /** * Write one partial index and commit it. * * Every write is atomic (temp + rename); the generation manifest is published before the * stamp, so "a stamp exists" implies "the artifacts it describes are committed" — every reader * keys on the stamp, which is what makes a half-written flush unobservable rather than merely * unlikely. Fail-soft by contract: a partial index is an optimization on the first-run * experience, and no failure to produce one may disturb the analysis that is actually running. */ export declare function flushPartialIndex(analysisDir: string, flush: PartialIndexFlush): Promise; /** * Refresh only the stamp on an already-committed partial index. * * Used by the artifact-phase heartbeat: the facts have not changed, but the receipt must keep * saying what the build is doing and that its owner is alive. The stamp is not part of the * published generation, so re-stamping cannot invalidate the commit — and it advances * `buildPhase` only, never `phase`, so it cannot inflate what the index claims to hold. */ export declare function refreshPartialIndexStamp(analysisDir: string, update: { buildPhase: string; }): Promise; /** * The live partial index for this analysis directory, or null. * * Null covers six distinct situations, all of which mean the same thing to a caller — there is * nothing trustworthy to serve: no partial index exists, its stamp is unparseable, no committed * generation stands behind it, its owning process is gone, its owner is alive but has not * re-stamped within {@link PARTIAL_STAMP_MAX_AGE_MS}, or the stamp is dated in the future * beyond ordinary clock skew. An abandoned partial index is never served, because "the build is * still running" is half of what the receipt promises. */ export declare function readPartialIndexStamp(analysisDir: string): Promise; /** * The path to read `artifact` from, when — and only when — a live partial index holds it. * * The one seam through which a reader that ordinarily reads `.openlore/analysis/` can be * answered from the partial index instead. Returning a PATH rather than the bytes keeps every * caller on its own existing bounded reader and its own cache, so nothing about how these * artifacts are read changes — only where the bytes come from while a first build runs. */ export declare function partialArtifactPathIfLive(analysisDir: string, artifact: PartialArtifactName): Promise; /** * Read one committed artifact out of a live partial index. * * Bound to the partial directory's own generation manifest and to that manifest's content * digest, so a read that lands mid-flush is refused rather than parsed, and read through the * bounded no-symlink descriptor path so a hostile repository cannot redirect it, stall it with * a FIFO, or make it allocate without limit. */ export declare function readPartialArtifact(analysisDir: string, artifact: PartialArtifactName): Promise; /** * Remove the partial index. * * Called once the real analysis has published its generation: from that moment the partial * index is not merely redundant but wrong, and leaving it would let a reader that consults it * serve a worse answer than the one on disk. * * `maxRetries` covers Windows, where a reader still holding a descriptor makes the unlink fail * with EBUSY/EPERM. A survivor there is not cosmetic: it would go on telling callers a build is * running until its stamp aged out. */ export declare function clearPartialIndex(analysisDir: string): Promise; //# sourceMappingURL=partial-index.d.ts.map