index

package
v0.16.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package index wraps chromem-go's persistent vector store with the SDD chunk and citation model. It is the side-effecting backing store for the search index — no domain reasoning happens here. The IndexHandler (internal/handlers) orchestrates: load entry → split → embed → upsert via this package.

Storage layout (under .sdd/index/):

chromem/        # chromem-go gob persistence
manifest.json   # entry tracking sidecar (see manifest.go)

Machine-global store resolution and the locking seam. The vector index lives outside every working tree in one content-addressed store per (repo-key, embedder-fingerprint) under the cache root — shared by a repo's checkout, its worktrees, and its connected-repo cache, so the same content is embedded once per machine (d-cpt-6cq / d-tac-nhx). All index locking is centralized here: no other package takes index locks.

Index

Constants

View Source
const (
	MetaEntryID              = "entry_id"
	MetaChunkPath            = "chunk_path"
	MetaDepth                = "depth"
	MetaContentHash          = "content_hash"
	MetaModelFingerprint     = "model_fingerprint"
	MetaIsSummary            = "is_summary"
	MetaIsAttachment         = "is_attachment"
	MetaSourceAttachmentPath = "source_attachment_path"
)

MetadataKey constants name the per-row chromem metadata fields. Kept as constants so the indexer, finder, and tests share one source of truth.

View Source
const CollectionName = "sdd-graph"

CollectionName is the chromem-go collection used for SDD chunks.

Variables

This section is empty.

Functions

func AttachmentChunkID

func AttachmentChunkID(entryID, attachmentPath string, n int) string

AttachmentChunkID is the deterministic chunk ID for the n-th chunk of the named attachment under entryID. attachmentPath is the entry-relative attachment path.

func BodyChunkID

func BodyChunkID(entryID string, n int) string

BodyChunkID is the deterministic chunk ID for the n-th body chunk of an entry. n is positional in the chunker's emit order.

func ChunkIDPrefix

func ChunkIDPrefix(entryID string) string

ChunkIDPrefix returns the per-entry prefix all this entry's chunk IDs share — used by Index.DeleteEntry's whereDocument filter (chromem-go supports id-prefix matching at delete-time via the metadata).

func HashContent

func HashContent(text string) string

HashContent returns a stable hex sha-256 of the chunk's embedded text. Used as the per-row content_hash metadata so future builds can detect when a row's source content changed.

func ManifestFingerprint added in v0.15.0

func ManifestFingerprint(m *Manifest) string

ManifestFingerprint returns the dominant embedder fingerprint recorded in a manifest — the fingerprint a legacy in-tree index was built under, used to pick its target store during migration. Empty when the manifest holds no fingerprints.

func MigrateDir added in v0.15.0

func MigrateDir(legacyDir, cacheRoot, repoKey string) (target string, migrated bool, err error)

MigrateDir moves a legacy index directory (an in-tree .sdd/index or a clone-cache .index) into the machine-global store, keyed by the fingerprint its own manifest records. Move-if-absent: when the target store already exists the legacy directory is left untouched and skipped — never clobbered, never merged. Returns the target dir and what happened.

func RepoKey added in v0.15.0

func RepoKey(repoID, repoRoot string) string

RepoKey returns the identity a repository's index store is keyed by: the declared repo_id when the repo has one, else a hash of the absolute repo root under the "local" namespace. A moved identity-less repo therefore re-embeds — accepted: `sdd init` migration covers the common case, and minting a synthetic ID would invent a second identity concept.

func StoreDir added in v0.15.0

func StoreDir(cacheRoot, repoKey, fingerprint string) string

StoreDir resolves the machine-global store directory for one (repo-key, embedder-fingerprint) pair: <cacheRoot>/index/<repo-key>/<fp-hash>. The fingerprint is hashed because it is a free-form embedder string, not a path; keying by fingerprint means a changed embedder starts a fresh store instead of drifting inside an existing one.

func SummaryChunkID

func SummaryChunkID(entryID string) string

SummaryChunkID is the deterministic chunk ID for an entry's summary chunk. Re-indexing the same entry produces the same ID — the indexer upserts via id rather than delete-and-add for the summary.

Types

type EntryState

type EntryState struct {
	// Hash is the content hash of the source material the index was
	// built from (entry body + each attachment). Changes invalidate the
	// entry's chunks and trigger re-indexing on lazy-fill.
	Hash string `json:"hash"`
	// Fingerprint is the embedder fingerprint that produced this entry's
	// chunks. Compared against the configured embedder at search-time
	// (lazy re-embed) and lint-time (drift count).
	Fingerprint string `json:"fingerprint"`
	// ChunkIDs are the IDs this entry produced last time it was indexed.
	// Used to delete stale rows before re-adding under new content/
	// fingerprint.
	ChunkIDs []string `json:"chunk_ids"`
	// IndexedAt is the time the entry was last indexed. Informational
	// only — not used for any lifecycle decision.
	IndexedAt time.Time `json:"indexed_at"`
}

EntryState tracks indexing status for a single entry.

type Hit

