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

67 lines (66 loc) • 2.91 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/. import { assetTypeOf } from "./eligibility.js"; import { computeSalience } from "./salience.js"; const DAY_MS = 86_400_000; /** An asset is due when last reflected/distilled more than this many days ago (or never). */ export const DEFAULT_DUE_DAYS = 30; /** Default bound on how many assets the selector surfaces per run. */ export const DEFAULT_MAX_PER_RUN = 25; /** Size floor for the cost term, so tiny files don't divide by ~0. */ const SIZE_FLOOR_BYTES = 200; /** * The due gate: never touched, or last touched more than `dueDays` ago. It * doubles as the rotation cooldown. */ function staleness(ref, lastReflectTs, lastDistillTs, dueDays, now) { const lastTouchMs = Math.max(0, Date.parse(lastReflectTs.get(ref) ?? "") || 0, Date.parse(lastDistillTs.get(ref) ?? "") || 0); const neverReflected = lastTouchMs === 0; const staleDays = neverReflected ? Number.POSITIVE_INFINITY : (now - lastTouchMs) / DAY_MS; return { staleDays, neverReflected, due: neverReflected || staleDays > dueDays }; } /** Rank due assets by salience (then staleness, then ref) and keep the top N. */ export function selectProactiveMaintenanceRefs(params) { const now = params.now ?? Date.now(); const dueDays = params.dueDays ?? DEFAULT_DUE_DAYS; const maxPerRun = params.maxPerRun ?? DEFAULT_MAX_PER_RUN; const scored = params.candidates.map((candidate) => { const ref = candidate.ref; const type = assetTypeOf(ref); const retrievalFreq = params.retrievalCounts.get(ref) ?? 0; let sizeBytes = params.sizeBytesOf?.(candidate) ?? 0; if (!sizeBytes || sizeBytes < 0) sizeBytes = SIZE_FLOOR_BYTES; const priority = computeSalience({ ref, type, retrievalFreq, lastUseMs: params.lastUseMs?.get(ref) ?? 0, sizeBytes, now, }).rankScore; return { ref: candidate, type, ...staleness(ref, params.lastReflectTs, params.lastDistillTs, dueDays, now), retrievalFreq, sizeBytes, priority, }; }); const dueScored = scored.filter((s) => s.due); const ranked = dueScored.slice().sort((a, b) => { if (b.priority !== a.priority) return b.priority - a.priority; if (b.staleDays !== a.staleDays) return b.staleDays - a.staleDays; return a.ref.ref < b.ref.ref ? -1 : a.ref.ref > b.ref.ref ? 1 : 0; }); return { selected: ranked.slice(0, Math.max(0, maxPerRun)).map((s) => s.ref), dueTotal: dueScored.length, neverReflected: dueScored.filter((s) => s.neverReflected).length, scored, }; }