/** * `briefUpgrade` — inspect the brief overlay (package floor + POLICY.md) and run the `--migrate` / `--retrofit` paths. * * Moved out of cli.ts's switch by the per-verb module split (#462); * only the case wrapper, the injected `ctx` lookups and the imports * changed from the original bodies. */ import type { CommandContext } from "./context.ts"; import { existsSync, readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { applyRetrofit, formatBriefReport, formatMigrateResult, formatRetrofitProposal, formatRetrofitRefusal, inspectBriefLayout, migrateToPolicy, missingSections, proposeRetrofit, repairPolicyBannerCrumbs, sharedPolicyPath, } from "../brief-upgrade.ts"; import { findProject, loadConfig } from "../config.ts"; import { briefPathForProject, policyPathForProject, renderBriefForProject, renderFloorForProject, shippedBriefTemplate, } from "../setup.ts"; import type { ProjectConfig } from "../types.ts"; export async function briefUpgradeCommand(ctx: CommandContext): Promise { const override = ctx.flag("file"); let project: ProjectConfig | undefined; let path: string; if (override === undefined) { project = findProject(loadConfig(), ctx.projectFlag); path = briefPathForProject(project); } else { path = override; try { project = findProject(loadConfig(), ctx.projectFlag); } catch { project = undefined; } } const workspaceRoot = project?.workspaceRoot ?? dirname(path); const rendered = project === undefined ? shippedBriefTemplate() : renderBriefForProject(project); const floor = project === undefined ? shippedBriefTemplate() : renderFloorForProject(project); const layout = inspectBriefLayout(workspaceRoot, rendered); // Host-wide shared policy, reported as a layer when present (#363). const sharedBrief = existsSync(sharedPolicyPath()) ? sharedPolicyPath() : undefined; if (ctx.argv.includes("--retrofit")) { let live: string; try { live = readFileSync(path, "utf8"); } catch { process.stderr.write(`omp-conductor: no brief at ${path}.\n`); process.exit(1); } const result = proposeRetrofit(live); if (result.kind === "no-cut") { process.stdout.write( `brief ${path}\n\nNo Releases / Project context / Reporting / Amendments heading found — cannot classify a cut.\n`, ); process.exit(1); } if (result.kind === "interleaved") { process.stdout.write(`${formatRetrofitRefusal(path, result)}\n`); process.exit(1); } process.stdout.write(`${formatRetrofitProposal(path, result.proposal)}\n`); if (ctx.argv.includes("--apply")) { const backup = applyRetrofit(path, result.proposal); process.stdout.write(`\napplied retrofit — previous brief kept at ${backup}\n`); } return; } if (ctx.argv.includes("--migrate")) { if (layout.kind === "overlay") { if (!ctx.argv.includes("--apply")) { process.stdout.write( `${formatBriefReport(path, layout, [], sharedBrief)}\n\n` + "POLICY.md already present. --migrate --apply will strip any leading\n" + "banner-comment crumbs from POLICY.md and recompose ORCHESTRATOR.md.\n", ); return; } if (project === undefined) { process.stderr.write( "omp-conductor: repairing an overlay needs --project (or a config) so the floor renders.\n", ); process.exit(1); } const repaired = repairPolicyBannerCrumbs({ orchestratorPath: layout.orchestratorPath, policyPath: layout.policyPath, floor: renderFloorForProject(project), }); if (repaired === undefined) { process.stdout.write( `${formatBriefReport(path, layout, [], sharedBrief)}\n\nrecomposed ORCHESTRATOR.md — POLICY.md needed no crumb strip.\n`, ); } else { process.stdout.write(`${formatMigrateResult(repaired)}\n`); } return; } if (layout.kind === "missing") { process.stderr.write(`omp-conductor: no brief at ${path} to migrate.\n`); process.exit(1); } if (layout.kind === "legacy-handwritten") { process.stdout.write(`${formatBriefReport(path, layout, layout.missing, sharedBrief)}\n`); process.exit(1); } if (project === undefined && /\{\{[A-Za-z0-9_]+\}\}/.test(floor)) { process.stderr.write( "omp-conductor: --migrate needs --project (or a config) so the floor renders without {{PLACEHOLDER}}s.\n", ); process.exit(1); } if (!ctx.argv.includes("--apply")) { process.stdout.write( [ `migrate ${layout.orchestratorPath}`, "", "Would write POLICY.md from the owned half below YOURS TO EDIT,", "then recompose ORCHESTRATOR.md from the package floor + that policy.", "", "Apply: omp-conductor brief-upgrade --migrate --apply", ].join("\n") + "\n", ); return; } const policyPath = project ? policyPathForProject(project) : join(workspaceRoot, "POLICY.md"); const result = migrateToPolicy({ orchestratorPath: layout.orchestratorPath, policyPath, floor: project ? renderFloorForProject(project) : floor, owned: layout.owned, }); process.stdout.write(`${formatMigrateResult(result)}\n`); return; } // No `--migrate` / `--retrofit`: report the layout and what to run. The // legacy single-file merge that used to live here is gone (#131) — a bare // `--apply` now says so rather than silently doing nothing. if (ctx.argv.includes("--apply")) { process.stderr.write( "brief-upgrade: the legacy single-file merge was removed in 0.4.3 — use --migrate --apply (bannered) or --retrofit --apply (hand-written)\n", ); process.exit(2); } let missing: string[] = []; if (layout.kind !== "overlay" && layout.kind !== "missing") { let live: string; try { live = readFileSync(path, "utf8"); } catch { process.stderr.write( `omp-conductor: no brief at ${path}` + `${override === undefined ? " — run `omp-conductor setup brief` and say yes to writing ORCHESTRATOR.md." : "."}\n`, ); process.exit(1); } missing = missingSections(live, rendered); } process.stdout.write(`${formatBriefReport(path, layout, missing, sharedBrief)}\n`); if (layout.kind !== "overlay" && project === undefined) { process.stdout.write( "\nnote: no conductor config resolved on this host, so the template's\n" + "{{PLACEHOLDER}} coordinates are unsubstituted. Section names are unaffected.\n", ); } }