/** * AttachmentStore-backed DocsAccessor adapter for `cleo docs import`. * * The default {@link DocsAccessorImpl} writes to `manifest.db` (the * llmtxt/blob store) and keeps an in-memory blob index. That is incorrect * for the import path because: * * 1. Slug→sha lookup (`cleo docs fetch `) queries the * `attachments` table in tasks.db via {@link AttachmentStore.findBySlug}. * 2. Project-wide search (`searchAllProjectDocs`) queries * {@link AttachmentStore.listAllInProject}. * 3. The in-memory index is dropped between processes — idempotency * across `cleo docs import` runs would always re-import everything. * * This adapter implements {@link DocsAccessor} on top of * {@link AttachmentStore.put}, populating the `slug` and `type` extras the * docs surface relies on. Bytes are written content-addressed to * `.cleo/attachments/sha256/` and registered in `tasks.db.attachments`. * * The owner ref MUST be one of the six supported entity types * ({@link assertOwnerType}). Imports without a natural task owner are * registered under the sentinel `task` / `__project__` pair so they appear * in `listAllInProject` without colliding with real task IDs. * * @epic T9791 (Saga T9787) * @task T9791 */ import type { DocExportFormat, DocRecord, DocSearchHit, DocsAccessor, ListDocsFilters, StoreDocParams, StoreDocResult } from '@cleocode/contracts'; /** * Sentinel owner used when imported docs do not carry a natural task ID. * * The value is registered against {@link OWNER_TYPE} so it surfaces from * `cleo docs list --project` without clashing with real task IDs in the * `tasks` table (no FK constraint exists on `attachment_refs.owner_id`). * * @task T9791 */ export declare const IMPORT_PROJECT_OWNER_ID = "__project__"; /** * Owner type used for the import sentinel. `task` is the most common type * surfaced by the docs CLI and keeps the row in the same listing as * task-attached docs without forcing a schema change. * * @task T9791 */ export declare const IMPORT_OWNER_TYPE: "task"; /** * Extract a task ID (e.g. `T9782`) from a relative path's leading segment. * * Returns the task ID when the path begins with `T` followed by digits * (case-insensitive), the project sentinel otherwise. Used by the rcasd * + agent-outputs source dirs where the directory name is often the task * the doc was produced for. * * @param relPath - Project-relative path of the imported file. * @returns Either a `T###` task ID or {@link IMPORT_PROJECT_OWNER_ID}. * * @task T9791 */ export declare function inferOwnerIdFromPath(relPath: string): string; /** * Concrete {@link DocsAccessor} that persists writes through * {@link AttachmentStore} so the imported bytes carry the queryable * `slug` + `type` columns the rest of the docs surface depends on. * * Reads (getDoc / listDocs / searchDocs / exportDoc) are best-effort — * the import path only needs storeDoc to be wired correctly. The other * methods proxy to AttachmentStore where the contract maps, and return * empty arrays / null otherwise to keep the surface honest. * * @task T9791 */ export declare class AttachmentStoreDocsAccessor implements DocsAccessor { private readonly store; /** Absolute project root used as the AttachmentStore cwd. */ private readonly projectRoot; /** * Track shas we have already counted so a second call with identical * content returns the existing id without retrying the `put` transaction. * AttachmentStore.put is itself idempotent via SHA dedup, but the cache * trims the round-trip cost when the importer re-asks for the same blob * within a single run (e.g. on a force-rerun). */ private readonly shaCache; constructor(options: { projectRoot: string; }); /** * Persist a document via AttachmentStore.put, honouring `meta.slug` + * `meta.importType` when present. * * Slug collisions raised by AttachmentStore are unwrapped: when the * incoming SHA matches the row that owns the slug, the put is a no-op. * Otherwise the slug is dropped (best-effort) and the put retried so * the import never blocks on legacy collisions. The audit manifest * surfaces the demotion via `meta.slugDemoted`. * * @param params - StoreDocParams; meta.slug + meta.importType are read. */ storeDoc(params: StoreDocParams): Promise; /** * Look up a doc by sha256 (preferred) or attachment id. * * AttachmentStore.get(sha256) reads the blob from disk; only the bytes * + metadata are surfaced — there is no notion of a `DocKind` on disk * so the kind is approximated from the stored type column. */ getDoc(idOrHash: string): Promise; /** * List every imported doc in the project, exposing each row's SHA so * `runDocsImport` can use it to seed the existing-sha set on the * dedup pass. * * IMPORTANT — only rows that ALREADY carry a `slug` are surfaced. Rows * predate the import path (e.g. attached via `cleo docs add` without * `--slug`) are excluded so the orchestrator's SHA-dedup re-runs them * through {@link storeDoc} and the AttachmentStore applies the slug + type * to the existing row (its `put` method is idempotent on SHA collision). * * Without this filter, the original Saga T9625 validation gate * (`cleo docs fetch sg-cleo-docs-canon-plan`) would silently no-op * forever — bytes are stored but the slug column stays NULL. * * @param filters - Honoured: `kind` (mapped to AttachmentStore type filter * when possible) and `limit`. Other filters fall back to listing all * rows; the importer reads `id` (sha) only. * * @task T9791 */ listDocs(filters?: ListDocsFilters): Promise; /** * Search is best-effort and intentionally returns no hits — the * import path never invokes searchDocs. Callers wanting semantic search * should use `searchAllProjectDocs` from `@cleocode/core/internal` * directly. */ searchDocs(_query: string, _limit?: number): Promise; /** * Export the raw markdown bytes for a stored doc. Only `markdown` * format is supported; other formats fall back to the same string. */ exportDoc(id: string, _format?: DocExportFormat): Promise; /** * AttachmentStore has no per-instance resources to release — the SQLite * connection is the shared singleton owned by @cleocode/core/store. * Implemented as a no-op so the import command can stay symmetric with * the previous DocsAccessor close() call. */ close(): Promise; /** * Compute the deterministic SHA-256 used by every consumer of the * import path. Exposed so callers (e.g. unit tests) can verify the * adapter and the scanner agree on the id format. */ static sha256Of(content: string): string; } /** * Factory wrapper mirroring {@link createDocsAccessor} from * `store/docs-accessor-impl.ts`. * * @param projectRoot - Absolute project root (the directory containing `.cleo/`). * @returns A new {@link AttachmentStoreDocsAccessor}. * * @task T9791 */ export declare function createAttachmentStoreDocsAccessor(projectRoot: string): AttachmentStoreDocsAccessor; //# sourceMappingURL=attachment-store-accessor.d.ts.map