/*
RFC-0221 §6: diff a bundle's consumed capabilities against the current
uni.registry, classifying each into a green / yellow / red catch-up tier.
- Do not apply migrators or edit files — classification only.
- RFC-0221: initial capability diff + tiering.
*/
import type { HandoffCapability } from "@warpgogol/werkstatt-engine/schemas";
import type { CapabilityDiffItem, CatchupTier, RegistryView } from "./types.ts";
import type { Migrator } from "../migrators/types.ts";
const TIER_RANK: Record = { green: 0, yellow: 1, red: 2 };
export function worstTier(tiers: CatchupTier[]): CatchupTier {
return tiers.reduce((acc, t) => (TIER_RANK[t] > TIER_RANK[acc] ? t : acc), "green");
}
/** Current entry ids whose intent overlaps the removed capability's intent. */
export function intentMatches(removed: HandoffCapability, registry: RegistryView): string[] {
const wanted = new Set(removed.intent);
if (wanted.size === 0) return [];
const out: string[] = [];
for (const [id, entry] of registry.byId) {
if (id === removed.id) continue;
if (entry.intent.some((i) => wanted.has(i))) out.push(id);
}
return out.sort();
}
/** Any migrator in the chain covers all bumped capabilities (RFC-0479: migrators are generic transforms). */
function hasMigratorFor(_cap: HandoffCapability, migrators: Migrator[]): boolean {
return migrators.length > 0;
}
/**
* RFC-0221 §6 tiers:
* unchanged / additive → green
* renamed-or-bumped + migrator covered → yellow ; otherwise → red
* removed → red (with intent-matched replacement candidates)
*/
export function diffCapabilities(
consumed: HandoffCapability[],
registry: RegistryView,
migrators: Migrator[],
): CapabilityDiffItem[] {
return consumed.map((cap): CapabilityDiffItem => {
const current = registry.byId.get(cap.id);
if (!current) {
const matches = intentMatches(cap, registry);
return {
id: cap.id,
fromVersion: cap.version,
toVersion: null,
change: "removed",
tier: "red",
intentMatches: matches,
note:
matches.length > 0
? `removed — candidate replacements by intent: ${matches.join(", ")}`
: "removed — no intent-matched replacement; manual decision required",
};
}
if (current.version === cap.version) {
return {
id: cap.id,
fromVersion: cap.version,
toVersion: current.version,
change: "unchanged",
tier: "green",
note: "unchanged",
};
}
const covered = hasMigratorFor(cap, migrators);
return {
id: cap.id,
fromVersion: cap.version,
toVersion: current.version,
change: "renamed-or-bumped",
tier: covered ? "yellow" : "red",
note: covered
? `version ${cap.version} → ${current.version}, covered by migrator`
: `version ${cap.version} → ${current.version}, NO migrator — manual`,
};
});
}