@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
655 lines (603 loc) • 25.8 kB
text/typescript
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, dirname, join, relative, resolve } from "node:path";
import type { RouteSwitchRecord } from "./route-switch.ts";
import { computeDecayedWeight, type RoutingRecord, type RoutingRecordV2 } from "./routing.ts";
import { nowIso } from "./protocol.ts";
import { compareCodeUnitIds } from "./relevance.ts";
export const CANDIDATE_SCHEMA = "kxm.candidate.v1" as const;
export const IMPROVEMENT_REPORT_SCHEMA = "kxm.improvement-report.v2" as const;
export const IMPROVEMENT_REPORT_V1_SCHEMA = "kxm.improvement-report.v1" as const;
export type CandidateKind = "gate" | "skill" | "workflow-step";
export interface CandidateBaselineMetrics {
recurrence: number;
meanCost: number;
meanLatency: number;
verifyPassRate: number;
rework: number;
}
export interface ImprovementCandidate {
schema: typeof CANDIDATE_SCHEMA;
id: string;
kind: CandidateKind;
summary: string;
evidenceRefs: string[];
baselineMetrics: CandidateBaselineMetrics;
declaredOutcome: string;
measure: string;
proposedDiffPath: string;
status: "proposed" | "evaluating" | "review-required" | "promoted" | "rejected";
createdAt: string;
}
/** Why a group that recurs is still not a coded-repeat candidate. */
export type ImprovementExcludedReason = "writes-repository" | "ask-not-repeated";
export interface ImprovementGroupRow {
workflowHash: string;
/** The engine's workflow id when the records carry one. */
workflowId?: string;
stepId: string;
agentRole: string;
promptHash: string;
recurrence: number;
/** Distinct runs among decided records. */
distinctRuns: number;
/** Most distinct decided runs that shared one objective (the same ask). */
askRecurrence: number;
undecidedRecords: number;
meanCost: number;
meanLatency: number;
/** Accepted share of decided records; a superseded retry never passes. */
verifyPassRate: number;
rework: number;
/** Recency-weighted recurrence. Orders rows; never decides candidacy. */
weightedRecurrence: number;
undatedRecords: number;
costSamples: number;
writesRepository: boolean;
evidenceRefs: string[];
isCandidate: boolean;
excludedReason?: ImprovementExcludedReason;
candidateKind?: CandidateKind;
candidateId?: string;
}
export interface ImprovementPromotionEntry extends PromotionReadiness {
candidateId: string;
}
export interface ImprovementReport {
schema: typeof IMPROVEMENT_REPORT_SCHEMA;
createdAt: string;
reviewDecision: "proposed";
recordsCount: number;
groups: ImprovementGroupRow[];
candidates: ImprovementCandidate[];
promotionPolicy: string;
promotion: ImprovementPromotionEntry[];
routeSwitches?: RouteSwitchRecord[] | undefined;
}
export function classifyCandidateKind(stepId: string, agentRole: string): CandidateKind {
const s = stepId.toLowerCase();
const r = agentRole.toLowerCase();
if (s.includes("verify") || s.includes("gate") || s.includes("test") || s.includes("check") || s.includes("lint") || r === "verifier") {
return "gate";
}
if (s.includes("plan") || s.includes("review") || s.includes("repro") || r === "planner" || r === "reviewer" || r === "critic") {
return "skill";
}
return "workflow-step";
}
function candidateSlug(value: string): string {
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "step";
}
function generateCandidateDiff(
kind: CandidateKind,
stepId: string,
agentRole: string,
workflowId?: string | undefined,
): { diff: string; declaredOutcome: string; measure: string; summary: string } {
const safeSlug = candidateSlug(stepId);
if (kind === "gate") {
const diff = [
"diff --git a/.kxm/gates.yaml b/.kxm/gates.yaml",
"--- a/.kxm/gates.yaml",
"+++ b/.kxm/gates.yaml",
"@@ -1,3 +1,7 @@",
" schema: kxm.gate-registry.v1",
" gates:",
`+ ${safeSlug}:`,
`+ kind: command`,
`+ argv: [npm, run, ${safeSlug}]`,
`+ timeoutMs: 3600000`,
"",
].join("\n");
return {
diff,
declaredOutcome: `Deterministic exit-code verification replacing LLM turn for ${stepId}`,
measure: `100% reduction in LLM inference cost and latency for ${stepId}`,
summary: `Promote deterministic check '${stepId}' (${agentRole}) to coded gate`,
};
}
if (kind === "skill") {
const rel = `.kxm/skills/candidates/${safeSlug}/SKILL.md`;
const skillContent = [
"---",
`name: ${safeSlug}`,
`description: Governed repeat skill for ${stepId}`,
"---",
"",
`# ${stepId}`,
"",
`Reusable skill instructions for ${stepId} (${agentRole}).`,
"",
].join("\n");
const diff = [
`diff --git a/${rel} b/${rel}`,
"new file mode 100644",
"--- /dev/null",
`+++ b/${rel}`,
`@@ -0,0 +1,${skillContent.split("\n").length} @@`,
...skillContent.split("\n").map((l) => `+${l}`),
"",
].join("\n");
return {
diff,
declaredOutcome: `Governed reusable skill for ${stepId}`,
measure: `Reduced prompt drift and consistent model guidance for ${stepId}`,
summary: `Consolidate repeated instructions for '${stepId}' (${agentRole}) into a governed skill (consolidation, not a coded step)`,
};
}
// A workflow-step candidate proposes the coded step itself: the agent turn
// becomes a command gate the operator writes and reviews.
const rel = `.kxm/workflows/${workflowId !== undefined ? candidateSlug(workflowId) : safeSlug}.yaml`;
const diff = [
`diff --git a/${rel} b/${rel}`,
`--- a/${rel}`,
`+++ b/${rel}`,
"@@ -1,3 +1,3 @@",
` - id: ${stepId}`,
"- kind: agent",
`- agent: ${agentRole}`,
"+ kind: gate",
`+ gate: ${safeSlug}`,
"diff --git a/.kxm/gates.yaml b/.kxm/gates.yaml",
"--- a/.kxm/gates.yaml",
"+++ b/.kxm/gates.yaml",
"@@ -1,2 +1,6 @@",
" schema: kxm.gate-registry.v1",
" gates:",
`+ ${safeSlug}:`,
"+ kind: command",
`+ argv: [node, scripts/${safeSlug}.mjs]`,
"+ timeoutMs: 600000",
"",
].join("\n");
return {
diff,
declaredOutcome: `Coded gate replaces the model turn for ${stepId}; the operator writes scripts/${safeSlug}.mjs`,
measure: `Model cost and latency for ${stepId} drop to zero while its accepted rate holds`,
summary: `Replace the model turn for '${stepId}' (${agentRole}) with a coded gate step`,
};
}
/** A trimmed, non-empty string, or undefined. */
function nonEmptyText(value: unknown): string | undefined {
if (typeof value !== "string") return undefined;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : undefined;
}
function roundThree(value: number): number {
return Number(value.toFixed(3));
}
interface GroupAccumulator {
key: string;
workflowHash: string;
workflowId?: string;
stepId: string;
agentRole: string;
promptHash: string;
costs: number[];
latencies: number[];
total: number;
decided: number;
passed: number;
undecided: number;
reworkCount: number;
weight: number;
undated: number;
writes: boolean;
decidedRuns: Set<string>;
/** Decided runs per objective digest; one '(unkeyed)' bucket for records without one. */
askRuns: Map<string, Set<string>>;
evidenceRefs: Set<string>;
}
export interface GroupRoutingOptions {
minRecurrence?: number | undefined;
minPassRate?: number | undefined;
/** improvement.telemetryHalfLifeDays; weights order rows and never decide candidacy. */
halfLifeDays?: number | undefined;
/** Epoch milliseconds the recency weights are measured against. */
now?: number | undefined;
}
/**
* Group routing records by (workflowHash, step, role, promptHash) across runs.
*
* Identity: workflowHash is the engine's workflowId when present, then a
* workflow definition digest, then the run; promptHash is the engine's
* askSha256 (stable across runs, independent of the objective), then a v1
* rolePromptSha256. A group is a coded-repeat candidate only when the same
* objective was decided in at least minRecurrence runs, the accepted rate over
* decided records reaches minPassRate, and the step writes no repository.
*/
export function groupRoutingRecords(
records: Array<RoutingRecord | RoutingRecordV2>,
options: GroupRoutingOptions = {},
): ImprovementGroupRow[] {
const minRecurrence = options.minRecurrence ?? 2;
const minPassRate = options.minPassRate ?? 0.75;
const halfLifeDays = options.halfLifeDays ?? 14;
const now = options.now ?? Date.now();
const raws = records.map((record) => record as unknown as Record<string, unknown>);
const runKeys = raws.map((raw, index) => nonEmptyText(raw.runId) ?? nonEmptyText(raw.workflowRunId) ?? `record:${index}`);
const stepIds = raws.map((raw) => nonEmptyText(raw.stepId) ?? nonEmptyText(raw.stageId) ?? "unknown");
const retriesOf = (raw: Record<string, unknown>): number =>
typeof raw.retries === "number" && Number.isFinite(raw.retries) ? raw.retries : 0;
// A retry of the same step in the same run supersedes every earlier attempt.
const maxRetries = new Map<string, number>();
raws.forEach((raw, index) => {
const attemptKey = `${runKeys[index]}\u0000${stepIds[index]}`;
maxRetries.set(attemptKey, Math.max(maxRetries.get(attemptKey) ?? 0, retriesOf(raw)));
});
const map = new Map<string, GroupAccumulator>();
raws.forEach((raw, index) => {
const providerMeta = raw.providerMetadata && typeof raw.providerMetadata === "object" && !Array.isArray(raw.providerMetadata)
? raw.providerMetadata as Record<string, unknown>
: {};
const workflowId = nonEmptyText(providerMeta.workflowId);
const workflowHash = workflowId
?? nonEmptyText(raw.workflowDefinitionSha256)
?? nonEmptyText(providerMeta.workflowDefinitionSha256)
?? nonEmptyText(raw.workflowRunId)
?? nonEmptyText(raw.runId)
?? "standalone";
const stepId = stepIds[index]!;
const agentRole = nonEmptyText(raw.agentRole) ?? "agent";
const promptHash = nonEmptyText(providerMeta.askSha256) ?? nonEmptyText(raw.rolePromptSha256) ?? "none";
const runKey = runKeys[index]!;
const key = `${workflowHash}:${stepId}:${agentRole}:${promptHash}`;
let group = map.get(key);
if (!group) {
group = {
key,
workflowHash,
stepId,
agentRole,
promptHash,
costs: [],
latencies: [],
total: 0,
decided: 0,
passed: 0,
undecided: 0,
reworkCount: 0,
weight: 0,
undated: 0,
writes: false,
decidedRuns: new Set<string>(),
askRuns: new Map<string, Set<string>>(),
evidenceRefs: new Set<string>(),
};
map.set(key, group);
}
if (group.workflowId === undefined && workflowId !== undefined) group.workflowId = workflowId;
group.total += 1;
const cost = raw.costUsd;
if (typeof cost === "number" && !Number.isNaN(cost) && cost >= 0) {
group.costs.push(cost);
}
const latency = raw.latencyMs;
if (typeof latency === "number" && !Number.isNaN(latency) && latency >= 0) {
group.latencies.push(latency);
}
const verifierOutcome = raw.verifierOutcome;
const finalOutcome = raw.finalOutcome;
const decided = (verifierOutcome !== undefined && verifierOutcome !== null)
|| (finalOutcome !== undefined && finalOutcome !== null && finalOutcome !== "pending");
if (decided) {
group.decided += 1;
group.decidedRuns.add(runKey);
const ask = nonEmptyText(providerMeta.objectiveSha256) ?? "(unkeyed)";
let askRuns = group.askRuns.get(ask);
if (!askRuns) {
askRuns = new Set<string>();
group.askRuns.set(ask, askRuns);
}
askRuns.add(runKey);
const superseded = (maxRetries.get(`${runKey}\u0000${stepId}`) ?? 0) > retriesOf(raw);
if (!superseded && (verifierOutcome === "passed" || finalOutcome === "accepted" || finalOutcome === "completed")) {
group.passed += 1;
}
} else {
group.undecided += 1;
}
const retries = raw.retries;
if (typeof retries === "number") {
group.reworkCount += retries;
}
const recordedAt = nonEmptyText(raw.recordedAt);
if (recordedAt === undefined || !Number.isFinite(Date.parse(recordedAt))) group.undated += 1;
group.weight += computeDecayedWeight(recordedAt ?? "", halfLifeDays, now);
if (providerMeta.stepWrites === true) group.writes = true;
const ref = (raw.attemptId as string) || (raw.runId as string) || (raw.workflowRunId as string) || (raw.behavioralSha256 as string);
if (ref && group.evidenceRefs.size < 16) {
group.evidenceRefs.add(ref);
}
});
const keyed: Array<{ key: string; row: ImprovementGroupRow }> = [];
for (const group of map.values()) {
const recurrence = group.total;
const meanCost = group.costs.length > 0
? Number((group.costs.reduce((a, b) => a + b, 0) / group.costs.length).toFixed(4))
: 0;
const meanLatency = group.latencies.length > 0
? Math.round(group.latencies.reduce((a, b) => a + b, 0) / group.latencies.length)
: 0;
const verifyPassRate = group.decided > 0 ? roundThree(group.passed / group.decided) : 0;
const distinctRuns = group.decidedRuns.size;
const askRecurrence = Math.max(0, ...[...group.askRuns.values()].map((runs) => runs.size));
const passes = verifyPassRate >= minPassRate;
const isCandidate = askRecurrence >= minRecurrence && passes && !group.writes;
let excludedReason: ImprovementExcludedReason | undefined;
if (!isCandidate && passes) {
if (askRecurrence >= minRecurrence && group.writes) excludedReason = "writes-repository";
else if (distinctRuns >= minRecurrence && askRecurrence < minRecurrence) excludedReason = "ask-not-repeated";
}
const candidateKind = isCandidate ? classifyCandidateKind(group.stepId, group.agentRole) : undefined;
const safeSlug = group.stepId.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 16) || "step";
const keyHash = createHash("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
const candidateId = isCandidate && candidateKind ? `cand_${candidateKind.replace(/-/g, "_")}_${safeSlug}_${keyHash}` : undefined;
keyed.push({
key: group.key,
row: {
workflowHash: group.workflowHash,
...(group.workflowId !== undefined ? { workflowId: group.workflowId } : {}),
stepId: group.stepId,
agentRole: group.agentRole,
promptHash: group.promptHash,
recurrence,
distinctRuns,
askRecurrence,
undecidedRecords: group.undecided,
meanCost,
meanLatency,
verifyPassRate,
rework: group.reworkCount,
weightedRecurrence: roundThree(group.weight),
undatedRecords: group.undated,
costSamples: group.costs.length,
writesRepository: group.writes,
evidenceRefs: [...group.evidenceRefs],
isCandidate,
...(excludedReason !== undefined ? { excludedReason } : {}),
...(candidateKind !== undefined ? { candidateKind } : {}),
...(candidateId !== undefined ? { candidateId } : {}),
},
});
}
return keyed
.sort((left, right) => {
const a = left.row;
const b = right.row;
if (a.isCandidate !== b.isCandidate) return a.isCandidate ? -1 : 1;
if (b.weightedRecurrence !== a.weightedRecurrence) return b.weightedRecurrence - a.weightedRecurrence;
if (b.recurrence !== a.recurrence) return b.recurrence - a.recurrence;
return compareCodeUnitIds(a.stepId, b.stepId) || compareCodeUnitIds(left.key, right.key);
})
.map((entry) => entry.row);
}
export interface BuildImprovementReportOptions extends GroupRoutingOptions {
candidatesDir?: string | undefined;
projectRoot?: string | undefined;
dryRun?: boolean | undefined;
/** improvement.promotionPolicy; reported as readiness only. */
promotionPolicy?: string | undefined;
autoThreshold?: PromotionAutoThreshold | undefined;
routeSwitches?: readonly RouteSwitchRecord[] | undefined;
}
export function buildImprovementReport(
records: Array<RoutingRecord | RoutingRecordV2>,
options: BuildImprovementReportOptions = {},
): ImprovementReport {
const projectRoot = options.projectRoot ? resolve(options.projectRoot) : process.cwd();
const candidatesDir = options.candidatesDir ? resolve(options.candidatesDir) : join(projectRoot, ".kxm", "candidates");
const promotionPolicy = options.promotionPolicy ?? "manual_pr";
const groups = groupRoutingRecords(records, options);
const candidates: ImprovementCandidate[] = [];
const promotion: ImprovementPromotionEntry[] = [];
for (const group of groups) {
if (!group.isCandidate || !group.candidateKind || !group.candidateId) continue;
const { diff, declaredOutcome, measure, summary } = generateCandidateDiff(
group.candidateKind,
group.stepId,
group.agentRole,
group.workflowId,
);
const diffFileName = `${group.candidateId}.diff`;
const diffFilePath = join(candidatesDir, diffFileName);
const relDiffPath = relative(projectRoot, diffFilePath).replace(/\\/g, "/");
const candidate: ImprovementCandidate = {
schema: CANDIDATE_SCHEMA,
id: group.candidateId,
kind: group.candidateKind,
summary,
evidenceRefs: group.evidenceRefs.length > 0 ? group.evidenceRefs : ["evidence:telemetry"],
baselineMetrics: {
recurrence: group.recurrence,
meanCost: group.meanCost,
meanLatency: group.meanLatency,
verifyPassRate: group.verifyPassRate,
rework: group.rework,
},
declaredOutcome,
measure,
proposedDiffPath: relDiffPath,
status: "proposed",
createdAt: nowIso(),
};
if (!options.dryRun) {
if (!existsSync(candidatesDir)) {
mkdirSync(candidatesDir, { recursive: true });
}
writeFileSync(diffFilePath, diff, "utf8");
const jsonFilePath = join(candidatesDir, `${group.candidateId}.json`);
writeFileSync(jsonFilePath, JSON.stringify(candidate, null, 2) + "\n", "utf8");
}
candidates.push(candidate);
}
const rowsByCandidate = new Map(groups.flatMap((row) => row.candidateId !== undefined ? [[row.candidateId, row] as const] : []));
for (const candidate of candidates) {
const row = rowsByCandidate.get(candidate.id);
promotion.push({
candidateId: candidate.id,
...evaluatePromotionPolicy(candidate, promotionPolicy, {
autoThreshold: options.autoThreshold,
...(row ? { costSamples: row.costSamples, distinctRuns: row.distinctRuns } : {}),
}),
});
}
const routeSwitches = options.routeSwitches?.filter((item) => item.from && item.to) ?? [];
return {
schema: IMPROVEMENT_REPORT_SCHEMA,
createdAt: nowIso(),
reviewDecision: "proposed",
recordsCount: records.length,
groups,
candidates,
promotionPolicy,
promotion,
...(routeSwitches.length > 0 ? { routeSwitches: [...routeSwitches] } : {}),
};
}
export function writeImprovementReport(improvementsDir: string, report: ImprovementReport, dryRun = false): string {
const stamp = report.createdAt.replace(/[:.]/g, "-");
const path = join(improvementsDir, `${stamp}.json`);
if (!dryRun) {
mkdirSync(improvementsDir, { recursive: true });
writeFileSync(path, `${JSON.stringify(report, null, 2)}\n`, { encoding: "utf8" });
}
return path;
}
export function formatImprovementReport(report: ImprovementReport): string {
const lines: string[] = [
`Improvement Report (${report.recordsCount} record(s), ${report.groups.length} group(s), ${report.candidates.length} candidate(s); promotion policy ${report.promotionPolicy})`,
"",
"Workflow Step Role Prompt Records Runs Asks Weighted Cost ($) Latency (ms) Accepted Rework Candidate",
"------------------------------------------------------------------------------------------------------------------------------------------",
];
for (const g of report.groups) {
const wf = g.workflowHash.slice(0, 12).padEnd(14);
const step = g.stepId.slice(0, 11).padEnd(12);
const role = g.agentRole.slice(0, 11).padEnd(12);
const prompt = g.promptHash.replace(/^sha256:/, "").slice(0, 10).padEnd(12);
const rec = String(g.recurrence).padStart(7);
const runs = String(g.distinctRuns).padStart(5);
const asks = String(g.askRecurrence).padStart(5);
const weighted = g.weightedRecurrence.toFixed(3).padStart(9);
const cost = g.meanCost.toFixed(3).padStart(9);
const lat = String(g.meanLatency).padStart(13);
const accepted = `${(g.verifyPassRate * 100).toFixed(0)}%`.padStart(9);
const rework = String(g.rework).padStart(7);
const cand = g.isCandidate
? `yes (${g.candidateKind})`
: g.excludedReason !== undefined ? `no (${g.excludedReason})` : "no";
lines.push(`${wf} ${step} ${role} ${prompt} ${rec} ${runs} ${asks} ${weighted} ${cost} ${lat} ${accepted} ${rework} ${cand}`);
}
if (report.routeSwitches && report.routeSwitches.length > 0) {
lines.push("");
lines.push(`Route switches (${report.routeSwitches.length}):`);
for (const item of report.routeSwitches) {
const effort = item.effort ? ` effort ${item.effort}` : "";
lines.push(` - ${item.from} -> ${item.to} (${item.reason}${effort}) on ${item.stepId}`);
}
}
if (report.candidates.length > 0) {
const readiness = new Map(report.promotion.map((entry) => [entry.candidateId, entry]));
lines.push("");
lines.push("Emitted Coded-Repeat Candidates:");
for (const c of report.candidates) {
lines.push(` - ${c.id} [${c.kind}]: ${c.summary}`);
lines.push(` Outcome: ${c.declaredOutcome}`);
lines.push(` Measure: ${c.measure}`);
lines.push(` Proposed diff: ${c.proposedDiffPath}`);
const entry = readiness.get(c.id);
if (entry) {
lines.push(` Promotion (${entry.policy}): ${entry.readyForReview ? "ready for review" : "not ready for review"}; ${entry.reason}`);
}
}
lines.push("");
lines.push("Candidates are proposals: readiness never authorizes, and activation is a reviewed Git change.");
}
return lines.join("\n");
}
/** Review readiness of one candidate under improvement.promotionPolicy. */
export interface PromotionReadiness {
policy: string;
readyForReview: boolean;
reason: string;
}
export interface PromotionAutoThreshold {
minRuns?: number | undefined;
minPassRate?: number | undefined;
minCostSavings?: number | undefined;
}
/**
* Report whether a candidate is ready for operator review (Decision Q11).
*
* No policy authorizes anything. AGENTS.md:141 forbids auto-promoting skills
* or gates from telemetry, and ADR-001 (docs/contracts/architecture.md:177-180)
* rejects automatic learned-policy activation: activation is a reviewed Git
* change. Every policy therefore ends at an operator PR:
* - manual_pr: always ready; the operator reviews the proposed diff.
* - critic_quorum: ready once two distinct critic receipts are cited.
* - auto_threshold: ready once the candidate's distinct runs and accepted rate
* reach the thresholds and, when minCostSavings is set, recorded cost samples
* show at least that mean cost; a group without cost samples is never ready.
* Any other policy is not ready.
*/
export function evaluatePromotionPolicy(
candidate: ImprovementCandidate,
policy: string = "manual_pr",
options: {
criticReceipts?: string[] | undefined;
autoThreshold?: PromotionAutoThreshold | undefined;
costSamples?: number | undefined;
distinctRuns?: number | undefined;
} = {},
): PromotionReadiness {
if (policy === "manual_pr") {
return { policy, readyForReview: true, reason: `operator PR applying ${candidate.proposedDiffPath} required` };
}
if (policy === "critic_quorum") {
const receipts = new Set((options.criticReceipts ?? []).map((receipt) => receipt.trim()).filter((receipt) => receipt.length > 0));
return receipts.size >= 2
? { policy, readyForReview: true, reason: "two critic receipts cited; operator PR still required" }
: { policy, readyForReview: false, reason: `awaiting 2 distinct critic receipts (have ${receipts.size})` };
}
if (policy === "auto_threshold") {
const minRuns = options.autoThreshold?.minRuns ?? 10;
const minPassRate = options.autoThreshold?.minPassRate ?? 0.95;
const minCostSavings = options.autoThreshold?.minCostSavings;
const runs = options.distinctRuns ?? candidate.baselineMetrics.recurrence;
const passRate = candidate.baselineMetrics.verifyPassRate;
const costSamples = options.costSamples ?? 0;
const meanCost = candidate.baselineMetrics.meanCost;
const costMet = minCostSavings === undefined || (costSamples > 0 && meanCost >= minCostSavings);
const ready = runs >= minRuns && passRate >= minPassRate && costMet;
const detail = [
`runs=${runs}/${minRuns}`,
`passRate=${passRate}/${minPassRate}`,
...(minCostSavings === undefined ? [] : [`meanCost=${meanCost}/${minCostSavings} over ${costSamples} cost sample(s)`]),
].join(", ");
return ready
? { policy, readyForReview: true, reason: `auto-threshold met (${detail}); operator PR still required` }
: { policy, readyForReview: false, reason: `auto-threshold not met (${detail})` };
}
return { policy, readyForReview: false, reason: "unknown promotion policy" };
}