akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
102 lines (101 loc) • 4.23 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
/**
* `index.db` LLM enrichment-cache repository.
*
* Owns the raw SQL for `llm_enrichment_cache` — the body-hash-keyed cache the
* graph-extraction and memory-inference passes use to skip an LLM call when
* a file's body is unchanged.
*/
import { sha256Hex } from "../../runtime.js";
import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
/**
* Look up a cached LLM result for the given asset_ref.
*
* Returns `undefined` when no entry exists OR when the stored body_hash
* doesn't match `currentBodyHash` (body has changed since the result was
* cached). In both cases the caller should invoke the LLM and write a new
* cache entry.
*/
export function getLlmCacheEntry(db, assetRef, currentBodyHash, cacheVariant) {
const row = db
.prepare("SELECT asset_ref, cache_variant, body_hash, result_json, updated_at FROM llm_enrichment_cache WHERE asset_ref = ? AND cache_variant = ?")
.get(assetRef, cacheVariant);
if (!row)
return undefined;
// Hash mismatch → body changed, treat as cache miss.
if (row.body_hash !== currentBodyHash)
return undefined;
return {
assetRef: row.asset_ref,
cacheVariant: row.cache_variant,
bodyHash: row.body_hash,
resultJson: row.result_json,
updatedAt: row.updated_at,
};
}
/**
* Batched variant of {@link getLlmCacheEntry}. Fetches every cache row whose
* `asset_ref` is in `refs` with a single `IN (...)` query (chunked to respect
* SQLITE_MAX_VARIABLE_NUMBER), returning a `Map<assetRef, LlmCacheEntry>`.
*
* Unlike `getLlmCacheEntry`, this does NOT filter by body hash — callers must
* compare `entry.bodyHash` against the current body hash themselves. This lets
* the batch path issue one DB query per chunk instead of one per file.
*/
export function getLlmCacheEntriesByRefs(db, refs, cacheVariant) {
const result = new Map();
if (refs.length === 0)
return result;
for (let i = 0; i < refs.length; i += SQLITE_CHUNK_SIZE) {
const chunk = refs.slice(i, i + SQLITE_CHUNK_SIZE);
const placeholders = chunk.map(() => "?").join(", ");
const rows = db
.prepare(`SELECT asset_ref, cache_variant, body_hash, result_json, updated_at FROM llm_enrichment_cache
WHERE cache_variant = ? AND asset_ref IN (${placeholders})`)
.all(cacheVariant, ...chunk);
for (const row of rows) {
result.set(row.asset_ref, {
assetRef: row.asset_ref,
cacheVariant: row.cache_variant,
bodyHash: row.body_hash,
resultJson: row.result_json,
updatedAt: row.updated_at,
});
}
}
return result;
}
/**
* Insert or update a cached LLM result for the given asset_ref.
*/
export function upsertLlmCacheEntry(db, assetRef, bodyHash, resultJson, cacheVariant) {
db.prepare(`INSERT INTO llm_enrichment_cache (asset_ref, cache_variant, body_hash, result_json, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(asset_ref, cache_variant) DO UPDATE SET
body_hash = excluded.body_hash,
result_json = excluded.result_json,
updated_at = excluded.updated_at`).run(assetRef, cacheVariant, bodyHash, resultJson, Date.now());
}
/**
* Delete LLM cache entries whose asset_ref is no longer present in the
* `entries` table. Should be called during the cleanup phase of each index
* run to prevent the cache from growing unboundedly as assets are removed.
*
* Cache refs are absolute file paths (memory inference).
*/
export function clearStaleCacheEntries(db) {
db.exec(`
DELETE FROM llm_enrichment_cache
WHERE asset_ref NOT IN (SELECT file_path FROM entries)
`);
}
/**
* Compute a stable SHA-256 hex digest of a UTF-8 string. Used as the body_hash
* key in `llm_enrichment_cache`. Routed through the runtime boundary so the
* SQLite layer stays free of direct runtime-specific references.
*/
export function computeBodyHash(body) {
return sha256Hex(body);
}