/** * Docs ops — thin typed wrappers over llmtxt primitives. * * Each function dynamically imports from the relevant llmtxt subpath so that * `llmtxt` can remain an optional peer dependency (following the T1041 pattern). * When a primitive is unavailable the function throws a typed * `LLMTXT_PRIMITIVE_UNAVAILABLE` error rather than a bare module-not-found. * * Supported llmtxt subpaths used here: * - `llmtxt/similarity` — `rankBySimilarity` * - `llmtxt/sdk` — `squashPatches`, `diffVersions`, `reconstructVersion` * - `llmtxt/graph` — `buildGraph` * * @epic T1041 (llmtxt v2026.4.12 adoption) * @see packages/cleo/src/cli/commands/docs.ts (CLI surface) */ import type { KnowledgeGraph } from 'llmtxt/graph'; /** * A single ranked search result from {@link searchDocs}. */ export interface DocsSearchHit { /** Attachment or candidate text identifier. */ readonly id: string; /** Human-readable name (attachment filename or description). */ readonly name: string; /** Normalised similarity score in [0, 1]. */ readonly score: number; /** Owner entity ID that holds this attachment. */ readonly ownerId?: string; } /** * Result returned by {@link searchDocs}. */ export interface DocsSearchResult { /** The original query string. */ readonly query: string; /** Ranked hits, highest score first. */ readonly hits: DocsSearchHit[]; } /** * Result returned by {@link mergeDocs}. */ export interface DocsMergeResult { /** The merged patch text content. */ readonly merged: string; /** Strategy that was applied. */ readonly strategy: 'three-way' | 'cherry-pick' | 'multi-diff'; /** Whether conflicts were detected during merge. */ readonly hasConflicts: boolean; } /** * A node in the document relationship graph. */ export interface DocsGraphNode { /** Node identifier. */ readonly id: string; /** Display label. */ readonly label: string; /** Node type from the graph primitive. */ readonly kind: string; /** Weight from the graph primitive. */ readonly weight: number; } /** * An edge in the document relationship graph. */ export interface DocsGraphEdge { /** Source node ID. */ readonly source: string; /** Target node ID. */ readonly target: string; /** Edge type from the graph primitive. */ readonly relation: string; /** Edge weight from the graph primitive. */ readonly weight: number; } /** * Result returned by {@link buildDocsGraph}. */ export interface DocsGraphResult { /** Graph nodes. */ readonly nodes: DocsGraphNode[]; /** Graph edges. */ readonly edges: DocsGraphEdge[]; /** Raw KnowledgeGraph primitive output for serialisation. */ readonly raw: KnowledgeGraph; } /** * A single ranked attachment from {@link rankDocs}. */ export interface DocsRankHit { /** Attachment SHA-256 content address. */ readonly id: string; /** Human-readable name. */ readonly name: string; /** Normalised relevance score. */ readonly score: number; } /** * Result returned by {@link rankDocs}. */ export interface DocsRankResult { /** Owner entity that was ranked. */ readonly ownerId: string; /** Ranked attachments, best first. */ readonly hits: DocsRankHit[]; } /** * A single version entry from {@link listDocVersions}. */ export interface DocsVersionEntry { /** Attachment ID (sha256 content address). */ readonly attachmentId: string; /** File name as stored. */ readonly name: string; /** Lowercase hex SHA-256 digest. */ readonly sha256: string; /** Byte size. */ readonly sizeBytes: number; /** IANA MIME type, when known. */ readonly mimeType?: string; /** CLEO release version that wrote this version (T11181). */ readonly ownerVersion?: string; /** Sequential doc version counter (T11181). */ readonly docVersion?: number; } /** * Result returned by {@link listDocVersions}. */ export interface DocsVersionsResult { /** Owner entity queried. */ readonly ownerId: string; /** Optional filename filter that was applied. */ readonly nameFilter?: string; /** Version entries. */ readonly versions: DocsVersionEntry[]; } /** * Search attachments by semantic similarity using `llmtxt/similarity.rankBySimilarity`. * * Loads all blob attachments for `ownerId` and ranks them against `query`. * Returns up to `limit` hits (default 10) in descending score order. * * @param query - Free-text search query. * @param opts - Optional owner scope and pagination. * @returns Ranked search hits from highest to lowest similarity score. * * @throws `LLMTXT_PRIMITIVE_UNAVAILABLE` when `llmtxt/similarity` is not installed. * * @example * ```ts * const result = await searchDocs('authentication flow', { ownerId: 'T123', limit: 5 }); * ``` */ export declare function searchDocs(query: string, opts?: { ownerId?: string; limit?: number; projectRoot?: string; }): Promise; /** * A single ranked hit returned by {@link searchAllProjectDocs}. * * @task T9647 */ export interface DocsProjectSearchHit { /** Attachment SHA-256 content address. */ readonly id: string; /** Slug under which the doc is published, when present. */ readonly slug: string | null; /** Taxonomy type (spec|adr|research|handoff|note|llm-readme), when present. */ readonly type: string | null; /** Owner entity type that originally bound the attachment (e.g. `"task"`). */ readonly ownerType: string; /** Owner entity ID that originally bound the attachment. */ readonly ownerId: string; /** Filename / display name. */ readonly name: string; /** Normalised similarity score in [0, 1]. */ readonly score: number; /** Best-effort plaintext snippet centred on the first query-term match. */ readonly snippet: string; } /** * Result returned by {@link searchAllProjectDocs}. * * @task T9647 */ export interface DocsProjectSearchResult { /** The original query string. */ readonly query: string; /** Total number of docs considered before ranking. */ readonly totalDocs: number; /** Ranked hits, highest score first. */ readonly hits: DocsProjectSearchHit[]; } /** * Rank every published doc in the project against `query` using * `llmtxt/similarity.rankBySimilarity` over their text content. * * Unlike {@link searchDocs}, which is scoped to a single owner and ranks by * blob name only, this function builds the full set of project docs (via * {@link AttachmentStore.listAllInProject}), reads the bytes for each text * attachment, ranks against the actual content, and returns hits with slug, * type, snippet and score. * * Non-text attachments (binary blobs, images) are skipped so their bytes do * not pollute the n-gram fingerprint. * * @param query - Free-text search query. * @param opts - Optional `type` filter, `limit`, and `projectRoot` override. * @returns Ranked hits with snippet, highest score first. * * @throws `LLMTXT_PRIMITIVE_UNAVAILABLE` when `llmtxt/similarity` is not installed. * * @task T9647 — viewer + CLI project-wide search * @epic T9631 * * @example * ```ts * const result = await searchAllProjectDocs('release pipeline', { limit: 5 }); * // result.hits[0]: { slug, type, score, snippet, ... } * ``` */ export declare function searchAllProjectDocs(query: string, opts?: { type?: string; limit?: number; projectRoot?: string; }): Promise; /** * A single ranked hit returned by {@link findSimilarDocs}. * * Shape pins the envelope contract documented in T10163 AC: * `{ slug, kind, score, summary, lifecycle_status }`. `kind` is the * taxonomy classification (DocKind name, e.g. `'adr'`, `'spec'`); the * underlying column on `attachments` is named `type` but the surfaced * field follows the canonical doc-kind terminology. * * @task T10163 (Epic T10157 / Saga T9855) */ export interface DocsFindSimilarHit { /** Attachment SHA-256 content address. */ readonly id: string; /** Slug under which the doc is published. */ readonly slug: string; /** Taxonomy classification (DocKind name) — null when the doc has none. */ readonly kind: string | null; /** Normalised cosine similarity score in `[0, 1]`. */ readonly score: number; /** Short human-readable summary from `attachments.summary`. */ readonly summary: string | null; /** Lifecycle state from `attachments.lifecycle_status`. */ readonly lifecycle_status: string; } /** * Result returned by {@link findSimilarDocs}. * * @task T10163 (Epic T10157 / Saga T9855) */ export interface DocsFindSimilarResult { /** The seed slug used as the similarity anchor. */ readonly seedSlug: string; /** DocKind of the seed doc — null when the seed has none. */ readonly seedKind: string | null; /** Total number of docs considered before threshold + limit filtering. */ readonly totalCandidates: number; /** Ranked hits, highest score first, post-threshold + limit. */ readonly hits: DocsFindSimilarHit[]; } /** * Default minimum similarity score for {@link findSimilarDocs} results. * Matches the AC for T10163 and biases toward higher-signal matches. */ export declare const DEFAULT_FIND_SIMILAR_THRESHOLD = 0.5; /** * Default maximum number of hits returned by {@link findSimilarDocs}. */ export declare const DEFAULT_FIND_SIMILAR_LIMIT = 10; /** * Find docs similar to a given seed slug via * `llmtxt/similarity.rankBySimilarity` over their text content. * * Unlike {@link searchAllProjectDocs}, which takes a free-text query, this * function uses the **content of an existing published doc** as the * similarity anchor. Useful for agents asking "what's already been written * about X?" before drafting a new doc. * * Behaviour: * - The seed doc itself is always excluded from the results. * - By default, candidates are filtered to the same `kind` (DocKind / * `attachments.type`) as the seed. Pass `allKinds: true` to disable * this filter and rank cross-kind. * - Hits below `threshold` (default {@link DEFAULT_FIND_SIMILAR_THRESHOLD}) * are dropped before slicing to `limit` * (default {@link DEFAULT_FIND_SIMILAR_LIMIT}). * - Non-text attachments (binary blobs, images) are skipped so their * bytes do not pollute the n-gram fingerprint. * * @param slug - Slug of the seed doc to anchor similarity against. * @param opts - Optional filter + pagination overrides. * @returns Ranked hits with `{ slug, kind, score, summary, lifecycle_status }`. * * @throws `LLMTXT_PRIMITIVE_UNAVAILABLE` when `llmtxt/similarity` is not installed. * @throws `Error` with `code = 'E_DOCS_SLUG_NOT_FOUND'` when the seed slug * does not resolve to a published doc. * * @task T10163 (Epic T10157 · Saga T9855 · E12.C6) * * @example * ```ts * const result = await findSimilarDocs('adr-073-above-epic-naming', { limit: 5 }); * for (const hit of result.hits) { * console.log(`${hit.score.toFixed(2)} ${hit.slug} (${hit.kind})`); * } * ``` */ export declare function findSimilarDocs(slug: string, opts?: { limit?: number; threshold?: number; allKinds?: boolean; projectRoot?: string; }): Promise; /** * Merge two text contents using llmtxt/sdk version primitives. * * Strategies: * - `three-way` (default): squash both patches onto the base via `squashPatches` * - `cherry-pick`: apply the first patch onto the base via `reconstructVersion` * - `multi-diff`: compute a diff summary between two versions via `diffVersions` * * When the primitive throws (e.g. patch conflict), conflict markers are inserted * and `hasConflicts` is set to `true`. * * @param a - First text content to use as patch text for version 1. * @param b - Second text content to use as patch text for version 2. * @param opts - Strategy, optional base content, output path. * @returns Merge result with combined text and conflict indicator. * * @throws `LLMTXT_PRIMITIVE_UNAVAILABLE` when `llmtxt/sdk` is not installed. * * @example * ```ts * const result = await mergeDocs(contentA, contentB, { strategy: 'three-way', base: baseContent }); * ``` */ export declare function mergeDocs(a: string, b: string, opts?: { strategy?: 'three-way' | 'cherry-pick' | 'multi-diff'; base?: string; }): Promise; /** * Build a document relationship graph using `llmtxt/graph.buildGraph`. * * Loads blob attachments for the owner and synthesises `MessageInput` objects * from blob metadata so the primitive can extract nodes, edges, and topics. * * @param opts - Owner scope and project root. * @returns Graph nodes and edges suitable for dot/mermaid/json rendering. * * @throws `LLMTXT_PRIMITIVE_UNAVAILABLE` when `llmtxt/graph` is not installed. * * @example * ```ts * const graph = await buildDocsGraph({ ownerId: 'T123' }); * ``` */ export declare function buildDocsGraph(opts: { ownerId?: string; projectRoot?: string; }): Promise; /** * Rank attachments for an owner by relevance using `llmtxt/similarity.rankBySimilarity`. * * When `query` is provided it is used directly; otherwise the owner ID is used * as the query anchor. * * @param opts - Required `ownerId`, optional `query` and `projectRoot`. * @returns Ranked attachment list, best first. * * @throws `LLMTXT_PRIMITIVE_UNAVAILABLE` when `llmtxt/similarity` is not installed. * * @example * ```ts * const result = await rankDocs({ ownerId: 'T123', query: 'architecture' }); * ``` */ export declare function rankDocs(opts: { ownerId: string; query?: string; projectRoot?: string; }): Promise; /** * List all SHA-256 content-address versions of attachments for an owner. * * Reads the blob manifest for `ownerId` and returns all entries, optionally * filtered by filename. Each entry carries its content-address SHA-256 so * callers can reconstruct history from the store. * * @param opts - Required `ownerId`, optional `name` filter and `projectRoot`. * @returns Version list with SHA-256 content addresses. * * @example * ```ts * const result = await listDocVersions({ ownerId: 'T123', name: 'spec.md' }); * ``` */ export declare function listDocVersions(opts: { ownerId: string; name?: string; projectRoot?: string; }): Promise; /** * Result returned by {@link publishDocs}. * * @epic T9626 (W0) * @task T9701 (ST-PUB-2a — atomic write-side + envelope SHA) */ export interface DocsPublishResult { /** Absolute path the bytes were written to. */ readonly publishedPath: string; /** Project-root-relative form of `publishedPath`. Stable across machines. */ readonly relativePath: string; /** Lowercase hex SHA-256 of the written bytes. */ readonly sha256: string; /** Byte count actually written to disk. */ readonly bytes: number; /** Content-addressed blob id (sha256) selected from the manifest. */ readonly blobSha256: string; /** User-visible attachment name (e.g. `"spec.md"`) selected from the manifest. */ readonly blobName: string; /** Owner entity ID whose blob was published. */ readonly ownerId: string; } /** * Atomically publish an attachment from the docs SSoT to a git-tracked path. * * Reads the named blob from the store and writes it to `toPath` using a * tmp-then-rename atomic pattern with `fsync` before close. When `attachmentId` * is omitted the most recently uploaded blob (latest by `uploadedAt`) is used. * * Path-escape guard: when `toPath` is relative it is joined under * `projectRoot`; absolute paths must still resolve within `projectRoot` * unless the caller passes `allowOutsideRoot: true`. This prevents an * attacker-controlled blob name from being published to an arbitrary path * via traversal sequences. * * Idempotency: writing the same bytes twice produces the same file SHA and * leaves the destination byte-identical (tmp-then-rename overwrites in-place). * * @param opts - Required `ownerId` and `toPath`, optional `attachmentId`, * `projectRoot`, and `allowOutsideRoot`. * @returns Published path, SHA-256 digest of written bytes, byte count, * blob name + sha256, and the owner ID. * * @throws {Error} when no matching attachment is found for the owner. * @throws {Error} when the blob store cannot supply the bytes. * @throws {Error} when the resolved publish path escapes `projectRoot` * and `allowOutsideRoot` is not set. * * @epic T9626 (W0) * @task T9701 (ST-PUB-2a) * * @example * ```ts * const out = await publishDocs({ ownerId: 'T123', toPath: 'docs/spec.md' }); * console.log(`Published ${out.bytes} bytes to ${out.publishedPath}`); * ``` */ export declare function publishDocs(opts: { ownerId: string; attachmentId?: string; toPath: string; projectRoot?: string; /** When true, allow writing to paths outside `projectRoot`. Default: `false`. */ allowOutsideRoot?: boolean; }): Promise; /** * On-disk record of one published doc. Persisted to * `/.cleo/docs-publications.json`. * * The ledger is intentionally a JSON sidecar rather than a SQLite table — * it stores ≤ O(docs) entries, is rewritten atomically, and avoids a * schema migration on the docs domain. * * @epic T9626 (W0) * @task T9703 (ST-PUB-2c — drift detector) */ export interface DocsPublicationRecord { /** Owner entity ID whose blob was published (e.g. `"T123"`). */ readonly ownerId: string; /** Attachment name as stored in the blob manifest. */ readonly blobName: string; /** Project-root-relative path the bytes were written to. */ readonly publishedPath: string; /** SHA-256 of the blob bytes at the time of publish. */ readonly lastBlobSha: string; /** ISO-8601 timestamp of the latest publish event for this record. */ readonly publishedAt: string; } /** * Load the docs-publications ledger from disk. * * Returns an empty list when the ledger file does not yet exist. Tolerates * corrupt JSON by returning an empty list — callers should treat a missing * ledger as "no publications recorded yet" rather than a hard error. * * @internal */ export declare function readPublicationsLedger(projectRoot: string): Promise; /** * Record a publish event in the docs-publications ledger. * * Upserts on `(ownerId, blobName, publishedPath)`. Refreshes * `lastBlobSha` and `publishedAt` when the row already exists so the * ledger always reflects the latest known good publication. * * @param opts - Required record fields. * @epic T9626 (W0) * @task T9703 (ST-PUB-2c) */ export declare function recordPublication(opts: { ownerId: string; blobName: string; publishedPath: string; lastBlobSha: string; projectRoot?: string; }): Promise; /** * List all recorded publications in the ledger. * * Returns an empty array when the ledger does not exist or is unreadable. * * @epic T9626 (W0) * @task T9703 (ST-PUB-2c) */ export declare function listPublications(opts?: { projectRoot?: string; }): Promise; /** * Result returned by {@link syncFromGit}. * * @epic T9626 (W0) * @task T9702 (ST-PUB-2b — reverse-ingest) */ export interface DocsSyncFromGitResult { /** Owner entity ID the file was ingested under. */ readonly ownerId: string; /** Attachment name as stored in the blob manifest. */ readonly blobName: string; /** Project-root-relative source path. */ readonly sourcePath: string; /** SHA-256 of the bytes that were ingested. */ readonly newSha: string; /** SHA-256 of the previously-stored blob with the same name, when present. */ readonly oldSha?: string; /** Byte count of the ingested file. */ readonly bytes: number; /** * What happened during ingest: * - `created` — first time this `(ownerId, name)` pair was seen. * - `updated` — content differs from the latest stored blob. * - `noop` — content sha matches the latest stored blob; no new blob was written. */ readonly action: 'created' | 'updated' | 'noop'; } /** * Ingest a git-tracked file as a new blob version on the docs SSoT. * * Reads `fromPath`, computes its SHA-256, and: * - Returns `{action: 'noop'}` when the latest stored blob for * `(ownerId, blobName)` already matches the content sha (idempotency). * - Otherwise attaches a new blob via `CleoBlobStore.attach` and returns * `{action: 'created' | 'updated', newSha, oldSha?}`. * * The `blobName` defaults to `path.basename(fromPath)` unless explicitly * passed. Use `--name ` from the CLI to override. * * @param opts - Required `ownerId` and `fromPath`, optional `blobName`, * `contentType`, and `projectRoot`. * @returns Action taken + before/after SHAs. * * @throws {Error} when `fromPath` cannot be read. * * @epic T9626 (W0) * @task T9702 (ST-PUB-2b) * * @example * ```ts * const out = await syncFromGit({ ownerId: 'T123', fromPath: 'docs/spec.md' }); * if (out.action === 'noop') console.log('already in sync'); * ``` */ export declare function syncFromGit(opts: { ownerId: string; fromPath: string; blobName?: string; contentType?: string; projectRoot?: string; }): Promise; /** * Single drift item returned by {@link statusDocs}. * * @epic T9626 (W0) * @task T9703 (ST-PUB-2c) */ export interface DocsDriftItem { /** Owner entity ID the blob is attached to. */ readonly ownerId: string; /** Attachment name in the blob manifest. */ readonly blobName: string; /** Project-root-relative path the blob was published to. */ readonly publishedPath: string; /** SHA-256 of the blob in the manifest (the docs SSoT). */ readonly blobSha: string; /** SHA-256 of the file at `publishedPath`, or `null` when missing. */ readonly fileSha: string | null; /** * Drift classification: * - `in-sync` — blobSha === fileSha * - `added` — file exists on disk but no row in the manifest (rare; never set today) * - `modified` — blobSha !== fileSha and file is present * - `deleted` — file is missing from disk */ readonly drift: 'in-sync' | 'added' | 'modified' | 'deleted'; } /** * Result returned by {@link statusDocs}. * * @epic T9626 (W0) * @task T9703 (ST-PUB-2c) */ export interface DocsStatusResult { /** Each recorded publication, with drift classification. */ readonly items: readonly DocsDriftItem[]; /** True when every item is `in-sync`. */ readonly allInSync: boolean; } /** * Read `.cleo/docs-publications.json` and classify drift for each entry. * * Drift cases covered: * - blob present + file present + matching sha → `in-sync` * - blob present + file present + sha mismatch → `modified` * - blob present + file missing → `deleted` * * The `added` classification is reserved for files-on-disk-without-a-manifest-row * and is not produced today — `status` operates strictly on the ledger. * * @param opts - Optional `projectRoot`. * @returns Items list + `allInSync` boolean. Suitable for CI exit-code gating * (0 when `allInSync`, 2 otherwise). * * @epic T9626 (W0) * @task T9703 (ST-PUB-2c) * * @example * ```ts * const status = await statusDocs(); * if (!status.allInSync) process.exit(2); * ``` */ export declare function statusDocs(opts?: { projectRoot?: string; }): Promise; //# sourceMappingURL=docs-ops.d.ts.map