type Hit struct {
	EntryID              string
	ChunkID              string
	Score                float32
	Text                 string
	Body                 string
	Breadcrumb           []string
	Depth                int
	IsSummary            bool
	IsAttachment         bool
	SourceAttachmentPath string
	ContentHash          string
	ModelFingerprint     string
}

Hit is one query result with the metadata needed to render a citation and to decide whether to re-embed (for fingerprint drift).

type Index

type Index struct {
	// contains filtered or unexported fields
}

Index wraps a chromem-go persistent DB and a single collection.

func Open

func Open(indexDir string) (*Index, error)

Open opens or creates the persistent index under indexDir. The chromem store lives in indexDir/chromem; the manifest sidecar at indexDir/manifest.json (managed by callers via LoadManifest/Save).

On a fresh directory, both subdirs are created. The collection is named CollectionName and is auto-created on first open.

The load phase holds the store's shared lock so a reader never decodes half-written documents from a concurrent write session; the lock is released before Open returns — queries run on the in-memory copy, and mutation goes through WriteSession's exclusive lock.

func OpenInMemory

func OpenInMemory() *Index

OpenInMemory returns a non-persistent index. Used by tests.

func (*Index) Count

func (i *Index) Count() int

Count returns the total number of chunk rows in the index.

func (*Index) DeleteEntry

func (i *Index) DeleteEntry(ctx context.Context, chunkIDs []string) error

DeleteEntry removes all rows whose chunk IDs are listed. Called when an entry is removed from the graph or when reconciling the manifest.

func (*Index) Path

func (i *Index) Path() string

Path returns the on-disk root of the index. Empty for in-memory indexes.

func (*Index) Query

func (i *Index) Query(ctx context.Context, embedding []float32, nResults int) ([]Hit, error)

Query returns the top-N matches for the given query embedding. The nResults parameter is clamped to the collection's current count to avoid chromem-go's "n_results larger than collection" behavior.

func (*Index) UpsertEntry

func (i *Index) UpsertEntry(ctx context.Context, entryID string, oldChunkIDs []string, rows []Row) error

UpsertEntry replaces all rows for entryID with the given rows in a single transaction-shaped pass. Old chunk IDs (from the manifest) are deleted first; the new rows are added afterward. Caller is responsible for updating the manifest with the new chunk IDs.

Pre-conditions:

  • Each row's Embedding is populated (the index does not embed).
  • row.EntryID == entryID (validated; mismatch is a programmer error).
  • row.ChunkID is unique within rows.

func (*Index) WriteSession added in v0.15.0

func (i *Index) WriteSession(ctx context.Context, fn func() error) error

WriteSession runs fn while holding the store's exclusive lock — the one write boundary for every index mutation (build, lazy-fill). Manifest reads belonging to the write must happen inside fn so concurrent writers cannot clobber each other's manifest state. In-memory indexes (tests) run fn without locking. Blocks until the lock is available or ctx ends.

type Manifest

type Manifest struct {
	// Version is bumped when the manifest schema changes. Currently 1.
	Version int                   `json:"version"`
	Entries map[string]EntryState `json:"entries"`
}

Manifest is the sidecar tracking which entries are indexed, their content hash (for lazy-fill staleness detection), the fingerprint of the embedder used (for drift counting), and the chunk IDs each entry produced (so re-indexing can delete the old set before adding the new).

Lives next to the chromem-go DB at .sdd/index/manifest.json.

func LoadManifest

func LoadManifest(indexDir string) (*Manifest, error)

LoadManifest reads .sdd/index/manifest.json or returns an empty manifest when the file does not exist.

func (*Manifest) EntryIDsSorted

func (m *Manifest) EntryIDsSorted() []string

EntryIDsSorted returns the manifest's entry IDs in lexicographic order. Useful for deterministic iteration in tests and lint output.

func (*Manifest) MismatchCount

func (m *Manifest) MismatchCount(current string) int

MismatchCount returns the number of entries whose recorded fingerprint differs from current. Empty fingerprint counts as a mismatch (the entry has never been embedded under the active embedder). Used by sdd lint.

func (*Manifest) PendingCount added in v0.9.0

func (m *Manifest) PendingCount(entryIDs []string, fingerprint string) int

PendingCount returns how many of entryIDs are absent from the manifest or recorded under a different embedder fingerprint — the entries a build or lazy-fill would (re-)embed. Entry bodies are immutable, so a stored content hash never goes stale on its own; presence and fingerprint are the reconciliation axes that decide whether there is work worth showing a transient progress view for.

func (*Manifest) Save

func (m *Manifest) Save(indexDir string) error

Save writes the manifest atomically (write to temp then rename) so a crash in the middle of indexing doesn't leave a partial JSON file.

type Row

type Row struct {
	EntryID              string
	ChunkID              string
	Text                 string // embedded text (with Entry/Breadcrumb preamble)
	Body                 string // citation snippet source (without preamble)
	Breadcrumb           []string
	Depth                int
	IsSummary            bool
	IsAttachment         bool
	SourceAttachmentPath string
	ContentHash          string
	ModelFingerprint     string
	Embedding            []float32
}

Row is one chunk-as-it-lives-in-the-index. The Index ingests Rows the IndexHandler has already populated from the splitter + embedder. Embedding must be non-empty; the index does not call out to embedders.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL