/** * lib/guarded-rm.ts — legacy-cleanup deletion with the two invariants every * scaffolder's `rmSync(legacyPaths)` sweep must honour: * * 1. A file whose head carries the `@customised` marker is NEVER deleted — * it is the bespoke seam (architecture C), the developer owns it. The * legacy path of a relocation is a perfectly plausible place for * hand-written code on an app that predates the classification * (`src/features/{module}/{entity}/…` is a banal React layout): before * this helper, scaffold-api-client's sweep deleted such files silently. * 2. Every deletion AND preservation is RETURNED so the caller traces it in * its envelope (`data.filesRemoved` + a warning per preserved file) — a * deletion the report never mentions is indistinguishable from data loss. * * Paths resolve through `safeJoinPath(outdir, rel)` (traversal-safe). `dryRun` * only stats — nothing is touched. A file written by hand WITHOUT the marker * remains deletable: the marker IS the sanctioned opt-out contract * (scaffold-component / scaffold-layout / scaffold-ui-primitives), and the * returned `removed` list is what makes the loss at least visible. */ import fs from 'node:fs' import { safeJoinPath } from './fs.js' /** * The `@customised` head contract shared by the frontend scaffolders * (scaffold-component/index.ts, scaffold-layout, scaffold-ui-primitives) plus * the HTML comment variant for non-JS artifacts. */ export function isCustomised(source: string): boolean { const head = source.trimStart().slice(0, 100) return ( head.startsWith('/* @customised') || head.startsWith('// @customised') || head.startsWith('