Documentation
¶
Overview ¶
Package memory is abcd's transport-agnostic curated-knowledge substrate at .abcd/memory/ (itd-36 / adr-13). Every verb — ingest, ask, lint, and the bare status render — is a function taking a structured request and returning a structured result plus error; nothing here writes stdout or knows about a CLI, MCP, or prompt surface. Surfaces under internal/surface/* marshal these results for their transport.
WritePages is the ONE mutating entry to the store; it honours the ADR-13 single-writer, atomic-rename crash model (advisory flock + six-step durable write + idempotent sibling reconciliation, no journal). Read-only paths (Ask, Bare, and the read half of Ingest) never heal drift — they report it.
Index ¶
- Constants
- func Dir(repoRoot string) string
- func IsMemoryPageName(filename string) bool
- func LoadRegistry(path string) (map[string]any, error)
- func MergeIngest(registry map[string]any, ev IngestEvent) (map[string]any, error)
- func ParsePageFilename(filename string) (typ, domain, slug string, ok bool)
- func RenderContradictions(pages []PageInfo) string
- func RenderIndex(pages []PageInfo) string
- func SerializeRegistry(registry map[string]any) string
- func SourceClasses(source map[string]any) []string
- func SourceHashes(source map[string]any) []string
- func SourcesIndexPath(repoRoot string) string
- func WithStoreLock(repoRoot string, fn func() error) error
- type AskCitation
- type AskError
- type AskRequest
- type AskResult
- type BareStatus
- type ClassCount
- type DistilledPage
- type Distiller
- type FetchedSource
- type Fetcher
- type FileBackDecision
- type FileBackResult
- type Finding
- type IngestError
- type IngestEvent
- type IngestRequest
- type IngestResult
- type LicenceDetection
- type LintRequest
- type LintResult
- type LintSummary
- type MatchedPage
- type MemorySchemaError
- type PDFExtractor
- type PageInfo
- type PageWrite
- type PlannedWrite
- type RegistryFormatError
- type RegistryMerge
- type StoreLockHeldError
- type Synthesizer
- type UnsafeStorePathError
- type WritePlan
- type WriteReport
- type WriterContractError
Constants ¶
const AskReportHeading = "abcd memory ask"
AskReportHeading heads every ask render. It is the BINARY invocation, not the `/abcd:memory ask` slash command, because this is core: the same bytes are rendered whether the caller came through the plugin surface, the bare CLI, or a later transport, and a heading naming one front door is a falsehood in the other two (iss-44). A reader who ran `abcd memory ask` in a terminal must not be told they invoked a plugin command they may not even have installed.
const AskTopN = 5
AskTopN is the pinned default retrieval depth.
const LintReportHeading = "abcd memory lint"
LintReportHeading heads the run-log report, on the same rule as AskReportHeading: core names the binary invocation, never one front door.
const RelDir = ".abcd/memory"
RelDir is the store's repo-relative directory, slash-separated. It is what a front door names when it has a checkout root and no store path yet — the stray-store walk a resolving front door makes reports a substrate sitting below the checkout root, and it has only the relative shape to look for.
const TokenCountVersion = 1
TokenCountVersion is the pinned deterministic tokenizer version: count of regex \w+ word tokens over the normalised text. The registry stores it so consumers read a stable basis.
Variables ¶
This section is empty.
Functions ¶
func IsMemoryPageName ¶
IsMemoryPageName reports whether filename is an individual memory page — a .md directly in the store that is not a sibling and not a dotfile.
func LoadRegistry ¶
LoadRegistry reads the registry JSON. A missing file yields an empty registry; a present-but-unparseable file raises RegistryFormatError — durable source metadata must fail loudly, never be silently replaced.
func MergeIngest ¶
MergeIngest merges one ingest event into registry and returns a NEW map (the input is never mutated). Pure — no I/O, no lock.
func ParsePageFilename ¶
ParsePageFilename parses <type>_<domain>_<slug>.md, reporting ok=false on a non-conforming name.
func RenderContradictions ¶
RenderContradictions renders contradictions.md deterministically from page facts.
func RenderIndex ¶
RenderIndex renders index.md deterministically (sorted by filename).
func SerializeRegistry ¶
SerializeRegistry deterministically serialises the registry (sorted keys, 2-space indent, trailing newline).
func SourceClasses ¶
SourceClasses returns the page's declared source classes: the UNION of the scalar `class` and the plural `classes` list (deduplicated). This is the same derivation the lint makes (memoryLinter.derivedClasses) — reading only the scalar when both shapes are present shadowed the plural list in the opposite direction to lint, so a both-shapes page rendered under only its scalar class in the generated index and write log while lint counted the union (iss-2608270945468534). The two shapes are mutually exclusive only on the write path (validateSourceBlock); readers see raw on-disk bytes and must read both.
func SourceHashes ¶
SourceHashes returns the page's full contributing source-hash set: source.source_hash (single) and every source.sources[].source_hash (multi).
func SourcesIndexPath ¶
SourcesIndexPath returns .abcd/memory/.sources_index.json.
func WithStoreLock ¶
WithStoreLock holds the exclusive non-blocking advisory lock on .abcd/memory/.lock for fn's extent. The closure form keeps the locked/unlocked split structural — flock does not nest. The lock is fsutil.WithFileLock with no wait: a lock another process holds fails closed at once, as *StoreLockHeldError, and a lock path that is a symlink or not a regular file is *UnsafeStorePathError, judged on the path first and again on the opened descriptor. fn's own error passes through unchanged.
Types ¶
type AskCitation ¶
type AskCitation struct {
SourceClass string `json:"source_class"`
Citation map[string]any `json:"citation"`
SourceHash string `json:"source_hash"`
Licence string `json:"licence"`
IngestedAt string `json:"ingested_at"`
}
AskCitation carries the per-source provenance facts of one matched page. Fields are "" when absent (a legacy / backfilled page), never invented.
type AskError ¶
type AskError struct{ Msg string }
AskError is a pre-write ask failure (unusable file-back payload, no matches to file back against, cited pages without complete provenance). Raised BEFORE any write; the surface reports it and exits 1.
type AskRequest ¶
type AskRequest struct {
RepoRoot string
Question string
TopN int
Synthesizer Synthesizer
FileBackPage map[string]any
DecideFileBack FileBackDecision
Now time.Time
}
AskRequest is the input to Ask.
type AskResult ¶
type AskResult struct {
Question string `json:"question"`
Matches []MatchedPage `json:"matches"`
Answer string `json:"answer"`
FileBack *FileBackResult `json:"file_back"`
}
AskResult is the structured result of one Ask call.
func Ask ¶
func Ask(req AskRequest) (AskResult, error)
Ask runs deterministic retrieval -> synthesis -> optional file-back.
type BareStatus ¶
type BareStatus struct {
StorePresent bool `json:"store_present"`
Pages int `json:"pages"`
ByClass []ClassCount `json:"by_class"`
LastIngest string `json:"last_ingest"`
Contradictions []string `json:"contradictions"`
Drift []string `json:"drift"`
Headroom []string `json:"headroom"`
}
BareStatus is the structured result of Bare.
type ClassCount ¶
ClassCount is one page-count-by-class row.
type DistilledPage ¶
type DistilledPage struct {
Type string
Domain string
Slug string
Body string
TopicHash string
Source map[string]any
Contradicts []string
Recall []string
}
DistilledPage is one validated distiller output page. TopicHash is computed by validateDistilledPage (a supplied one is rejected) and persisted into the written page's frontmatter.
func (DistilledPage) Filename ¶
func (p DistilledPage) Filename() string
Filename returns <type>_<domain>_<slug>.md.
type Distiller ¶
Distiller is the host-delegated seam: (normalisedText, sourceBlock) -> raw page maps. A map omitting "source" gets sourceBlock injected. The core validates every page.
type FetchedSource ¶
FetchedSource is the raw result of fetching a URL (the injectable Fetcher contract). Content-type / size / decode checks are applied uniformly by the ingest path after the fetcher returns.
type Fetcher ¶
type Fetcher func(url string) (FetchedSource, error)
Fetcher fetches a URL; a nil Fetcher uses the bounded default fetch.
type FileBackDecision ¶
type FileBackDecision func(page DistilledPage) bool
FileBackDecision is consulted after validation and before any write; false declines (no partial write).
type FileBackResult ¶
type FileBackResult struct {
Status string `json:"status"`
Pages []string `json:"pages"`
Linked [][2]string `json:"linked"`
Contradictions [][2]string `json:"contradictions"`
WriteReport *WriteReport `json:"write_report"`
}
FileBackResult records what the file-back branch did.
type Finding ¶
type Finding struct {
Code string `json:"code"`
Severity string `json:"severity"`
File string `json:"file"`
Line int `json:"line"`
Message string `json:"message"`
Suggestion string `json:"suggestion"`
}
Finding is a single memory-lint finding.
type IngestError ¶
type IngestError struct{ Msg string }
IngestError is a pre-dispatch ingest failure — raised BEFORE any memory-store write (bad source path, fetch failure, binary source, zero distilled pages, repair collision). The surface reports it fail-closed and exits 1.
func (*IngestError) Error ¶
func (e *IngestError) Error() string
type IngestEvent ¶
type IngestEvent struct {
ContentHash string
Consumer string
SourceClass string
Citation map[string]any
Origin string
Licence string
IngestedAt string
Pages []string
SourceTokenCount int
TokenCountVersion int
}
IngestEvent is one ingest event to merge into the registry. A zero SourceTokenCount (or TokenCountVersion) is treated as "not provided" — the durable value is filled only when currently null, never overwritten.
type IngestRequest ¶
type IngestRequest struct {
RepoRoot string
Source string
Distiller Distiller
KeepOriginal bool
Fetcher Fetcher
PDFExtractor PDFExtractor
Now time.Time
}
IngestRequest is the input to Ingest.
type IngestResult ¶
type IngestResult struct {
Status string `json:"status"`
ContentHash string `json:"content_hash"`
Licence string `json:"licence"`
SourceTokenCount int `json:"source_token_count"`
Pages []string `json:"pages"`
Citation map[string]any `json:"citation"`
KeptOriginal string `json:"kept_original"`
// KeepOriginalError records a --keep-original copy failure that occurred
// AFTER the pages and registry were durably written. The ingest itself
// succeeded; only the best-effort original copy did not. Empty when
// --keep-original was not requested or the copy succeeded.
KeepOriginalError string `json:"keep_original_error,omitempty"`
Linked [][2]string `json:"linked"`
Contradictions [][2]string `json:"contradictions"`
WriteReport *WriteReport `json:"write_report"`
// ScanGap names the coverage the repository asked for and did not get: a
// scanner augmenter it configured (gitleaks, in .abcd/config/gitleaks.json)
// whose tool is not installed. The ingest still writes, redacted by the
// native scanner, and this says so (the 2026-09-25 ruling on
// iss-2608291814575788). Empty when there is no gap.
ScanGap string `json:"scan_gap,omitempty"`
}
IngestResult is the structured result of one Ingest call.
func Ingest ¶
func Ingest(req IngestRequest) (IngestResult, error)
Ingest runs the full ingest flow for one source (a local path or an https URL; a plaintext http source is refused — see isURL).
type LicenceDetection ¶
type LicenceDetection struct {
Licence string // SPDX id, verbatim compound expression, or "unknown"
Restrictive bool
Source string // where it was detected: spdx_header | manifest | licence_file | http_header | none
}
LicenceDetection is the result of detectLicence.
type LintRequest ¶
LintRequest is the input to Lint.
type LintResult ¶
type LintResult struct {
Findings []Finding `json:"findings"`
Summary LintSummary `json:"summary"`
CoverageIndex map[string]any `json:"coverage_index"`
ReportDir string `json:"report_dir"`
GeneratedAt string `json:"generated_at"`
StorePath string `json:"store_path"`
ExitCode int `json:"exit_code"`
}
LintResult is the structured result of Lint.
func Lint ¶
func Lint(req LintRequest) (LintResult, error)
Lint runs the full-store curator health-check and writes one run-log report. Mutates no memory-store state (only the regenerable coverage index + report).
type LintSummary ¶
type LintSummary struct {
Blockers int `json:"blockers"`
Warnings int `json:"warnings"`
Infos int `json:"infos"`
}
LintSummary tallies findings by severity.
type MatchedPage ¶
type MatchedPage struct {
Filename string `json:"filename"`
Score int `json:"score"`
Classes []string `json:"classes"`
Domain string `json:"domain"`
Summary string `json:"summary"`
Body string `json:"body"`
Citations []AskCitation `json:"citations"`
}
MatchedPage is one retrieval hit.
type MemorySchemaError ¶
type MemorySchemaError struct{ Msg string }
MemorySchemaError signals a distilled page / source block that does not conform to the memory schema. No artifact is produced.
func (*MemorySchemaError) Error ¶
func (e *MemorySchemaError) Error() string
type PDFExtractor ¶
PDFExtractor extracts text from PDF bytes; a nil extractor rejects with a clear error (never silently pulls in a parser dependency).
type PageInfo ¶
type PageInfo struct {
Filename string
Classes []string
Domain string
Summary string
Contradicts []string
}
PageInfo carries the per-page facts the derived siblings render from.
type PageWrite ¶
PageWrite is one page to materialise: <type>_<domain>_<slug>.md, full frontmatter (must carry a schema-valid source: block), and the markdown body.
type PlannedWrite ¶
PlannedWrite is one page the write plan materialises (field-compatible with PageWrite).
type RegistryFormatError ¶
type RegistryFormatError struct{ Msg string }
RegistryFormatError signals .sources_index.json exists but is not parseable as a JSON object — durable metadata must fail loudly, never be silently replaced with an empty index.
func (*RegistryFormatError) Error ¶
func (e *RegistryFormatError) Error() string
type RegistryMerge ¶
RegistryMerge recomputes the COMPLETE new registry from the registry as freshly read under the store lock. WritePages calls it with the on-disk registry loaded INSIDE the lock, so the merged result is never derived from a stale pre-lock snapshot. This closes the load-merge-write lost-update: two concurrent ingests that each loaded the registry before either wrote would, if each wrote its own wholesale pre-computed registry, have the last writer silently clobber the other's entry (and orphan its pages). Re-running the merge against the locked read makes each write additive. A nil RegistryMerge leaves the registry untouched (a heal-only pass).
type StoreLockHeldError ¶
type StoreLockHeldError struct{ Path string }
StoreLockHeldError signals a live process holds .abcd/memory/.lock — the writer fails closed (LOCK_NB; the lock file is never deleted, flock releases on process exit).
func (*StoreLockHeldError) Error ¶
func (e *StoreLockHeldError) Error() string
type Synthesizer ¶
type Synthesizer func(question string, matches []MatchedPage) string
Synthesizer turns matches into answer prose; nil uses renderCitedMatches.
type UnsafeStorePathError ¶
type UnsafeStorePathError struct{ Msg string }
UnsafeStorePathError signals a memory-store path (.abcd, .abcd/memory, or the lock leaf) is a symlink or non-regular filesystem object.
func (*UnsafeStorePathError) Error ¶
func (e *UnsafeStorePathError) Error() string
type WritePlan ¶
type WritePlan struct {
Writes []PlannedWrite
RegistryPages map[string][]string
Linked [][2]string
Contradictions [][2]string
}
WritePlan is the output of resolveDistilledPages.
type WriteReport ¶
type WriteReport struct {
CreatedSiblings []string `json:"created_siblings"`
Backfilled []string `json:"backfilled"`
Pruned []string `json:"pruned"`
Reconciled []string `json:"reconciled"`
WrotePages []string `json:"wrote_pages"`
}
WriteReport records what one WritePages call did (all lists sorted).
func WritePages ¶
func WritePages(repoRoot string, writes []PageWrite, merge RegistryMerge, now time.Time) (WriteReport, error)
WritePages is the single mutating entry point for .abcd/memory/. It acquires the advisory store lock, runs the full locked sequence (skeleton ensure -> legacy backfill -> orphan prune -> pre-reconcile -> page writes -> registry load+merge+write when given -> post-reconcile -> log append), and returns a report. An empty writes slice is a valid heal-only pass. merge may be nil (no registry mutation); when non-nil it is invoked with the registry read fresh under the lock and must return the COMPLETE new mapping. A zero now is sampled as time.Now().UTC() AFTER the lock is held.
type WriterContractError ¶
type WriterContractError struct{ Msg string }
WriterContractError is a writer-side contract violation (invalid write request, unreadable user-visible file the writer refuses to overwrite). No artifact is produced.
func (*WriterContractError) Error ¶
func (e *WriterContractError) Error() string