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

93 lines (92 loc) • 4.39 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/. /** * What improve may rework (#986): an asset that retrieval returned inside the * usage window, or newly captured material no improve stage has processed. * * Fresh feedback and an explicit `--scope <ref>` are usage evidence of their * own, so the signal-delta and scope lanes need no check. The fallback lanes * (proactive maintenance, high salience) and consolidation pick assets * without such evidence, so they pick only inside this scope. */ import fs from "node:fs"; import { daysToMs } from "../../core/common.js"; import { warn } from "../../core/warn.js"; import { listUsedEntryRefs, USAGE_EVENT_RETENTION_DAYS } from "../../indexer/usage/usage-events.js"; import { listImproveLedgerRows } from "../../storage/repositories/improve-ledger-repository.js"; import { listProposalRefSources } from "../../storage/repositories/proposals-repository.js"; import { PAIR_PASS_LEDGER_SOURCE, readLedgerDb, stripBundle } from "./ledger.js"; /** * Ledger and proposal sources that bring material in. Every other source is an * improve stage reworking an asset (reflect, distill, consolidate, schema repair). */ const CAPTURE_SOURCES = new Set(["extract", "propose", "remember", "import"]); /** * Load the scope from state.db. The window is the usage log's retention: the * log keeps nothing older, and anything shorter would drop assets read less * often than the window. A `.derived` hit counts for its parent memory, whose * facts it carries. `undefined` (every asset eligible, as before #986) only * when state.db cannot be read. */ export function loadRetrievalScope(access, stashDir) { const sinceMs = Date.now() - daysToMs(USAGE_EVENT_RETENTION_DAYS); const used = new Set(); const processed = new Set(); try { readLedgerDb(access, (db) => { for (const entryRef of listUsedEntryRefs(db, new Date(sinceMs).toISOString())) { const conceptId = stripBundle(entryRef); used.add(conceptId); if (conceptId.endsWith(".derived")) used.add(conceptId.slice(0, -".derived".length)); } if (!stashDir) return; // Blocker 1 (second review round): a pair-pass ledger row must NOT mark // an asset "processed" — unlike every other stage, the pair pass judges // material against its NEIGHBOURS, not on its own merits, so its own // attempt is not usage evidence the fallback lanes (proactive, // high-salience) or promotion retries should be starved by. Left in // the ledger for the pair pass's OWN eligibility // (selectInitiators reads content_hash directly, never this scope). // Proposal rows are untouched: a MINTED retire proposal is real // evidence something happened to the asset. for (const row of listImproveLedgerRows(db, stashDir)) { if (row.source === PAIR_PASS_LEDGER_SOURCE) continue; if (!CAPTURE_SOURCES.has(row.source)) processed.add(stripBundle(row.ref)); } for (const row of listProposalRefSources(db, stashDir)) { if (!CAPTURE_SOURCES.has(row.source)) processed.add(stripBundle(row.ref)); } }); } catch (error) { warn(`[improve] usage history unreadable, so every asset stays eligible: ${error instanceof Error ? error.message : String(error)}`); return undefined; } return { used, processed, sinceMs }; } /** * Whether improve may rework `ref` (whose file is `filePath`) under `scope`. An * unprocessed asset whose file cannot be read is left to the disk check that * follows, which reports it as missing. */ export function isInRetrievalScope(scope, ref, filePath) { if (!scope) return true; const conceptId = stripBundle(ref); if (scope.used.has(conceptId)) return true; if (scope.processed.has(conceptId)) return false; try { return filePath === undefined || fs.statSync(filePath).mtimeMs >= scope.sinceMs; } catch { return true; } }