import type { SessionEntry } from "@earendil-works/pi-coding-agent"; import type { ApplyLedger, OwnershipState } from "./types.ts"; export const APPLY_ENTRY_TYPE = "pi-tree-labeler.apply"; function isLedger(value: unknown): value is ApplyLedger { if (!value || typeof value !== "object") return false; const record = value as Partial; if (record.version !== 1 || typeof record.runId !== "string" || !Array.isArray(record.changes)) return false; return record.changes.every((change) => Boolean(change) && typeof change.targetId === "string" && typeof change.label === "string" && typeof change.labelEntryId === "string" && (change.previousLabel === undefined || typeof change.previousLabel === "string")); } export function reconstructOwnership(entries: SessionEntry[]): OwnershipState { const latestLabelEntryByTarget = new Map(); const seenLabelsById = new Map(); const ownedLabelEntryIds = new Set(); for (const entry of entries) { if (entry.type === "label") { const label = { targetId: entry.targetId, label: entry.label }; seenLabelsById.set(entry.id, label); latestLabelEntryByTarget.set(entry.targetId, { id: entry.id, label: entry.label }); continue; } if (entry.type === "custom" && entry.customType === APPLY_ENTRY_TYPE && isLedger(entry.data)) { for (const change of entry.data.changes) { const referenced = seenLabelsById.get(change.labelEntryId); if (referenced?.targetId === change.targetId && referenced.label === change.label) { ownedLabelEntryIds.add(change.labelEntryId); } } } } const manuallyClearedTargets = new Set(); for (const [targetId, latest] of latestLabelEntryByTarget) { if (latest.label === undefined && !ownedLabelEntryIds.has(latest.id)) manuallyClearedTargets.add(targetId); } return { latestLabelEntryByTarget, ownedLabelEntryIds, manuallyClearedTargets }; } export function classifyOwnership(targetId: string, state: OwnershipState): "owned" | "manual" | "none" { const latest = state.latestLabelEntryByTarget.get(targetId); if (!latest) return "none"; return state.ownedLabelEntryIds.has(latest.id) ? "owned" : "manual"; }