Documentation
¶
Overview ¶
Package evidence — cache.go amortises the per-call corpus indexing across BuildPack invocations. The H3 ranking algorithm scans every node + edge + hunk blob to build the BM25 corpus; on a 240K-node / 9K-hunk graph that's ~4s wall time per query, dominated by ~9K GetBlob calls (each gzip-decompressing the patch text on the way out).
Cache holds:
- the indexed `hunkCorpus` (per-SHA / per-hunk maps),
- the bm25.Scorer with the corpus pre-indexed (the expensive step that materialises term-frequency stats across all docs),
keyed by (manifest.BuildTimestamp + manifest.SrcCommit). Any rebuild drifts the key and the next call rebuilds the index lazily.
Concurrency model: sync.RWMutex. The hot path is read-only — every concurrent BuildPack takes the read lock for a key check and reuses the cached corpus + scorer. Cache miss promotes one goroutine to the write lock; the rest queue at the read-side and benefit from the rebuilt state. Double-check-locked: the writer re-validates the key after acquiring the write lock so two concurrent invalidations don't double-build.
Package evidence implements the H3 EvidencePack assembler — given a free-form intent string and an optional seed qname, ranks the schema-1.8 Hunk corpus by BM25 over a (commit subject || patch text || modifies qnames) virtual document, groups the top-K hunks by their parent commit, decorates each with its `modifies` neighbours, and returns the Pack JSON the Coding Agent can fold into a few-shot prompt frame.
Algorithm (mirrors docs/design/hunk-graph.md §5.2):
- Build per-hunk virtual document = subject + decompressed patch + modifies-qnames.
- BM25-score against intent → top 50 hunks.
- If seed_qname set: filter to hunks reaching seed via the modifies edge directly OR via one hop on the G3 call graph (calls/invokes). Take top-K survivors.
- Group by parent commit; attach all hunks the commit contains (the adjacent edge means the Agent reads the full change).
- Decorate each hunk with its `modifies` neighbours' metadata (qname, type, file_path, start/end lines — no body bytes).
- Order commits by author timestamp DESC; stop emitting once cumulative patch text exceeds budget_tokens.
§11.3 retrieval boundary: only EXTRACTED-confidence Hunks and Commits enter the corpus. AMBIGUOUS rows (the unreachable-history recovery track) are filtered out at scan time so the LLM never sees code paths that were rolled back.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache is the BuildPack accelerator. Construct one per persistent process (mcp server, ckg serve) and reuse across calls — the cache invalidates itself when the underlying graph.db rebuilds.
func NewCache ¶
func NewCache() *Cache
NewCache returns a fresh, empty Cache. Safe to share across goroutines — every method takes the lock internally.
func (*Cache) BuildPack ¶
BuildPack runs the H3 assembly with the cached index when possible. Falls back to the uncached path on the first call (or any rebuild of the underlying graph.db).
Identical contract to the package-level BuildPack — same Options, same Pack JSON, same §11.3 retrieval boundary semantics — only the performance changes.
func (*Cache) CachedKey ¶
CachedKey returns the manifest signature the cache currently holds. Empty string means the cache is uninitialised. Useful for testing and for telemetry (logging "cache hit, key=…").
func (*Cache) Invalidate ¶
func (c *Cache) Invalidate()
Invalidate clears the cached state, forcing the next BuildPack to rebuild from scratch. Used by tests; production code shouldn't need this — manifest-based invalidation handles the common rebuild case.
Resets the per-Cache manifest mini-cache as well so a test can drive the (setKey → re-BuildPack) flow without waiting for the 1-second TTL to expire. Production callers that mutate the underlying store directly should also call Invalidate() to mirror the new state immediately rather than risking a one-second window of stale corpus.
func (*Cache) TicketIndex ¶
TicketIndex returns ticket statistics aggregated from the cached hunk corpus. Rebuilds the corpus on the first call (or after a graph.db rebuild); subsequent calls are pure in-memory walks over already-indexed data.
limit ≤ 0 returns the full sorted list; otherwise the top N rows by HunkCount descending. SampleCommits is capped at 3 per ticket (most-recent first by author timestamp).
§11.3 boundary: only EXTRACTED Hunks/Commits feed indexCorpus, so AMBIGUOUS unreachable-history tickets — even if a force-pushed commit's subject mentioned a ticket — never surface here. The Recovery panel is the dedicated surface for that data.
type CommitInfo ¶
type CommitInfo struct {
SHA string `json:"sha"`
Subject string `json:"subject"`
AuthorTime int64 `json:"author_time"`
// IssueIDs is reserved for the H4 issue-id extraction stage; left
// empty by H3 so the schema is stable now and the Agent doesn't
// crash on a missing field once H4 lands.
IssueIDs []string `json:"issue_ids,omitempty"`
// TopFiles is populated by TicketIndex's pickSampleCommits — the
// top-3 most-frequently-touched directory paths across the
// commit's hunks (e.g. ["crypto/secp256k1", "consensus", "core"]).
// Lets the viewer's TicketIndex panel hint at a ticket's reach
// before the user pays the cost of fetching the full EvidencePack.
// Left empty (omitempty) by EvidencePack's BuildPack flow because
// hunk file_path is already surfaced per-HunkRow there.
TopFiles []string `json:"top_files,omitempty"`
}
type Hit ¶
type Hit struct {
Commit CommitInfo `json:"commit"`
Hunks []HunkRow `json:"hunks"`
}
type ModifiesInfo ¶
type Options ¶
type Options struct {
Intent string
SeedQname string
IssueID string
K int
BudgetTokens int
// Offset skips the first N commits in the recency-sorted result —
// the "Load more" page boundary. Used by viewer/agent flows that
// already consumed page 0 and want to keep walking back through a
// large ticket without raising BudgetTokens (which would also
// inflate the per-call payload). Stable across calls because
// commit recency tie-breaks on SHA in groupByCommit's sort.
Offset int
// Mode picks the term-match strategy applied on top of the BM25
// ranking:
// - "" or "or" (default): keep BM25's any-term-match behaviour.
// A high-scoring hit only needs to share one query token with
// the candidate doc; useful for fuzzy semantic search.
// - "and": after BM25 ranking, drop hits whose virtual document
// doesn't contain *every* query token. Useful for precise
// agent queries like "all hunks mentioning RetryPolicy AND
// Backoff" where OR's looser fuzzy match would surface noise.
//
// AND is purely a post-filter: the BM25 ranking still computes
// across the full corpus, so the relative scoring among AND-mode
// survivors matches what they would have been in OR mode.
Mode string
}
Options controls one BuildPack invocation. The defaults below match the design §5.1 schema. Zero / negative values fall back to the defaults — callers can pass an empty struct for "default behaviour over the default intent" (rare but legal).
IssueID, when set, restricts the candidate hunks to those whose parent commit's H4-extracted issue set contains the requested ID. Combines additively with Intent / SeedQname:
- IssueID alone: returns the ticket's hunks ordered by commit recency.
- IssueID + Intent: BM25-rank inside the ticket subset.
- IssueID + SeedQname: filter by ticket AND by the seed neighbourhood.
type Pack ¶
Pack is the EvidencePack JSON shape (§1.5). Stable field names so the Agent prompt format is portable across CKG versions.
func BuildPack ¶
func BuildPack(store persist.StoreReader, opt Options) (*Pack, error)
BuildPack runs the full H3 assembly. Returns an empty Pack (with hits=[]) when no hunks match — never nil — so the Agent's JSON parsers don't have to guard against null. Errors propagate from the underlying store or from gzip decompression.
This is the uncached entrypoint — every call rebuilds the BM25 corpus from scratch. Long-lived processes (ckg serve, mcp Run) should hold a *Cache instance instead and call Cache.BuildPack so the indexing cost amortises across queries. See cache.go.
type TicketRow ¶
type TicketRow struct {
IssueID string `json:"issue_id"`
HunkCount int `json:"hunk_count"`
CommitCount int `json:"commit_count"`
SampleCommits []CommitInfo `json:"sample_commits,omitempty"`
}
TicketRow is one entry in the TicketIndex output: an issue/PR ID the H4 extractor recognised + how many hunks / commits cite it + up to 3 most-recent commit subjects for context. The Coding Agent or a human reviewer uses this to navigate "what tickets does this codebase track most heavily" without round-tripping to GitHub.