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

121 lines (120 loc) • 5.42 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/. /** * Upsert one `asset_outcome` row. Extracted verbatim from the tail of * `updateAssetOutcome` in outcome-loop.ts — same SQL text, same bind order; * the business logic that computes these values (warm-start seed vs. * differential update) stays in outcome-loop.ts and calls this function. * * `review_pressure` is intentionally omitted from both the INSERT column list * and the ON CONFLICT SET clause: the column's DEFAULT 0 seeds fresh rows, and * omitting it from SET leaves an existing row's value untouched on update * (never written going forward). The column itself is dropped in migration 018. */ export function upsertAssetOutcome(db, values) { db.prepare(`INSERT INTO asset_outcome (asset_ref, last_retrieved_at, retrieval_count, expected_retrieval_rate, negative_feedback_count, accepted_change_count, outcome_score, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(asset_ref) DO UPDATE SET last_retrieved_at = excluded.last_retrieved_at, retrieval_count = excluded.retrieval_count, expected_retrieval_rate= excluded.expected_retrieval_rate, negative_feedback_count= excluded.negative_feedback_count, accepted_change_count = excluded.accepted_change_count, outcome_score = excluded.outcome_score, updated_at = excluded.updated_at`).run(values.ref, values.lastRetrievedAt, values.retrievalCount, values.expectedRetrievalRate, values.negativeFeedbackCount, values.acceptedChangeCount, values.outcomeScore, values.updatedAt); } /** * Load the outcome row for one asset, or `undefined` if not yet written. */ export function getAssetOutcome(db, ref) { const row = db .prepare(`SELECT asset_ref, last_retrieved_at, retrieval_count, expected_retrieval_rate, negative_feedback_count, accepted_change_count, outcome_score, updated_at FROM asset_outcome WHERE asset_ref = ?`) .get(ref); return row == null ? undefined : row; } /** * Load ALL asset_outcome rows. Used for the stash-wide outcome-score * normalisation in improve preparation. */ export function getAllAssetOutcomes(db) { return db .prepare(`SELECT asset_ref, last_retrieved_at, retrieval_count, expected_retrieval_rate, negative_feedback_count, accepted_change_count, outcome_score, updated_at FROM asset_outcome ORDER BY asset_ref`) .all(); } /** * Build a Map<ref, outcome_score> for a set of refs in one query. * Used by `salience.ts` to populate `outcomeSalience`. */ export function getOutcomeScoresByRef(db, refs) { const result = new Map(); if (refs.length === 0) return result; const CHUNK = 500; for (let i = 0; i < refs.length; i += CHUNK) { const chunk = refs.slice(i, i + CHUNK); const placeholders = chunk.map(() => "?").join(","); const rows = db .prepare(`SELECT asset_ref, outcome_score FROM asset_outcome WHERE asset_ref IN (${placeholders})`) .all(...chunk); for (const row of rows) { result.set(row.asset_ref, row.outcome_score); } } return result; } /** List every `asset_outcome` ref with its current `missing_since` marker. */ export function listAssetOutcomeMissingState(db) { return db.prepare(`SELECT asset_ref, missing_since FROM asset_outcome`).all(); } /** * Stamp `missing_since = now` for the given refs, but ONLY where it is * currently NULL — a ref already stamped keeps its original timestamp so the * grace window measures from first sighting, not last. Refs with no row at * all are silently skipped (0 rows affected). Returns the number of rows * actually stamped. */ export function stampAssetOutcomeMissing(db, refs, now) { if (refs.length === 0) return 0; const stmt = db.prepare(`UPDATE asset_outcome SET missing_since = ? WHERE asset_ref = ? AND missing_since IS NULL`); let changed = 0; for (const ref of refs) changed += Number(stmt.run(now, ref).changes); return changed; } /** * Clear `missing_since` for refs that resolved again. Returns the number of * rows actually cleared. */ export function clearAssetOutcomeMissing(db, refs) { if (refs.length === 0) return 0; const stmt = db.prepare(`UPDATE asset_outcome SET missing_since = NULL WHERE asset_ref = ? AND missing_since IS NOT NULL`); let changed = 0; for (const ref of refs) changed += Number(stmt.run(ref).changes); return changed; } /** * Delete `asset_outcome` rows whose `missing_since` is older than * `cutoffMs`. A single DELETE — SQLite runs it as one implicit transaction. * Returns the number of rows deleted. */ export function deleteAssetOutcomeMissingBefore(db, cutoffMs) { return Number(db.prepare(`DELETE FROM asset_outcome WHERE missing_since IS NOT NULL AND missing_since < ?`).run(cutoffMs).changes); } /** Count `asset_outcome` rows currently marked missing (the live backlog size). */ export function countAssetOutcomeMissing(db) { const row = db.prepare(`SELECT COUNT(*) AS n FROM asset_outcome WHERE missing_since IS NOT NULL`).get(); return row.n; }