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
65 lines (64 loc) • 2.97 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/.
/**
* Per-proposal-source accept-rate metrics, folded into `akm health --report`
* (0.9.0 CLI overhaul, S3).
*
* Provides the core self-measurement metric for recursive self-improvement:
* if reflect proposals are accepted at 20% and distill proposals at 60%,
* that guides resource allocation to higher-ROI generators.
*
* Previously exposed as the removed `akm history --accept-rate-by-source`
* flag (src/commands/sources/history.ts, F-4 / #385); `health` already reads
* the pending proposal queue for its own `--report` dataset, so this is the
* same read extended to the accepted/rejected archive.
*/
import { resolveStashDir } from "../../core/common.js";
import { isProceduralRejection } from "../proposal/proposal-types.js";
import { listProposals } from "../proposal/repository.js";
/**
* Compute accept-rate-per-source metrics from the proposal store. Defaults to
* the configured default stash when `stashDir` is omitted (same resolution
* `akm health` already uses for the rest of its report).
*/
export function computeAcceptRateBySource(stashDir) {
const stash = stashDir ?? resolveStashDir();
const bySource = new Map();
const countProposals = (statuses, includeArchive) => {
for (const status of statuses) {
const proposals = listProposals(stash, { status, includeArchive });
for (const p of proposals) {
// A stale-target auto-reject (STALE, R20) is procedural, not a
// judgement on the content — counting it would understate the
// source's real accept rate for content the drain will re-propose.
if (status === "rejected" && isProceduralRejection(p))
continue;
const src = p.source || "(unknown)";
const entry = bySource.get(src) ?? { accepted: 0, rejected: 0, pending: 0 };
if (status === "accepted")
entry.accepted++;
else if (status === "rejected")
entry.rejected++;
else
entry.pending++;
bySource.set(src, entry);
}
}
};
countProposals(["pending"], false);
countProposals(["accepted", "rejected"], true);
return Array.from(bySource.entries())
.map(([source, counts]) => {
const decided = counts.accepted + counts.rejected;
return {
source,
total: decided + counts.pending,
accepted: counts.accepted,
rejected: counts.rejected,
pending: counts.pending,
acceptRate: decided > 0 ? counts.accepted / decided : null,
};
})
.sort((a, b) => b.total - a.total); // Most active source first
}