import { allConfident } from "./decide"; import type { FixPlan } from "./plan"; export const MERGEFIX_MODES = ["propose", "auto"] as const; export type MergeFixMode = (typeof MERGEFIX_MODES)[number]; /** An explicit request wins; otherwise the repository's opt-in decides, and proposals are the default. */ export function resolveMode(requested: MergeFixMode | undefined, repoAutoApply: boolean): MergeFixMode { return requested ?? (repoAutoApply ? "auto" : "propose"); } /** Only auto mode lands a plan without review, and only when decide.ts raised no reasons for any file. */ export function shouldAutoApply(mode: MergeFixMode, plan: FixPlan): boolean { return ( mode === "auto" && plan.autoApply && plan.files.every((file) => file.status === "planned" && allConfident(file.decision)) ); } /** Accepted files whose picks fell below the confidence threshold; applying them needs an explicit confirmation. */ export function belowThreshold(plan: FixPlan, paths: string[]): string[] { const wanted = new Set(paths); return plan.files.flatMap((file) => file.status === "planned" && wanted.has(file.path) && !allConfident(file.decision) ? [file.path] : [], ); } export type Resolution = { path: string; content: string }; export type Selection = { ok: true; resolutions: Resolution[] } | { ok: false; error: string }; /** Picks the accepted files of a proposal; omitted `paths` means every file that has a resolution. */ export function selectResolutions(plan: FixPlan, paths?: string[]): Selection { const available = new Map( plan.files.flatMap((file) => file.status === "planned" && file.resolution !== null ? [[file.path, file.resolution] as const] : [], ), ); const wanted = paths === undefined ? [...available.keys()] : [...new Set(paths)]; if (wanted.length === 0) return { ok: false, error: "No files were selected to apply" }; const missing = wanted.filter((path) => !available.has(path)); if (missing.length > 0) return { ok: false, error: `No resolution is proposed for ${missing.join(", ")}` }; return { ok: true, resolutions: wanted.map((path) => ({ path, content: available.get(path) ?? "" })) }; } /** The proposal is only valid against the exact head and base it was planned on. */ export function isStale( run: { head_sha: string; base_sha: string | null }, current: { headSha: string; baseSha: string | null }, ): boolean { return run.base_sha === null || run.head_sha !== current.headSha || run.base_sha !== current.baseSha; }