import { type InstalledPlugin, type Scope } from '../types.js'; /** * Thin memory-document resolver for the document substrate. Precedence is the * OUTER loop, not a global scan: scopes/sources are tried NEAREST-FIRST * (node > project stack nearest > ... > profile > user > builtin), and WITHIN each * source, in order: (1) exact substrate-identity match (`doc.name === query` * — the explicit frontmatter `name`, or its path-derived fallback), (2) direct * `memory/.md` physical-path lookup (plus the bare-dir/bare-plugin-name * → `INDEX.md` convenience neither identity nor a literal path expresses), * (3) bare leaf-name fallback (final path segment only). The first hit wins, * so a nearer source's match of ANY kind always beats a farther source's * match of any kind — a farther-scope identity can never shadow a * nearer-scope physical file. It returns the raw parsed frontmatter + body; * it does NOT interpret the schema, kind, gate, or rungs — that is the * schema/gate layer's job (callers filter by `frontmatter.kind`). Project * resolution is a nearest-first stack of every ancestor `.crouter/` (widened by * a selected profile's `projects` manifest entries — see `findProjectScopeRoots` * in scope.ts), profile is the selected profile's own singleton `memory/` store * (from `CRTR_PROFILE_ID`), and user/builtin remain singleton scopes. */ export interface MemoryDoc { /** Resolver identity: the doc's explicit frontmatter `name` when present, * otherwise its normalized path under the scope's memory/ root — e.g. * memory/taste/foo.md → "taste/foo". */ name: string; scope: MemoryScope; /** Absolute path to the resolved .md file. */ path: string; /** Raw, uncoerced frontmatter record (null when the doc has no frontmatter). */ frontmatter: Record | null; /** Document body, with the frontmatter block stripped. */ body: string; /** Set to the owning plugin's name when this doc is mounted from an installed * plugin (under `/`), undefined for a native scope doc. Plugin * docs surface within a source scope but live under the plugins dir and are * managed by `crtr pkg`, not writable/deletable as scope docs — consumers * that mutate the store gate on this. */ plugin?: string; } /** The memory-only scope union: the global `Scope` (`user|project|builtin`) * plus `profile` and `node`. Confined to the memory resolver/list/read/write/ * render paths — deliberately NOT folded into the global `Scope` union, because * that would leak these into config, plugin, marketplace, view, and doctor/ * sys-config surfaces the spec does not extend. `node` is the this-node store * (`nodes//context/memory/`), available only inside a running node * (`CRTR_NODE_ID`), and it is the NEAREST scope — a node doc overrides a * same-named doc at any wider scope. */ export type MemoryScope = Scope | 'profile' | 'node'; export interface MemoryResolutionOpts { /** Restrict resolution to a single scope. Conflicts with a scope prefix on * the identifier (e.g. `user/foo` with scope=project) throw. */ scope?: MemoryScope; /** Restrict resolution to docs whose resolved kind matches. Threaded INTO * precedence, not applied as a post-resolution filter: within each source, * a doc whose kind doesn't match is skipped (not returned), so a nearer * source's wrong-kind doc can no longer shadow a farther source's * matching-kind doc into a false not_found — resolution simply continues * to the next source. See `effectiveDocKind` for the kind coercion. */ kind?: string; } /** Canonical, unambiguous identifier for a memory document: `/`. */ export declare function memoryDocId(doc: MemoryDoc): string; /** All native memory docs for a scope. Project scope is a nearest-first stack of * every ancestor `.crouter/memory/` (widened by a selected profile's project * stack); profile is the selected profile's own singleton store, resolved * through `loadProfileManifest`; user and builtin are singleton stores. */ export declare function listMemoryDocs(scope: MemoryScope, quiet?: boolean): MemoryDoc[]; /** All of one plugin's substrate docs, mounted under the virtual `/` * namespace. Walks `pluginMemoryDir(plugin)` recursively for *.md, deriving each * doc's name exactly as `listMemoryDocs` does (path-relative, no extension, * slash-separated) then prefixing the plugin name. Builtin has no plugins. */ export declare function listPluginMemoryDocs(plugin: InstalledPlugin, scope: MemoryScope, quiet?: boolean): MemoryDoc[]; /** All memory docs across the resolved sources, in precedence order: each * ancestor project `.crouter/` from nearest to farthest, then the selected * profile's memory (if any), then user, then builtin. Within each source, * native docs are emitted before enabled-plugin docs, so native wins on the * caller's first-wins dedup. */ export declare function listAllMemoryDocs(scope?: MemoryScope, quiet?: boolean): MemoryDoc[]; /** * Resolve a memory document name to a single memory document. * * Accepted identifier forms: * — bare name; resolved by scope precedence project>user>builtin * / — pinned to one scope (user|project) * `` may carry topical subdirs (`taste/foo`); a bare leaf (`foo`) falls * back to a last-segment match within the resolved scopes. * * Precedence is the outer loop: sources are visited nearest-first, and the * FIRST source with any hit wins outright — a farther source is never even * consulted once a nearer one matches. Within each source, in order: (1) * exact substrate-identity match (`doc.name === query` — the explicit * frontmatter `name`, else its normalized path-derived fallback, the SAME * identity `listAllMemoryDocs` reports); (2) direct `memory/.md` * physical-path lookup (the bare-dir → INDEX.md and bare-plugin-name → plugin * root INDEX.md conveniences neither identity nor a literal path expresses); * (3) bare leaf-name fallback (final path segment), ambiguous only when two * DIFFERENT identities in the SAME source share a leaf. * * `opts.kind`, when given, is a resolution CONSTRAINT, not a post-filter: at * each of the three steps above, a candidate whose `effectiveDocKind` doesn't * match is skipped rather than accepted, so a nearer source's wrong-kind doc * no longer shadows a farther source's matching-kind doc — resolution simply * keeps walking outward. A source only counts as having "an answer" once a * kind-matching candidate is found. */ export declare function resolveMemoryDoc(rawName: string, opts?: MemoryResolutionOpts): MemoryDoc;