UNPKG

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
// 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); }