@noir-ai/create 1.19.2-beta.1 → 1.19.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +71 -71
- package/dist/index.js +29 -29
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/templates/gitignore.tmpl +1 -0
- package/templates/noir-readme.md.tmpl +4 -3
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ancestors.ts","../src/hooks.ts","../src/writers.ts","../src/manifest.ts","../src/template.ts","../src/template-loader.ts","../src/merge.ts","../src/migrations/index.ts","../src/template-history.ts","../src/migrations/runner.ts","../src/scaffold.ts","../src/baseline.ts","../src/scaffold-version.ts","../src/stack-detect.ts"],"sourcesContent":["// Ancestor store for three-way managed-region merge.\n//\n// Persists the last-emitted managed-region text per (file, block) so a later\n// `noir init`/`sync` can three-way merge (base/ours/theirs) instead of\n// strip-replacing. Stored at `.noir/ancestors.json` as a flat map.\n//\n// Ancestor capture is UNCONDITIONAL (every init/create/sync writes a\n// snapshot of the managed regions it touched), so the first merge run — now the\n// DEFAULT — always has a base. `writeAncestors` is content-hash idempotent: a\n// no-op sync leaves the file untouched (no mtime/git churn).\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nconst ANCESTORS_REL = '.noir/ancestors.json';\n\n/** Absolute path of the ancestor store under `root`. */\nexport function ancestorsPath(root: string): string {\n return join(root, ANCESTORS_REL);\n}\n\n/** Read the ancestor map (`{ \"${relPath}::${blockBegin}\": regionText }`).\n * Returns `{}` for a missing/corrupt file (never throws). */\nexport function readAncestors(root: string): Record<string, string> {\n if (!existsSync(ancestorsPath(root))) return {};\n try {\n const raw = readFileSync(ancestorsPath(root), 'utf8');\n const obj: unknown = JSON.parse(raw);\n if (obj && typeof obj === 'object' && !Array.isArray(obj)) {\n return obj as Record<string, string>;\n }\n return {};\n } catch {\n return {};\n }\n}\n\n/** Write the ancestor map. Ensures `.noir/` exists (a standalone caller may\n * invoke this before the scaffold has created the dir). Derived state, so the\n * plain write (no tmp) is fine. Content-hash dedup — when the serialized\n * bytes equal what is already on disk, the rewrite is skipped so a no-op sync\n * leaves ancestors.json untouched (no mtime/git churn). */\nexport function writeAncestors(root: string, map: Record<string, string>): void {\n const path = ancestorsPath(root);\n const next = `${JSON.stringify(map, null, 2)}\\n`;\n try {\n if (readFileSync(path, 'utf8') === next) return; // unchanged → skip\n } catch {\n /* missing → fall through to write */\n }\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, next, 'utf8');\n}\n","import { existsSync, mkdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { type HostId, resolveAdapter } from '@noir-ai/adapters';\nimport { resolveNoirCommand } from '@noir-ai/core';\nimport { mergeJson, regenerate } from './writers.js';\n\n/**\n * Emit a host's lifecycle-hook config on demand — the write-once `mergeJson`\n * hook the scaffold deliberately does NOT re-emit on `noir init --upgrade` or\n * `noir sync`. This is the explicit restore path (`noir hooks sync`): it runs\n * the same `adapter.emitHooks` seam the scaffold uses and dispatches each entry\n * to the writer its mode names (`regenerate` for cursor, `mergeJson` for\n * claude/gemini). With `force`, an existing Noir-owned hook entry (one whose\n * command carries the dedup marker) is refreshed to the currently resolved\n * command instead of being left as-is; the user's other keys and non-Noir hooks\n * are never touched.\n *\n * Pure filesystem work — no daemon, no store, no prompt. A host with no hook\n * surface (agents-md, opencode) returns an empty outcome list.\n */\n\n/** One host hook-config write performed by {@link emitHooksToFile}. */\nexport interface HookEmitOutcome {\n /** Repo-relative path of the host's hook config file. */\n path: string;\n /** `mergeJson` = merged into a user-owned file; `regenerate` = rewritten. */\n mode: 'mergeJson' | 'regenerate';\n /** True when the bytes on disk changed (a write happened). */\n written: boolean;\n}\n\nexport interface EmitHooksResult {\n host: HostId;\n outcomes: HookEmitOutcome[];\n}\n\nexport function emitHooksToFile(\n root: string,\n host: HostId,\n opts?: { force?: boolean },\n): EmitHooksResult {\n const adapter = resolveAdapter(host);\n const emissions = adapter.emitHooks?.({ command: resolveNoirCommand() }) ?? null;\n if (emissions === null) return { host, outcomes: [] };\n\n const outcomes: HookEmitOutcome[] = emissions.map((e) => {\n const abs = join(root, e.path);\n // The writers create a temp sibling in the target's directory and rename it\n // in; the directory must exist first (mirrors the scaffold's mkdir -p).\n mkdirSync(dirname(abs), { recursive: true });\n if (e.mode === 'regenerate') {\n const content = `${JSON.stringify(e.content, null, 2)}\\n`;\n // `regenerate` always rewrites; skip a byte-identical file so `written`\n // stays honest and a repeat run is a true no-op (the scaffold tracks this\n // as \"identical\" at the orchestrator level, which this path bypasses).\n if (existsSync(abs) && readFileSync(abs, 'utf8') === content) {\n return { path: e.path, mode: 'regenerate', written: false };\n }\n const out = regenerate(abs, content);\n return { path: e.path, mode: 'regenerate', written: out.written };\n }\n const out = mergeJson(abs, e.content, e.dedupSubstring, opts?.force === true);\n return { path: e.path, mode: 'mergeJson', written: out.written };\n });\n\n return { host, outcomes };\n}\n","import {\n closeSync,\n existsSync,\n openSync,\n readFileSync,\n renameSync,\n rmSync,\n writeSync,\n} from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport type { FileModeOutcome, ManagedBlock } from '@noir-ai/core';\nimport { atomicWriteFile, stripManagedBlock, writeManagedRegion } from '@noir-ai/core';\n\n/**\n * The three-mode writer — generalizes the earlier `writeManagedRegion` into\n * the declarative dispatch the scaffold manifest drives. Each mode maps 1:1 to\n * an artifact class in the artifact matrix:\n *\n * - {@link regenerate} — pure pointers (`.mcp.json`, `NOIR.md` brief, …).\n * Always overwritten, atomically.\n * - {@link managedBlock} — co-owned files (`CLAUDE.md` context/rules,\n * `.gitignore` noir block, …). DELEGATES to\n * core's `writeManagedRegion` so user content\n * outside the markers is preserved byte-for-byte and\n * re-runs are idempotent. Never duplicate the\n * managed-region logic.\n * - {@link skipIfExists} — user-owned seeds (`RULES.md`, `config.yml`,\n * `project.id`). Write once; never clobber.\n *\n * The orchestrator (`scaffold.ts`) is the only intended caller; the per-mode\n * functions are exported so the cli and tests can drive them directly\n * when a one-off write is needed outside the manifest.\n */\n\nexport type WriteMode = 'regenerate' | 'managedBlock' | 'skipIfExists' | 'mergeJson';\n\nexport interface WriteOutcome {\n /** The absolute path that was written. */\n path: string;\n mode: WriteMode;\n /** true when bytes hit disk; false for skipIfExists no-ops. regenerate and\n * managedBlock always write (managedBlock may write identical bytes — that\n * still counts as a write for telemetry purposes; the file IS up to date). */\n written: boolean;\n}\n\n/** Atomic overwrite. Writes to `<file>.tmp.<pid>.<rnd>` in the same directory,\n * fsyncs, then renames over the target so a crash never leaves a half-written\n * file (the pointer files this is used for are read by the host agent first —\n * a truncated CLAUDE.md/.mcp.json would break the very startup Noir serves).\n *\n * Parent directories are NOT created here — the orchestrator does that once\n * for the whole manifest so a missing dir is a single, attributable failure\n * rather than N silent ones inside the writer.\n */\nexport function regenerate(absPath: string, content: string): WriteOutcome {\n const dir = dirname(absPath);\n const tmp = join(\n dir,\n `.${basename(absPath)}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`,\n );\n // Open with 'w' truncates; writeSync + closeSync before rename so the bytes\n // are durable pre-swap. O_SYNC would be stronger but is platform-flaky; the\n // rename is the real atomicity guarantee on POSIX (and practical-enough on\n // the win32 targets Noir supports).\n //\n // The tmp MUST be cleaned up on EVERY exit path. The previous shape only\n // ran `rmSync(tmp)` when `renameSync` threw, so a `writeSync` failure (disk\n // full, EPERM, …) left the tmp behind. A single try/finally with `force:true`\n // rmSync (no-op ENOENT after a successful rename consumed the file) covers\n // both.\n let fd: number | undefined;\n try {\n fd = openSync(tmp, 'w');\n writeSync(fd, content, 0, 'utf8');\n closeSync(fd);\n fd = undefined; // closed cleanly — don't re-close in finally\n renameSync(tmp, absPath);\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n /* best-effort: the rename/rm below are the meaningful cleanups */\n }\n }\n try {\n rmSync(tmp, { force: true });\n } catch {\n /* best-effort */\n }\n }\n return { path: absPath, mode: 'regenerate', written: true };\n}\n\n/** Re-emit a managed region, delegating to core's `writeManagedRegion`.\n * `regionText` MUST already include the begin/end markers (matches the shape\n * `writeManagedRegion` expects and that `IGNORE_BLOCK`/`CONTEXT_BLOCK`\n * callers build in core/cli). Use {@link buildRegion} to assemble it from a\n * block + body. */\nexport function managedBlock(\n absPath: string,\n block: ManagedBlock,\n regionText: string,\n): WriteOutcome {\n writeManagedRegion(absPath, block, regionText);\n return { path: absPath, mode: 'managedBlock', written: true };\n}\n\n/** Atomically (re)emit MULTIPLE managed regions into the SAME file in one\n * pass. Used when a co-owned target carries more than one managed block —\n * today only `CLAUDE.md` (CONTEXT + RULES).\n *\n * WHY this exists: calling {@link managedBlock} twice on the same file is\n * NOT byte-idempotent. The 2nd call strips ONLY its own block, treats the 1st\n * region (and the `\\n\\n` separator) as user content, `trimEnd`s it, and\n * re-appends a fresh `\\n\\n` separator. Re-runs therefore accumulate ~2 leading\n * `\\n` bytes per init (verified: 158→168 over 5 runs). Doing both regions in a\n * SINGLE read → strip-all → append-all pass removes the interleaving: after\n * stripping BOTH blocks the only thing left is real user content, so re-runs\n * produce identical bytes.\n *\n * Strategy:\n * 1. Read the file (missing → empty).\n * 2. Strip EVERY named block (via core's `stripManagedBlock`, in the given\n * order) — what remains is user content + any managed blocks outside this\n * group.\n * 3. Append all `regionText`s in the GIVEN ORDER, joined by a single `\\n`.\n * Each `regionText` already ends with `\\n` (buildRegion appends the end\n * marker's trailing newline), so `\\n` between regions yields exactly one\n * blank-line separator (`END\\n` + `\\n` + `BEGIN`) — byte-identical to what\n * the single-block path emits on a first run, so the CONTEXT/RULES parity\n * gates against `claudeAdapter.emitContext/emitRules` keep passing.\n * 4. Write the result atomically (via core's `atomicWriteFile`: temp sibling +\n * rename). This target is a file the user also owns, so an interrupted\n * write must not leave the user's own content half-written — and the swap\n * keeps the file's existing permissions, which a fresh temp file would\n * otherwise reset to the umask default.\n *\n * Single-region files (NOIR.md brief, ignore files) do NOT route through here\n * — the orchestrator only calls this for groups of ≥2 managed blocks, so\n * single-region byte-stability (delegated to core's `writeManagedRegion`)\n * is unchanged. */\nexport function managedBlocks(\n absPath: string,\n regions: ReadonlyArray<{ block: ManagedBlock; regionText: string }>,\n): WriteOutcome {\n if (regions.length === 0) {\n throw new Error('managedBlocks requires at least one region');\n }\n if (regions.length === 1) {\n const only = regions[0];\n if (!only) throw new Error('managedBlocks: undefined region');\n return managedBlock(absPath, only.block, only.regionText);\n }\n let content = '';\n try {\n content = readFileSync(absPath, 'utf8');\n } catch {\n /* missing → treat as empty */\n }\n let stripped = content;\n for (const r of regions) {\n stripped = stripManagedBlock(stripped, r.block);\n }\n const regionsJoined = regions.map((r) => r.regionText).join('\\n');\n // Whitespace-only remainder (typical on re-run after both blocks are\n // stripped) → emit just the regions, no leading separator.\n const next =\n stripped.trim().length > 0 ? `${stripped.trimEnd()}\\n\\n${regionsJoined}` : regionsJoined;\n atomicWriteFile(absPath, next);\n return { path: absPath, mode: 'managedBlock', written: true };\n}\n\n/** Assemble `<begin>\\n<body>\\n<end>\\n` for a managed block. Centralized here so\n * every caller (manifest rendering, tests, future migrations) produces the\n * exact byte shape `writeManagedRegion` strips/expects. The trailing newline\n * is part of the contract — `stripManagedBlock`'s regex eats a trailing `\\n`\n * so re-runs stay idempotent instead of accumulating blank lines.\n *\n * `body` is `trimEnd()`-ed before wrapping so template authors can keep the\n * conventional trailing newline in `.tmpl` files without producing a\n * double-newline before the end marker. This keeps the output byte-identical\n * to `claudeAdapter.emitContext`/`emitRules` and core's `syncIgnores`, which\n * the cli refactor relies on for diff-free output. */\nexport function buildRegion(block: ManagedBlock, body: string): string {\n return `${block.begin}\\n${body.trimEnd()}\\n${block.end}\\n`;\n}\n\n/** Compute the exact byte content a {@link managedBlock} write would produce,\n * WITHOUT touching disk. Mirrors core's `writeManagedRegion` byte-for-byte\n * (`stripManagedBlock` + `trimEnd` + `\\n\\n` separator + `regionText`) so the\n * orchestrator's content-hash dedup (skip an unchanged managed region) and\n * the writer always agree on every byte. Pass the CURRENT on-disk content;\n * the predictor never reads the file itself (the orchestrator reads once and\n * reuses the bytes for both predict + compare). */\nexport function predictManagedBlock(\n currentContent: string,\n block: ManagedBlock,\n regionText: string,\n): string {\n const stripped = stripManagedBlock(currentContent, block);\n return stripped ? `${stripped.trimEnd()}\\n\\n${regionText}` : regionText;\n}\n\n/** Compute the exact byte content a {@link managedBlocks} (multi-region) write\n * would produce, WITHOUT touching disk. Mirrors the multi-region writer's\n * strip-all + append-all pass (the single-region case delegates to\n * {@link predictManagedBlock}). Used by the content-hash dedup so a\n * multi-region file (CLAUDE.md CONTEXT+RULES) that is already up to date is\n * skipped — `noir sync` on an unchanged tree writes NOTHING. */\nexport function predictManagedBlocks(\n currentContent: string,\n regions: ReadonlyArray<{ block: ManagedBlock; regionText: string }>,\n): string {\n if (regions.length === 0) {\n throw new Error('predictManagedBlocks requires at least one region');\n }\n if (regions.length === 1) {\n const only = regions[0];\n if (!only) throw new Error('predictManagedBlocks: undefined region');\n return predictManagedBlock(currentContent, only.block, only.regionText);\n }\n let stripped = currentContent;\n for (const r of regions) {\n stripped = stripManagedBlock(stripped, r.block);\n }\n const regionsJoined = regions.map((r) => r.regionText).join('\\n');\n return stripped.trim().length > 0 ? `${stripped.trimEnd()}\\n\\n${regionsJoined}` : regionsJoined;\n}\n\n/** Write `content` to `absPath` only if no file exists there. Returns whether\n * bytes were written. Parent dirs are created by {@link atomicWriteFile}.\n *\n * `fileMode` is the permission for the NEWLY created file —\n * `0o600` for the `.noir/.env` credential seed. It is applied to the temp file\n * BEFORE the rename (see `AtomicWriteOptions.mode`), so the file never exists\n * at its final path with a laxer mode. It is deliberately ignored when the\n * target already exists: this writer's whole contract is \"never touch an\n * existing file\", and that includes its permissions. POSIX-only — Windows\n * permissions are ACL-based and the mode is dropped there.\n *\n * The write goes through `atomicWriteFile` (tmp + rename) rather than a bare\n * `writeFileSync`: a requested mode must land on the temp, and an interrupted\n * seed write should not leave a half-written `.noir/.env` behind. Byte output\n * is unchanged for every existing caller (same content, umask default 0o644\n * when no mode is requested).\n *\n * An EXISTING file keeps this writer's create-only contract, but its mode is\n * not left to drift: the orchestrator calls {@link ensureOwnerOnly} after the\n * emit, which tightens a group/world-readable credential to 0600. */\nexport function skipIfExists(absPath: string, content: string, fileMode?: number): WriteOutcome {\n if (existsSync(absPath)) {\n return { path: absPath, mode: 'skipIfExists', written: false };\n }\n atomicWriteFile(absPath, content, fileMode !== undefined ? { mode: fileMode } : {});\n return { path: absPath, mode: 'skipIfExists', written: true };\n}\n\n/** What happened when the owner-only mode was re-asserted on a credential\n * file — see {@link ensureOwnerOnly}. */\nexport type EnvMode = FileModeOutcome;\n\n/** Re-exported from `@noir-ai/core`, where the re-assert lives: the embedded\n * store needs the same \"tighten a lax permission on every open\" behaviour for\n * its database and directory, and a copy per package would drift. `EnvMode` is\n * the name the scaffold result has always used for the outcome; the canonical\n * name is core's `FileModeOutcome`.\n *\n * A `.noir/.env` needs it because {@link skipIfExists} applies its 0600 only\n * when it creates the file and no writer ever opens an existing one, so a file\n * seeded by an earlier Noir, or rewritten by an editor that saves by rename,\n * would keep a lax mode indefinitely and make the environment diagnostic warn\n * on every command. */\nexport { ensureOwnerOnly } from '@noir-ai/core';\n\n/** Overwrite an existing seed's bytes while keeping the permission bits it\n * already has. The `skipIfExists` refresh variant: the file exists (a refresh\n * is only ever decided from bytes already on disk), its content is Noir's own\n * shipped text, and only that text changes — so, unlike {@link regenerate},\n * which writes a fresh temp file at the umask default and renames over the\n * target, this leaves the mode alone.\n *\n * `atomicWriteFile` is what makes that safe: it stats the existing target and\n * restores that mode after the rename, so a crash mid-write cannot leave a\n * half-written seed behind and no permission is silently changed. Parent\n * directories are the orchestrator's job, created once for the whole manifest.\n *\n * The staleness decision is NOT made here — the writer is handed bytes that\n * the caller has already established are an unedited older copy of the seed. */\nexport function refreshSeed(absPath: string, content: string): WriteOutcome {\n atomicWriteFile(absPath, content);\n return { path: absPath, mode: 'skipIfExists', written: true };\n}\n\n/**\n * Merge-aware JSON write (the SessionStart hook the scaffold emits). `settings.local.json` is JSON\n * and CANNOT carry `<!-- noir:* -->` managed markers, so it is neither\n * `managedBlock` (would need comments) nor `regenerate` (would clobber the\n * user's `permissions`/`env`/`enabledPlugins`) nor `skipIfExists` (would go\n * stale). This path:\n * 1. Reads the existing file (missing → `{}`).\n * 2. Preserves EVERY existing top-level key (`permissions`, `env`, `hooks`, …).\n * 3. Merges the provided `patch` object — for `hooks` arrays, the existing\n * arrays are kept and entries whose command already contains the given\n * `dedupSubstring` are NOT re-added (context-mode's alreadyRegistered\n * pattern — the entry is written once, never clobbered).\n * 4. Writes atomically via the same tmp+rename as `regenerate` when the merged\n * bytes differ (content-hash dedup: an unchanged `noir sync` writes nothing).\n *\n * `patch` is a deep-merge target: top-level keys merge by key; `hooks.*` arrays\n * append-with-dedup. Other arrays replace only when provided. Returns the same\n * `WriteOutcome` shape. */\nexport function mergeJson(\n absPath: string,\n patch: Record<string, unknown>,\n dedupSubstring?: string,\n force?: boolean,\n): WriteOutcome {\n let existing: Record<string, unknown> = {};\n if (existsSync(absPath)) {\n try {\n existing = JSON.parse(readFileSync(absPath, 'utf8')) as Record<string, unknown>;\n } catch {\n // Corrupt/unparseable existing file — treat as empty, the merge will\n // produce a valid file (the old bytes are preserved in git if versioned).\n existing = {};\n }\n }\n\n const merged = deepMergePreservingHooks(existing, patch, dedupSubstring, force);\n const content = `${JSON.stringify(merged, null, 2)}\\n`;\n\n // Content-hash dedup — a no-op sync writes nothing.\n if (existsSync(absPath)) {\n try {\n if (readFileSync(absPath, 'utf8') === content) {\n return { path: absPath, mode: 'mergeJson', written: false };\n }\n } catch {\n /* fall through and write */\n }\n }\n\n regenerate(absPath, content); // reuse the atomic tmp+rename path\n return { path: absPath, mode: 'mergeJson', written: true };\n}\n\n/** True when a `hooks.<Event>` entry carries the dedup marker — either as its\n * own `command` or inside the matcher group's inner `hooks` array, the shape\n * Claude Code stores (`{hooks:[{type:'command',command:'…'}]}`). Checking only\n * the top level never matched that shape, so a re-emit (init `--force`)\n * appended a second copy of the hook. */\nfunction entryHasCommand(entry: unknown, marker: string): boolean {\n if (typeof entry !== 'object' || entry === null) return false;\n const obj = entry as { command?: unknown; hooks?: unknown };\n if (typeof obj.command === 'string' && obj.command.includes(marker)) return true;\n if (!Array.isArray(obj.hooks)) return false;\n return obj.hooks.some((h) => {\n if (typeof h !== 'object' || h === null) return false;\n const command = (h as { command?: unknown }).command;\n return typeof command === 'string' && command.includes(marker);\n });\n}\n\n/** Deep-merge `patch` into `existing`. `hooks.*` arrays append-with-dedup\n * (an entry whose command contains `dedupSubstring` is kept as-is, not\n * re-added); other arrays replace only when present in the patch. With\n * `force`, an existing entry whose command carries the marker is DROPPED\n * before the patch entries are appended, so a drifted Noir-owned entry is\n * refreshed in place while entries the user added stay untouched. */\nfunction deepMergePreservingHooks(\n existing: Record<string, unknown>,\n patch: Record<string, unknown>,\n dedupSubstring?: string,\n force?: boolean,\n): Record<string, unknown> {\n const out: Record<string, unknown> = { ...existing };\n for (const [key, value] of Object.entries(patch)) {\n if (key === 'hooks' && typeof value === 'object' && value !== null) {\n const existingHooks = (out.hooks as Record<string, unknown> | undefined) ?? {};\n const mergedHooks: Record<string, unknown> = { ...existingHooks };\n for (const [event, entries] of Object.entries(value as Record<string, unknown>)) {\n const existingEntries = Array.isArray(mergedHooks[event])\n ? (mergedHooks[event] as unknown[])\n : [];\n const patchEntries = Array.isArray(entries) ? entries : [];\n const hasMarker = (x: unknown): boolean =>\n dedupSubstring !== undefined && entryHasCommand(x, dedupSubstring);\n // Append-only with dedup: only add patch entries whose command does NOT\n // already contain the dedup marker (the Noir hook was registered before —\n // write-once, never clobbered). A patch entry whose command itself is\n // missing is still added (caller bug, but non-fatal). `force` instead\n // drops every marker-carrying entry first, then re-adds the patch.\n const keptEntries =\n force === true ? existingEntries.filter((x) => !hasMarker(x)) : existingEntries;\n const alreadyRegistered = force !== true && existingEntries.some(hasMarker);\n const toAdd = alreadyRegistered ? [] : patchEntries;\n mergedHooks[event] = [...keptEntries, ...toAdd];\n }\n out.hooks = mergedHooks;\n } else {\n // Non-hooks keys: patch wins, but only when the patch actually provides it.\n out[key] = value;\n }\n }\n return out;\n}\n","import { existsSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport {\n AGENTS_MD_FILENAME,\n type EmitContext,\n emitAgentsMd,\n type HostAdapter,\n type HostId,\n mcpConfigPathForAdapter,\n resolveAdapter,\n} from '@noir-ai/adapters';\nimport {\n CONTEXT_BLOCK,\n IGNORE_BLOCK,\n type ManagedBlock,\n managedBlock,\n NOIR_DIR,\n paths,\n RULES_BLOCK,\n readWorkspaceMarker,\n} from '@noir-ai/core';\nimport type { StackInfo } from './stack-detect.js';\nimport { render } from './template.js';\nimport type { SeedKind } from './template-history.js';\nimport { loadTemplate } from './template-loader.js';\nimport type { WriteMode } from './writers.js';\n\n/**\n * Declarative scaffold manifest — the single source of truth for what\n * `init` / `create` / `sync` emit. Each entry is one artifact, tagged with its\n * write {@link WriteMode} so the orchestrator can dispatch without knowing\n * what's inside.\n *\n * FAITHFULNESS CONTRACT: this table is a strict superset of\n * the artifacts `packages/cli/src/{init,sync}.ts` wrote before the manifest\n * refactor. The cli refactor replaced those ad-hoc writers with a call into\n * `scaffold()`; the byte-for-byte output MUST stay equivalent for first-run\n * init. The manifest is HOST-PARAMETRIC: {@link buildManifest} returns\n * host-agnostic entries + a {@link buildHostArtifacts} call that materializes\n * per-host files\n * (CLAUDE.md/GEMINI.md for claude/gemini; AGENTS.md + .cursor/.../opencode.json\n * for agents-md/cursor/opencode) via the resolved adapter. The claude default\n * `noir init` stays BYTE-IDENTICAL to v1.1 — REMOVED the additive\n * root `AGENTS.md` (it was double-importing `.noir/NOIR.md` + RULES.md via\n * CLAUDE.md's existing @-imports; claude's native surface is CLAUDE.md alone).\n *\n * Path-derivation: repo-relative POSIX strings that mirror\n * `@noir-ai/core/layout.ts` (`paths.*`). The test suite asserts\n * `join(root, entry.path) === paths.X(root)` for every entry layout knows\n * about, so a layout rename is caught here instead of silently drifting.\n */\n\n/** `HostTag` is the SAME `HostId` enum the adapter registry uses\n * (re-exported so existing imports keep working); it used to be the\n * literal `'claude'`. Widening it to `HostId` lets one manifest serve every\n * host via the orchestrator's host filter + {@link buildHostArtifacts}. */\nexport type HostTag = HostId;\n\nexport interface ManifestEntry {\n /** Repo-relative POSIX path (forward slashes). Orchestrator joins with root. */\n path: string;\n mode: WriteMode;\n /** Opt-in flag for a DOC-ONLY seed — a file whose only job is to be read by a\n * human. On an upgrade, a seed still byte-identical to what an older Noir\n * shipped is refreshed in place rather than staying frozen at that older\n * text forever; one the user edited is never refreshed (it goes to the\n * ordinary conflict flow, which keeps the user's bytes by default).\n *\n * Honored on the upgrade emit only. A fresh `init`/`create` either creates\n * the file or leaves it alone, and `sync` does not emit seeds at all, so\n * neither can observe the flag — the behavior change is confined to the one\n * command a user runs to bring an initialized project up to date.\n *\n * DOC-ONLY SEEDS ONLY. A file the user owns the contents of must never carry\n * this flag: `.noir/.env`, `.noir/config.yml` and `.noir/project.id` are\n * theirs to shape, and an upgrade that rewrote one of them would destroy\n * exactly the work this engine exists to protect. Co-owned files (managed\n * blocks) must not set it either — they have a three-way merge that\n * preserves user edits, which is the better tool for a shared file.\n *\n * Which recorded seed the bytes are compared against is declared separately\n * in {@link REFRESHABLE_SEED_KIND}. */\n refreshIfStale?: true;\n /** Host tag; entry is skipped when opts.host !== entry.host.\n * Undefined = host-agnostic (every host emits it). */\n host?: HostTag;\n /** Required for `managedBlock` mode: the named block to re-emit. */\n block?: ManagedBlock;\n /** Literal content (`regenerate`/`skipIfExists`) or literal region BODY\n * (`managedBlock` — the orchestrator wraps it with the block markers).\n * Mutually exclusive with {@link template}. */\n content?: string;\n /** Template name (resolved by `template-loader`) for content/body.\n * Mutually exclusive with {@link content}. */\n template?: string;\n /** One-line human description for `noir doctor` + logs. */\n description?: string;\n /** Permission for a NEWLY created file (e.g. `0o600` for a credential seed).\n * Applied only on creation — the `skipIfExists` contract means an existing\n * file is not even opened. The orchestrator forwards this to the writer and\n * reports it on the result as {@link ScaffoldResult.fileModes}; the mode of\n * an existing credential file is re-asserted separately (`ensureOwnerOnly`).\n * Ignored by every other write mode, whose targets are regenerated rather\n * than seeded.\n * NOTE: POSIX-only. Windows permissions are ACL-based and ignore it. */\n fileMode?: number;\n /** Required for `mergeJson` mode: the JSON patch object (or a template that\n * renders to one). Merged into the existing file, preserving user keys. */\n patch?: Record<string, unknown>;\n /** Required for `mergeJson` mode: dedup substring for `hooks.*` entries — an\n * existing entry whose command contains this is NOT re-added. */\n dedupSubstring?: string;\n}\n\nexport type BuildManifestContext = {\n /** Absolute repo root. Needed so {@link buildHostArtifacts} can resolve\n * absolute adapter paths (`adapter.mcpConfigPath({root})`, etc.) to the\n * manifest's repo-relative POSIX shape. */\n root: string;\n /** Canonical project id (already created/read by the orchestrator). */\n projectId: string;\n /** Target host. Drives {@link buildHostArtifacts} via `resolveAdapter(host)`. */\n host: HostTag;\n /** MCP transport the host should use to reach Noir. */\n transport: 'stdio' | 'streamable-http';\n /** Required when transport is `streamable-http`. */\n url?: string;\n /** The `command` value emitted into the host's MCP config for the stdio\n * server. Defaults to `'noir'`; the orchestrator passes the absolute native\n * shim path (`~/.noir/bin/noir`) when a native install is detected, so GUI\n * MCP clients that don't read shell profiles can spawn the server. See\n * `resolveNoirCommand()` in @noir-ai/core. */\n command: string;\n /** Detected stack — drives stack-aware ignore emission (.npmignore /\n * .prettierignore only for JS; .dockerignore only when a Dockerfile is\n * present; an unknown/empty stack ⇒ all four, for backward compat). */\n stack?: StackInfo;\n /** Compiled project baseline (stack + canonical commands + entry points),\n * rendered into the `.noir/NOIR.md` brief block as `{{baseline}}`. */\n baseline?: string;\n /** The `rules.enabled` switch from the project's `.noir/config.yml`. False\n * drops the working-rules seed from the manifest, so no command emits or\n * backfills `.noir/rules/RULES.md` for a project that opted out. Undefined\n * (the schema's default state, and every caller that predates the switch)\n * means enabled — a project with no `rules:` block emits the seed exactly\n * as before. A file already on disk is never removed: dropping the entry\n * removes the emission, not the file. */\n rulesEnabled?: boolean;\n};\n\n// --- named managed blocks ----------------------------------------------------\n\n/** Co-owned NOIR.md auto-brief region. Defined locally (not exported from\n * core) because core's own named block instances cover only the three\n * regions core itself writes (context/rules/ignore); the brief is the\n * scaffold engine's own. Uses the SAME `managedBlock()` factory so marker\n * shape stays consistent with the rest of the family. */\nexport const BRIEF_BLOCK: ManagedBlock = managedBlock('brief', 'html');\n\n/** Co-owned `.noir/README.md` runtime map. Defined locally\n * for the same reason as {@link BRIEF_BLOCK}: the named\n * instances in core cover only the regions core itself writes. The map describes\n * paths that appear LATER in the project's life, so it must be re-emitted —\n * `regenerate` would clobber a user's annotations and `skipIfExists` would\n * freeze it at init time; a managed block keeps both sides honest. */\nexport const README_BLOCK: ManagedBlock = managedBlock('readme', 'html');\n\n/** The one row of the `.noir/README.md` store map that describes the\n * working-rules seed. The map ships as a single template because it documents\n * the whole canonical store at once, but the rules row describes a file that\n * only exists while `rules.enabled` is on. It is therefore dropped from the\n * rendered text rather than kept in a second template variant: one template\n * keeps the wording (and the surrounding table) in one place, and a test\n * asserts the template still carries exactly one row with this prefix, so a\n * reworded or renamed row fails loudly instead of silently surviving a\n * switch-off emit.\n *\n * A prefix match, not a full-line constant: the row's prose is free to change\n * without a second edit here. */\nconst RULES_MAP_ROW_PREFIX = '| `rules/RULES.md` |';\n\n/** Render the `.noir/README.md` store map: the shipped template, minus the\n * working-rules row when the project has switched the seed off. With the\n * switch on (and for every project that predates it) this is exactly what\n * {@link renderEntry} produced from the plain template. */\nfunction readmeMapContent(ctx: BuildManifestContext): string {\n const rendered = render(loadTemplate('noir-readme.md.tmpl'), ctx);\n if (ctx.rulesEnabled !== false) return rendered;\n return rendered\n .split('\\n')\n .filter((line) => !line.startsWith(RULES_MAP_ROW_PREFIX))\n .join('\\n');\n}\n\n// --- repo-relative path constants (mirror @noir-ai/core/layout.ts) -----------\n// Inlined as string literals so the manifest has zero runtime dep on layout\n// for path strings; the test suite cross-checks against `paths.*`.\n\nconst P = {\n projectId: `${NOIR_DIR}/project.id`,\n config: `${NOIR_DIR}/config.yml`,\n envExample: `${NOIR_DIR}/.env.example`,\n env: `${NOIR_DIR}/.env`,\n noirMd: `${NOIR_DIR}/NOIR.md`,\n rulesMd: `${NOIR_DIR}/rules/RULES.md`,\n readme: `${NOIR_DIR}/README.md`,\n} as const;\n\n// Aliases for the parity test (kept here so a layout rename breaks the test\n// at the same site the literal lives, not in a far-off helper).\nexport const MANIFEST_PATH_PARITY: ReadonlyArray<\n [entryPath: string, layoutFn: (root: string) => string]\n> = [\n [P.projectId, paths.projectId],\n [P.config, paths.config],\n [P.noirMd, paths.noirMd],\n [P.rulesMd, paths.rulesMd],\n];\n\n/**\n * Which recorded seed each refreshable entry is compared against, keyed by the\n * path that emits it — see {@link ManifestEntry.refreshIfStale}.\n *\n * The history entries record the text of BOTH seeds, so the comparison has to\n * be told which one it is looking at. The manifest is what knows which path\n * holds which seed, and keeping that pairing in a table beside the path\n * constants means a path change breaks at the same site instead of silently\n * comparing `.noir/.env.example` against the recorded working-rules text — a\n * mismatch whose only symptom would be a file the user edited, overwritten.\n *\n * `manifest.test.ts` asserts this table and the flagged entries cover each\n * other, so a refreshable entry cannot be added without naming its seed.\n */\nexport const REFRESHABLE_SEED_KIND: Readonly<Record<string, SeedKind>> = {\n [P.envExample]: 'envExample',\n [P.rulesMd]: 'rulesSeed',\n};\n\n/**\n * Build the manifest for a given ctx. Pure (no I/O). The orchestrator calls\n * this once per scaffold run; tests assert the shape is stable.\n *\n * The manifest is `[...hostAgnosticEntries(ctx), ...hostSpecificEntries(ctx)]`\n * where the host-specific half comes from {@link buildHostArtifacts} (driven by\n * `resolveAdapter(ctx.host)`). The host-agnostic half is unchanged from v1.1\n * (canonical `.noir/` store + ignore files). {@link buildHostArtifacts}\n * emits AGENTS.md ONLY for agents-md/cursor/opencode (claude/gemini use their\n * own CLAUDE.md/GEMINI.md — emitting AGENTS.md too would double-import .noir/).\n *\n * Mode-tagging rationale per artifact:\n * - `project.id` → skipIfExists. First init writes a fresh id; re-init MUST\n * NOT overwrite — that would orphan the indexed store DB named after it.\n * - `config.yml` → skipIfExists. User-owned; the seed is written once.\n * (The seed renders `host: {{host}}` so a `--host gemini` init persists the\n * chosen host for `noir sync` to read back.)\n * - `NOIR.md` → managedBlock (BRIEF_BLOCK). Auto-brief is co-owned.\n * - `RULES.md` → skipIfExists. User-owned working-contract seed.\n * - `README.md` → managedBlock (README_BLOCK). The `.noir/` runtime map is\n * regenerated as the layout grows; user notes outside the markers survive.\n * - ignore files → managedBlock (IGNORE_BLOCK). Matches syncIgnores.\n * - host entries → SEE {@link buildHostArtifacts} (regenerate / managedBlock).\n */\nexport function buildManifest(ctx: BuildManifestContext): ManifestEntry[] {\n return [...hostAgnosticEntries(ctx), ...buildHostArtifacts(resolveAdapter(ctx.host), ctx)];\n}\n\n/** The host-agnostic canonical-store + ignore entries — identical bytes for\n * every host. Split out so {@link buildHostArtifacts} can be unit-tested in\n * isolation and so the doctor's host-artifacts check can reason about the\n * host-specific half alone. */\nfunction hostAgnosticEntries(ctx: BuildManifestContext): ManifestEntry[] {\n const entries: ManifestEntry[] = [\n {\n path: P.projectId,\n mode: 'skipIfExists',\n content: `${ctx.projectId}\\n`,\n description: 'canonical project id (store DB is named after it)',\n },\n {\n path: P.config,\n mode: 'skipIfExists',\n template: 'config.yml.tmpl',\n description: 'user config seed (host + mode)',\n },\n {\n // Refreshable: the variable set Noir documents grows between releases,\n // and this file is pure documentation the user is never asked to edit —\n // so an upgrade may replace an untouched older copy of it.\n path: P.envExample,\n mode: 'skipIfExists',\n template: 'env.example.tmpl',\n refreshIfStale: true,\n description: '.noir/.env.example committable documentation (never loaded)',\n },\n {\n // init creates the REAL env file, not just the example. The body is\n // all-comment, so the file parses to an EMPTY overlay and creating it\n // changes no behaviour — it exists so the user never has to copy the\n // example by hand. `fileMode: 0o600` because the file holds tokens the\n // moment the user edits it. The mode is applied when the file is created;\n // a copy that predates this contract (or that an editor saved by rename)\n // has its mode re-asserted on every init/sync/create run — see `ensureOwnerOnly`.\n path: P.env,\n mode: 'skipIfExists',\n template: 'config.env.tmpl',\n fileMode: 0o600,\n description: 'project-scoped env file (gitignored, 0600)',\n },\n {\n path: P.noirMd,\n mode: 'managedBlock',\n block: BRIEF_BLOCK,\n template: 'noir.md.tmpl',\n description: 'NOIR.md auto-brief (project id pointer)',\n },\n {\n // Refreshable: shipped as a starting contract the user is INVITED to\n // rewrite, so only the never-edited copy may be swapped for a newer one.\n path: P.rulesMd,\n mode: 'skipIfExists',\n template: 'rules-seed.md.tmpl',\n refreshIfStale: true,\n description: 'AI working-rules seed',\n },\n {\n // The `.noir/` runtime map — what init just wrote,\n // what appears later (and which command creates it), and where to go\n // next. Host-agnostic: it describes the canonical store, which every\n // host shares. Co-owned (README_BLOCK) so user notes survive while the\n // map stays current through `noir sync` / `init --upgrade`.\n //\n // Rendered here rather than left to `renderEntry` because one row of the\n // map depends on the project: the working-rules row is dropped when\n // `rules.enabled` is off (see {@link readmeMapContent}).\n path: P.readme,\n mode: 'managedBlock',\n block: README_BLOCK,\n content: readmeMapContent(ctx),\n description: '.noir/ runtime map (what exists now / what appears later)',\n },\n\n // --- ignore files (host-agnostic; co-owned via IGNORE_BLOCK) ------------\n {\n path: '.gitignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'gitignore.tmpl',\n description: '.gitignore noir managed block',\n },\n {\n path: '.dockerignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'dockerignore.tmpl',\n description: '.dockerignore noir managed block',\n },\n {\n path: '.npmignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'npmignore.tmpl',\n description: '.npmignore noir managed block',\n },\n {\n path: '.prettierignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'prettierignore.tmpl',\n description: '.prettierignore noir managed block',\n },\n ];\n // Stack-aware ignore emission. Only emit the ignore\n // files relevant to the detected stack. An unknown/empty stack (no language\n // markers, no package manager — e.g. a blank dir or undetectable project) ⇒\n // emit all four (backward-compatible with the pre-fix behavior).\n const isEmpty = (ctx.stack?.languages?.length ?? 0) === 0 && !ctx.stack?.packageManager;\n const isJs =\n isEmpty ||\n (ctx.stack?.languages?.some((l) => l === 'typescript' || l === 'javascript') ?? false) ||\n ['npm', 'pnpm', 'yarn'].includes(ctx.stack?.packageManager ?? '');\n const hasDocker =\n isEmpty ||\n existsSync(join(ctx.root, 'Dockerfile')) ||\n existsSync(join(ctx.root, 'docker-compose.yml')) ||\n existsSync(join(ctx.root, 'compose.yaml'));\n // The working-rules seed follows the project's own `rules.enabled` switch.\n const rulesEnabled = ctx.rulesEnabled !== false;\n return entries.filter((e) => {\n if (e.path === P.rulesMd) return rulesEnabled;\n if (e.path === '.npmignore' || e.path === '.prettierignore') return isJs;\n if (e.path === '.dockerignore') return hasDocker;\n return true;\n });\n}\n\n// Host-specific artifact generation. One entry point: `buildHostArtifacts`.\n\n/** Context shape passed to {@link buildHostArtifacts}. A strict subset of\n * {@link BuildManifestContext} (no `projectId`/`host` — the adapter IS the\n * resolved host, and host artifacts never need the project id). Exported\n * separately so callers + tests can name the narrower contract. */\nexport interface BuildHostArtifactsContext {\n root: string;\n transport: 'stdio' | 'streamable-http';\n url?: string;\n /** The `command` for the stdio MCP server entry (absolute native shim when a\n * native install is detected, else `'noir'`). Passed through to\n * `adapter.emitMcpConfig`. */\n command: string;\n /** The project's `rules.enabled` switch. False drops the rules `@`-import a\n * host would otherwise emit (CLAUDE.md's rules block, GEMINI.md's rules\n * block, AGENTS.md's rules import) so no host file points at a\n * `.noir/rules/RULES.md` that the switch withheld. Undefined means enabled —\n * byte-identical to every build that predates the switch. */\n rulesEnabled?: boolean;\n}\n\n/**\n * Materialize the host-specific manifest entries from a resolved adapter.\n * SINGLE entry point — no scattered `if (host === '…')` conditionals in the\n * orchestrator. Returns entries in emission order:\n *\n * 1. **AGENTS.md** (universal baseline) — `regenerate` at\n * `adapter.agentsMdPath(ctx)` (default `<root>/AGENTS.md`), content from\n * the shared `emitAgentsMd(ctx)` helper. Emitted ONLY for hosts whose\n * `emitContext` IS the AGENTS.md content (agents-md, cursor, opencode) —\n * for them AGENTS.md is the SINGLE native context surface AND carries the\n * Noir working rules via its `@.noir/rules/RULES.md` import. claude and\n * gemini have their OWN native context file (CLAUDE.md / GEMINI.md) that\n * `@`-imports the canonical `.noir/` sources; emitting AGENTS.md too\n * would IMPORT THOSE FILES TWICE into the host's context (2× tokens +\n * drift risk), so for those two hosts AGENTS.md is SKIPPED. (Claude Code\n * still discovers AGENTS.md at the repo root when present — users who\n * want the universal file can drop one in by hand; Noir's auto-emission\n * stays single-source per host.)\n * 2. **Host-native context file** — emitted ONLY for hosts whose `emitContext`\n * is NOT the AGENTS.md content (i.e. the host has its OWN context file\n * with a distinct syntax). Concretely: claude → `CLAUDE.md` (CONTEXT +\n * RULES managed blocks, byte-identical to v1.1 via templates); gemini →\n * `GEMINI.md` (CONTEXT + RULES managed blocks with Gemini's bare `@`\n * import syntax). For `agents-md`/`cursor`/`opencode` the context IS the\n * AGENTS.md (already emitted in step 1) → SKIP to avoid a duplicate.\n * Rules live INSIDE the host's context file: claude's in CLAUDE.md,\n * gemini's in GEMINI.md, agents-md/cursor/opencode's in AGENTS.md — NO\n * host emits a separate rules file. (The prior cursor\n * `.cursor/rules/noir-contract.mdc` host-rules pointer was REMOVED: it\n * collided with the cursor flat-skill prune of `noir-*.mdc` under\n * `.cursor/rules/`, and cursor's rules are already delivered via\n * AGENTS.md's `@.noir/rules/RULES.md` import.)\n *\n * That rules import/block is dropped for every host when\n * `ctx.rulesEnabled` is false: the switch withholds the seed, so pointing\n * a host at it would resolve to nothing. The context import is unaffected\n * — the switch gates the rules, not the brief. A host file that already\n * carries the rules region from when the switch was on keeps it: dropping\n * the entry stops the emission, and nothing here rewrites a region out of\n * an existing file.\n * 3. **Host MCP config** — `regenerate` at `adapter.mcpConfigPath(ctx)`\n * (default `<root>/.mcp.json` for claude), content from\n * `adapter.emitMcpConfig(ctx, {transport,url})`. Claude KEEPS the template\n * path (byte-identical parity with v1.1 + the .mcp.json parity test that\n * compares against `claudeAdapter.emitMcpConfig`) for a repo that has not\n * joined a workspace; a joined repo — one carrying a `.noir/workspace.json`\n * marker — gets the workspace entry instead, on every host, whatever\n * transport the run asked for (see the entry's own comment below).\n *\n * Skills are OUT OF SCOPE here — the cli composes `emitSkillsToDir` with\n * `adapter.skillsDir` + the host's `CompileTarget` (claude → `.claude/skills/`\n * as SKILL.md; cursor → `.cursor/rules/<skill>.mdc` FLAT; gemini/\n * agents-md/opencode have no skill dir → skip).\n */\nexport function buildHostArtifacts(\n adapter: HostAdapter,\n ctx: BuildHostArtifactsContext,\n): ManifestEntry[] {\n const rulesEnabled = ctx.rulesEnabled !== false;\n const ectx: EmitContext = { root: ctx.root, rulesEnabled };\n const host = adapter.id;\n const entries: ManifestEntry[] = [];\n\n // 1. AGENTS.md — emitted for hosts whose emitContext IS the AGENTS.md content\n // (agents-md, cursor, opencode). SKIPPED for claude/gemini: their native\n // CLAUDE.md / GEMINI.md already @-import the canonical .noir/ sources, so\n // a root AGENTS.md would double-import (2× context tokens, drift risk).\n // This also restores the claude default `noir init` to byte-identity with\n // v1.1 (the prior additive AGENTS.md delta is removed).\n const emitsAgentsMd = host === 'agents-md' || host === 'cursor' || host === 'opencode';\n if (emitsAgentsMd) {\n entries.push({\n path: hostRel(adapter.agentsMdPath?.(ectx) ?? join(ctx.root, AGENTS_MD_FILENAME), ctx.root),\n mode: 'regenerate',\n host,\n content: emitAgentsMd(ectx),\n description: `AGENTS.md (${host}'s native context surface; @-imports .noir/)`,\n });\n }\n\n // 2. Host-native context file (when distinct from AGENTS.md) + folded rules.\n switch (host) {\n case 'claude':\n // CLAUDE.md keeps template-based bodies — byte-identical to v1.1 (the\n // scaffold.test.ts parity gates compare against claudeAdapter.emitContext\n // + emitRules; the templates render to the same body bytes).\n entries.push({\n path: 'CLAUDE.md',\n mode: 'managedBlock',\n host,\n block: CONTEXT_BLOCK,\n template: 'claude-context-block.md.tmpl',\n description: 'CLAUDE.md context @import block',\n });\n // The rules block only exists when the project HAS a rules file. With the\n // switch off the block is not emitted — a fresh project gets no dangling\n // `@import`, and a project that already carries the block from when the\n // switch was on keeps it (off stops the emission; it never cuts a region\n // out of a user's file).\n if (rulesEnabled) {\n entries.push({\n path: 'CLAUDE.md',\n mode: 'managedBlock',\n host,\n block: RULES_BLOCK,\n template: 'claude-rules-block.md.tmpl',\n description: 'CLAUDE.md rules @import block',\n });\n }\n break;\n case 'gemini':\n // GEMINI.md carries CONTEXT_BLOCK + RULES_BLOCK with Gemini's bare\n // `@file` import syntax (no `@import` keyword, no quotes — distinct from\n // Claude's form). Emitted as TWO managed regions so user content outside\n // the markers survives `noir sync` (same write path as CLAUDE.md — the\n // multi-region atomic `managedBlocks` writer).\n entries.push({\n path: 'GEMINI.md',\n mode: 'managedBlock',\n host,\n block: CONTEXT_BLOCK,\n content: '@.noir/NOIR.md',\n description: 'GEMINI.md context @-import block',\n });\n // Same rule as claude's: no rules file, no rules import.\n if (rulesEnabled) {\n entries.push({\n path: 'GEMINI.md',\n mode: 'managedBlock',\n host,\n block: RULES_BLOCK,\n content: '@.noir/rules/RULES.md',\n description: 'GEMINI.md rules @-import block',\n });\n }\n break;\n case 'agents-md':\n case 'cursor':\n case 'opencode':\n // emitContext IS the AGENTS.md content (already emitted in step 1) →\n // no separate context file. Rules are carried by AGENTS.md's\n // `@.noir/rules/RULES.md` import (agents-md/cursor/opencode share that\n // universal surface — NO host emits a separate rules file).\n break;\n }\n\n // 3. Host MCP config. Claude keeps the template path for a repo that has not\n // joined a workspace (byte-identical parity gate); every other case goes\n // through the adapter, so the entry is rendered by the same code the host's\n // own emitter uses.\n //\n // A repo that has JOINED a workspace gets the workspace entry, and that\n // decision belongs to the marker — not to the transport this run asked for.\n // Membership is what decides how the host reaches the daemon (through the\n // stdio bridge, which reads the token itself), so re-scaffolding must never\n // drop a repo out of a workspace the marker still says it belongs to, nor\n // write an address into its config. Reading the marker HERE, where the entry\n // is chosen, means no caller can bypass it — `sync`, `init --force`,\n // `create --force` and the doctor's expectation check all follow membership\n // for free.\n const mcpAbs = mcpConfigPathForAdapter(adapter, ctx.root);\n const mcpRel = hostRel(mcpAbs, ctx.root);\n const workspace = readWorkspaceMarker(ctx.root);\n if (host === 'claude' && workspace === null) {\n const mcpTemplate =\n ctx.transport === 'streamable-http' ? 'mcp.http.json.tmpl' : 'mcp.stdio.json.tmpl';\n entries.push({\n path: mcpRel,\n mode: 'regenerate',\n host,\n template: mcpTemplate,\n description: 'host MCP server pointer',\n });\n } else {\n const mcpContent = `${adapter.emitMcpConfig(\n ectx,\n workspace !== null\n ? // Joined: stdio + the workspace name, whatever transport was asked for.\n { transport: 'stdio', command: ctx.command, workspace }\n : {\n transport: ctx.transport,\n command: ctx.command,\n ...(ctx.url !== undefined ? { url: ctx.url } : {}),\n },\n )}\\n`;\n entries.push({\n path: mcpRel,\n mode: 'regenerate',\n host,\n content: mcpContent,\n description:\n workspace !== null\n ? `${host} MCP server pointer (workspace \"${workspace}\")`\n : `${host} MCP server pointer`,\n });\n }\n\n // 4. Lifecycle hooks — per host, via the adapter seam. The shared `noir hook`\n // dispatcher carries all policy (brief injection, deny-list, Stop gate);\n // each adapter returns only the thin per-host config that invokes it with a\n // `--host` dialect. Hosts with no hook surface (agents-md, opencode) return\n // none. The `.noir/router.md` + `.noir/rules/anti-slop.md` sources feed the\n // hook's compiled brief, so they are emitted for any host that emits hooks.\n const hookEmissions = adapter.emitHooks?.({ command: ctx.command }) ?? null;\n if (hookEmissions !== null) {\n entries.push({\n path: '.noir/router.md',\n mode: 'managedBlock',\n host,\n block: CONTEXT_BLOCK,\n template: 'router.md.tmpl',\n description: 'skill router contract (co-owned managed block)',\n });\n if (rulesEnabled) {\n entries.push({\n path: '.noir/rules/anti-slop.md',\n mode: 'regenerate',\n host,\n template: 'anti-slop.md.tmpl',\n description: 'always-on anti-slop + laziness ruleset (hook-injected, not @imported)',\n });\n }\n for (const emission of hookEmissions) {\n const json = JSON.stringify(emission.content, null, 2);\n entries.push({\n path: emission.path,\n mode: emission.mode,\n host,\n content: emission.mode === 'regenerate' ? `${json}\\n` : json,\n ...(emission.dedupSubstring !== undefined\n ? { dedupSubstring: emission.dedupSubstring }\n : {}),\n description: emission.description,\n });\n }\n }\n\n return entries;\n}\n\n/** Convert an absolute path under `root` to a repo-relative POSIX string (the\n * manifest's path shape). Throws if `abs` is NOT under `root` so a future\n * adapter that returns a stray path fails loudly instead of producing a\n * malformed manifest entry. */\nfunction hostRel(abs: string, root: string): string {\n const rel = relative(root, abs);\n if (rel.length === 0 || rel.startsWith('..') || rel.startsWith('/')) {\n throw new Error(`buildHostArtifacts: path '${abs}' is not under root '${root}'`);\n }\n // Normalize any platform separators to POSIX (manifest paths are POSIX).\n return rel.replace(/\\\\/g, '/');\n}\n","/**\n * Hand-rolled `{{var}}` interpolation. No mustache/handlebars dependency —\n * Noir's markdown/yaml/json templates are simple enough that a tokenizer +\n * map lookup is sufficient and keeps `@noir-ai/create` dependency-light.\n *\n * Semantics (documented):\n * - Tokens are `{{ name }}` / `{{name}}` — any amount of ASCII whitespace\n * inside the braces is permitted. The name is trimmed.\n * - Known vars (value is a string) are substituted verbatim. NO escaping is\n * applied — callers must pre-escape for the target format (JSON/YAML/MD).\n * This is intentional: the engine renders into multiple formats and a\n * single universal escaper would be wrong for at least one of them.\n * - Unknown vars (not in `vars`, or value `undefined`) → the original token\n * is LEFT IN PLACE. Rationale: drift between template and ctx becomes\n * visible (an unrendered `{{projectId}}` in CLAUDE.md is immediately\n * obvious), rather than silently swallowed to empty. The spec called this\n * out as the preferred failure mode.\n * - Non-string var values are stringified via `String(value)` so a stray\n * number/boolean still interpolates rather than printing `[object Object]`.\n * - A lone `{{` with no closing `}}` is left untouched (not a token).\n */\n\nconst TOKEN = /\\{\\{\\s*([^}{}]+?)\\s*\\}\\}/g;\n\nexport function render(template: string, vars: Record<string, unknown>): string {\n return template.replace(TOKEN, (whole, name: string) => {\n if (!Object.hasOwn(vars, name)) return whole; // unknown → leave token\n const v = vars[name];\n if (v === undefined || v === null) return whole; // treat as unknown → leave token\n return String(v);\n });\n}\n","import { readFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Template loader. Templates ship at `<pkg>/templates/` (see `files` in\n * package.json) and are read at runtime so non-code changes (copy tweaks, new\n * pointers) don't require a rebuild.\n *\n * Path resolution must work identically in three layouts:\n * - source (vitest, tsx): this file at `packages/create/src/template-loader.ts`\n * → `../templates/<name>`.\n * - built (tsup): this module is inlined into the package's single bundle at\n * `packages/create/dist/index.js` → `../templates/<name>`. `dist/` and\n * `src/` are both direct children of the package root, so the relative\n * offset is unchanged and `import.meta.url` — which points at the bundle,\n * not at a `template-loader.js` — still resolves correctly.\n * - packed (npm tarball): `templates/` is included per `files`, dist/ layout\n * preserved → same as built.\n *\n * `NOIR_TEMPLATES_DIR` overrides everything (used by tests + downstream packs\n * that want to substitute their own template set without forking the engine).\n */\n\nconst DEFAULT_TEMPLATES_DIR = resolveTemplatesDir();\n\nfunction resolveTemplatesDir(): string {\n const override = process.env.NOIR_TEMPLATES_DIR;\n if (override && override.length > 0) return resolve(override);\n // `import.meta.url` is this module's URL. Going up one level reaches the\n // package root in both source and built layouts (see file header).\n const here = dirname(fileURLToPath(import.meta.url));\n return join(here, '..', 'templates');\n}\n\n/** Read a template's raw text by name (e.g. `noir.md.tmpl`). Throws on missing\n * — an unknown template is a manifest bug and should fail loudly, not render\n * an empty string silently. */\nexport function loadTemplate(name: string): string {\n return readFileSync(join(DEFAULT_TEMPLATES_DIR, name), 'utf8');\n}\n\n/** Resolve a template name to its absolute path (for diagnostics + tests). */\nexport function templatesDir(): string {\n return DEFAULT_TEMPLATES_DIR;\n}\n","// Three-way merge for managed-block regions.\n//\n// When a user hand-edits the INSIDE of a `<!-- noir:* -->` managed region and a\n// later `noir init`/`sync` updates the template, this merges (base/ours/theirs)\n// instead of strip-replacing (which would silently clobber the user's edit).\n// Line-level diff3: disjoint line changes merge cleanly; overlapping changes\n// surface as inline `<<<<<<< / ======= / >>>>>>>` markers for manual resolution.\n// Never silently drops either side.\n\nexport interface MergeResult {\n /** The merged text. When `conflict` is true, contains inline markers. */\n merged: string;\n /** True when ours and theirs diverged in the same region (markers emitted). */\n conflict: boolean;\n}\n\n/** Whole-string short-circuits for the trivial cases (also covers empty). */\nfunction trivial(base: string, ours: string, theirs: string): MergeResult | null {\n if (ours === base) return { merged: theirs, conflict: false };\n if (theirs === base) return { merged: ours, conflict: false };\n if (ours === theirs) return { merged: ours, conflict: false };\n return null;\n}\n\n/** Read index `i` of `arr` as a number (0 when out of range) — sidesteps\n * noUncheckedIndexedAccess without non-null assertions. */\nfunction numAt(arr: readonly number[], i: number): number {\n return arr[i] ?? 0;\n}\n\n/** Longest-common-subsequence match pairs between two line arrays. */\nexport function lcsMatch(a: readonly string[], b: readonly string[]): Array<[number, number]> {\n const n = a.length;\n const m = b.length;\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0));\n for (let i = n - 1; i >= 0; i--) {\n const dpi = dp[i] ?? [];\n const dpi1 = dp[i + 1] ?? [];\n const ai = a[i] ?? '';\n for (let j = m - 1; j >= 0; j--) {\n const bj = b[j] ?? '';\n dpi[j] = ai === bj ? numAt(dpi1, j + 1) + 1 : Math.max(numAt(dpi1, j), numAt(dpi, j + 1));\n }\n }\n const out: Array<[number, number]> = [];\n let i = 0;\n let j = 0;\n while (i < n && j < m) {\n if ((a[i] ?? '') === (b[j] ?? '')) {\n out.push([i, j]);\n i++;\n j++;\n } else if (numAt(dp[i + 1] ?? [], j) >= numAt(dp[i] ?? [], j + 1)) {\n i++;\n } else {\n j++;\n }\n }\n return out;\n}\n\n/**\n * Unified line diff for the conflict resolver's stderr preview. Returns a\n * sequence of {type:'eq'|'del'|'add', line} records, LCS-based, equivalent in\n * shape to `git diff --no-color`. Pure (no IO) so it unit-tests cleanly and the\n * resolver can render it through {@link packages/cli/src/theme.ts} (`+` green,\n * `-` red, context dim). Reuses the SAME LCS machinery {@link mergeThreeWay}\n * does so there is exactly one line-diff algorithm in the package.\n *\n * `context` (default 3) — number of unchanged lines to keep around each hunk\n * (git-style). 0 → every non-equal line stands alone; large → whole-file blob.\n * The resolver uses the default; tests pin to a specific shape.\n */\nexport interface DiffLine {\n type: 'eq' | 'del' | 'add';\n line: string;\n}\nexport function lineDiff(base: string, head: string, context = 3): DiffLine[] {\n const A = base.split('\\n');\n const B = head.split('\\n');\n const pairs = lcsMatch(A, B);\n const inA = new Set<number>(pairs.map((p) => p[0]));\n const inB = new Set<number>(pairs.map((p) => p[1]));\n // Walk both sequences in lockstep, emitting del/eq/add per the LCS anchors.\n const out: DiffLine[] = [];\n let i = 0;\n let j = 0;\n // Build a baseIdx→headIdx map so we can advance along matched pairs.\n const bToH = new Map<number, number>();\n for (const [bi, hi] of pairs) bToH.set(bi, hi);\n while (i < A.length || j < B.length) {\n const matched = bToH.get(i);\n if (matched !== undefined && matched === j) {\n out.push({ type: 'eq', line: A[i] ?? '' });\n i++;\n j++;\n } else if (matched !== undefined) {\n // Head is behind — emit adds up to the matched head index.\n out.push({ type: 'add', line: B[j] ?? '' });\n j++;\n } else if (i < A.length && !inA.has(i)) {\n // A-only line (deleted).\n out.push({ type: 'del', line: A[i] ?? '' });\n i++;\n } else if (j < B.length && !inB.has(j)) {\n // B-only line (added).\n out.push({ type: 'add', line: B[j] ?? '' });\n j++;\n } else {\n // Both at matched anchors but not paired — advance the smaller index.\n if (i < A.length) i++;\n else j++;\n }\n }\n return context > 0 ? collapseContext(out, context) : out;\n}\n\n/** Collapse runs of >2*`context` unchanged lines into a single `...` hunk\n * separator so the preview stays readable on long files. Pure trim — never\n * drops a del/add. */\nfunction collapseContext(lines: readonly DiffLine[], context: number): DiffLine[] {\n const out: DiffLine[] = [];\n const keep = new Array<boolean>(lines.length).fill(false);\n // Mark `context` lines around each del/add.\n for (let i = 0; i < lines.length; i++) {\n const dl = lines[i];\n if (dl && dl.type !== 'eq') {\n for (let k = Math.max(0, i - context); k <= Math.min(lines.length - 1, i + context); k++) {\n keep[k] = true;\n }\n }\n }\n let inRun = false;\n for (let i = 0; i < lines.length; i++) {\n const dl = lines[i];\n if (!dl) continue;\n if (keep[i]) {\n out.push(dl);\n inRun = false;\n } else {\n if (!inRun) {\n out.push({ type: 'eq', line: '…' });\n inRun = true;\n }\n }\n }\n return out;\n}\n\n/**\n * zdiff3-style conflict markers for an unresolved overlap. Shape:\n * `<<<<<<< ours`\n * <ours lines>\n * `||||||| base`\n * <base lines>\n * `=======`\n * <theirs lines>\n * `>>>>>>> theirs`\n *\n * `diff3(zdiff3)` writes the SAME shape — included here so the engine + clack\n * resolver have a single source of truth for marker layout. Returns the marked\n * region body (no outer delimiters); the caller wraps it with surrounding\n * context.\n */\nexport function zdiff3Region(\n ours: readonly string[],\n base: readonly string[],\n theirs: readonly string[],\n): string[] {\n return ['<<<<<<< ours', ...ours, '||||||| base', ...base, '=======', ...theirs, '>>>>>>> theirs'];\n}\n\nconst eq = (a: readonly string[], b: readonly string[]): boolean =>\n a.length === b.length && a.every((v, k) => v === b[k]);\n\n/**\n * Three-way merge of `ours` (the user's current region) against `theirs` (the\n * new template) with `base` (the last-emitted ancestor). Line-level diff3:\n * changes in disjoint line ranges merge; overlapping changes become inline\n * conflict markers. Default marker shape is the minimal diff3 form\n * (`<<<<<<< ours` / `=======` / `>>>>>>> theirs`) — byte-identical to v1.2 so\n * {@link mergeManagedRegion} stays regression-anchored. Pass `'zdiff3'` to\n * include the base section (`||||||| base`) — used by the conflict resolver's\n * 6th \"merge (with conflict markers)\" option. Pure + deterministic\n * (no IO) so it's unit-testable.\n */\nexport function mergeThreeWay(\n base: string,\n ours: string,\n theirs: string,\n style: 'minimal' | 'zdiff3' = 'minimal',\n): MergeResult {\n const t = trivial(base, ours, theirs);\n if (t) return t;\n\n const B = base.split('\\n');\n const O = ours.split('\\n');\n const T = theirs.split('\\n');\n const oMap = new Map<number, number>(); // baseIdx → oursIdx\n for (const [bi, oi] of lcsMatch(B, O)) oMap.set(bi, oi);\n const tMap = new Map<number, number>(); // baseIdx → theirsIdx\n for (const [bi, ti] of lcsMatch(B, T)) tMap.set(bi, ti);\n const isAnchor = (bi: number): boolean => oMap.has(bi) && tMap.has(bi);\n\n const anchors: number[] = [];\n for (let b = 0; b < B.length; b++) if (isAnchor(b)) anchors.push(b);\n const pts: number[] = [-1, ...anchors, B.length];\n\n const out: string[] = [];\n let conflict = false;\n for (let k = 0; k < pts.length - 1; k++) {\n const aBase = pts[k];\n const bBase = pts[k + 1];\n if (aBase === undefined || bBase === undefined) break;\n const baseSeg = B.slice(aBase + 1, bBase);\n const oStart = aBase >= 0 ? (oMap.get(aBase) ?? -1) + 1 : 0;\n const oEnd = bBase < B.length ? (oMap.get(bBase) ?? 0) : O.length;\n const tStart = aBase >= 0 ? (tMap.get(aBase) ?? -1) + 1 : 0;\n const tEnd = bBase < B.length ? (tMap.get(bBase) ?? 0) : T.length;\n const oSeg = O.slice(oStart, oEnd);\n const tSeg = T.slice(tStart, tEnd);\n if (eq(oSeg, baseSeg) && eq(tSeg, baseSeg)) out.push(...baseSeg);\n else if (eq(oSeg, baseSeg)) out.push(...tSeg);\n else if (eq(tSeg, baseSeg)) out.push(...oSeg);\n else if (eq(oSeg, tSeg)) out.push(...oSeg);\n else {\n conflict = true;\n if (style === 'zdiff3') {\n out.push(...zdiff3Region(oSeg, baseSeg, tSeg));\n } else {\n out.push('<<<<<<< ours', ...oSeg, '=======', ...tSeg, '>>>>>>> theirs');\n }\n }\n if (bBase < B.length) out.push(B[bBase] ?? '');\n }\n return { merged: out.join('\\n'), conflict };\n}\n","import { existsSync, readdirSync, readFileSync, rmdirSync, rmSync, writeFileSync } from 'node:fs';\nimport { join, relative, sep } from 'node:path';\nimport { type HostId, mcpConfigPathFor, noirStdioArgs, TRANSPORT_KEYS } from '@noir-ai/adapters';\nimport {\n atomicWriteFile,\n loadProjectInfo,\n readProjectConfig,\n readWorkspaceMarker,\n resolveNoirCommand,\n} from '@noir-ai/core';\nimport { render } from '../template.js';\nimport { isStaleSeed, type SeedKind } from '../template-history.js';\nimport { loadTemplate } from '../template-loader.js';\nimport { refreshSeed } from '../writers.js';\nimport type { MigrationContext, MigrationResult, MigrationScript } from './types.js';\n\nexport { runMigrations } from './runner.js';\nexport type { MigrationContext, MigrationResult, MigrationScript } from './types.js';\n\n/**\n * Migration registry — the linear history of scaffold-version upgrades.\n *\n * `CURRENT_SCAFFOLD_VERSION` is `1.5.0`; a project stamped at an older version\n * migrates through every entry whose window covers it. Six entries ship today:\n * the synthetic `1.0.0 → 1.0.0` runner-proof, the first REAL migration\n * `1.0.0 → 1.1.0` (which performs the transformation `skipIfExists` cannot),\n * `1.1.0 → 1.2.0`, which refreshes the doc-only seed whose text changed in\n * that release and carries the unchanged RULES.md seed forward for parity,\n * `1.2.0 → 1.3.0`, which repairs the stale workspace pointer a repo that joined\n * a workspace under the older flow still carries, `1.3.0 → 1.4.0`, which moves\n * the SessionStart hook command onto `noir hook`, and `1.4.0 → 1.5.0`, which\n * appends the enforcement events to the claude hook and backfills the gemini\n * hook.\n *\n * Convention:\n * - `from`/`to` are bare `x.y.z` (no `v` prefix, no pre-release); the runner\n * compares them numerically.\n * - Every `run` MUST be idempotent and non-throwing (capture failures into\n * `result.conflicts`). See {@link types.ts}.\n * - Conflict resolution writes git-style markers inline — see\n * {@link applyWithConflict} for the canonical helper.\n * - Paths in `changed`/`conflicts` are repo-relative and POSIX-separated, like\n * every other path the engine reports.\n */\n\n/** Synthetic 1.0.0 → 1.0.0 migration. Proves the runner wires up; also\n * demonstrates the conflict-marker path with a guarded, idempotent touch on\n * `.noir/scaffold-version` only when explicitly asked via\n * `NOIR_TEST_FORCE_CONFLICT`. It STAYS now that a real migration has landed:\n * the real entry is a one-shot transformation of user content, while this one\n * is the registry's permanent, side-effect-free smoke test for the runner and\n * its conflict-marker plumbing. */\nconst synthetic: MigrationScript = {\n from: '1.0.0',\n to: '1.0.0',\n description: 'no-op synthetic migration (runner smoke test)',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n // The only \"real\" thing it does: when the env var is set, write a conflict\n // marker into `.noir/scaffold-version` so the runner's conflict plumbing is\n // exercised by tests. In normal operation this branch never fires and the\n // script is a true no-op.\n if (process.env.NOIR_TEST_FORCE_CONFLICT === '1' && !ctx.dryRun) {\n const file = join(ctx.root, '.noir', 'scaffold-version');\n if (existsSync(file)) {\n const prev = readFileSync(file, 'utf8');\n const merged = applyInlineConflict(prev, 'noir-scaffold=1.0.0\\n', 'ours', 'theirs');\n writeFileSync(file, merged, 'utf8');\n result.conflicts.push('.noir/scaffold-version');\n }\n }\n result.notes.push('synthetic 1.0.0→1.0.0 migration ran');\n return result;\n },\n};\n\n// --- 1.0.0 → 1.1.0: the first real migration -------------------------------\n\n/** Marker line that guards the `.noir/.env` pointer block in `config.yml`.\n * Its presence means the block is already on disk — emitted by\n * `config.yml.tmpl` for a new project, or appended by an earlier run of this\n * migration — so the append is skipped. That guard is what makes the\n * migration idempotent.\n *\n * `config.yml.tmpl` carries the same literal (a template cannot import a\n * TypeScript constant); `migration-1_0_0.test.ts` asserts the two agree so the\n * guard cannot drift out from under the template. */\nexport const ENV_POINTER_MARKER = '# noir:env-pointer';\n\n/** The comment block appended to an EXISTING `.noir/config.yml`. It states the\n * two things a reader of that file needs: it is committable, and secrets go in\n * `.noir/.env`. Deliberately comment-only — appending YAML keys would change\n * the parsed config, which a migration must never do. */\nconst ENV_POINTER_BLOCK = [\n ENV_POINTER_MARKER,\n '# This file is safe to commit: it holds no secrets.',\n '# Project-scoped values and secrets belong in .noir/.env (gitignored, mode',\n '# 0600). .noir/.env.example documents the full variable set; `noir env`',\n '# reports which source wins for each key.',\n '',\n].join('\\n');\n\n/** Append `block` to `prev` with exactly one blank line between the user's\n * content and the block (no blank line at all when the file is empty). */\nfunction appendSeparated(prev: string, block: string): string {\n if (prev.length === 0) return block;\n if (prev.endsWith('\\n\\n')) return `${prev}${block}`;\n return prev.endsWith('\\n') ? `${prev}\\n${block}` : `${prev}\\n\\n${block}`;\n}\n\n/** `1.0.0 → 1.1.0`: point an existing `.noir/config.yml` at `.noir/.env`.\n *\n * Scoped to the one transformation `skipIfExists` structurally cannot perform.\n * `config.yml` is a user-owned seed written once at init, so the manifest\n * never opens an existing one — a project initialized before this migration\n * existed would therefore never learn where secrets belong. Everything else\n * this version adds is *creation* (a `skipIfExists` backfill) or *managed\n * blocks* (`managedBlock` re-emission); the migration duplicates neither. */\nconst envPointer: MigrationScript = {\n from: '1.0.0',\n to: '1.1.0',\n description: 'point .noir/config.yml at .noir/.env (the secrets home)',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const rel = '.noir/config.yml';\n const abs = join(ctx.root, '.noir', 'config.yml');\n\n // Absent: nothing to transform, and creating it is NOT this migration's\n // job — an absent `config.yml` is backfilled from the template (which\n // already carries the pointer) by the `skipIfExists` emit phase.\n if (!existsSync(abs)) {\n result.notes.push(`${rel}: absent — the template seed carries the pointer`);\n return result;\n }\n\n let prev: string;\n try {\n prev = readFileSync(abs, 'utf8');\n } catch (err) {\n // Unreadable (a directory, a permission wall). Non-throwing is the\n // registry-wide contract: record and let the caller decide.\n result.conflicts.push(rel);\n result.notes.push(`${rel}: unreadable (${errorMessage(err)}) — left untouched`);\n return result;\n }\n\n // The idempotency guard: a second run (or a file the template already\n // stamped) sees the marker and writes nothing at all — byte-level no-op.\n if (prev.includes(ENV_POINTER_MARKER)) {\n result.notes.push(`${rel}: already carries ${ENV_POINTER_MARKER}`);\n return result;\n }\n\n if (ctx.dryRun) {\n result.changed.push(rel);\n result.notes.push(`${rel}: would append the .noir/.env pointer`);\n return result;\n }\n\n try {\n writeFileSync(abs, appendSeparated(prev, ENV_POINTER_BLOCK), 'utf8');\n } catch (err) {\n result.conflicts.push(rel);\n result.notes.push(`${rel}: write failed (${errorMessage(err)}) — left untouched`);\n return result;\n }\n result.changed.push(rel);\n result.notes.push(`${rel}: appended the .noir/.env pointer`);\n return result;\n },\n};\n\n// --- 1.1.0 → 1.2.0: refresh the doc-only seeds ------------------------------\n\n/** The doc-only seeds this migration walks. Each maps a repo-relative path to\n * the seed kind it is recorded under and the template that renders its current\n * bytes. Only these two are tracked: they are written once at init and never\n * opened again by the manifest. `.env.example` changed in this release, so an\n * unedited older copy must be brought forward. RULES.md is carried in the same\n * loop although its text did NOT change here — its current render equals the\n * recorded bytes, so the refresh decision leaves it alone today, and a future\n * release that does change it is already handled. It is skipped entirely for a\n * project that switched the working rules off; see {@link envTemplates}. */\nconst DOC_SEEDS: ReadonlyArray<{ rel: string; kind: SeedKind; template: string }> = [\n { rel: '.noir/.env.example', kind: 'envExample', template: 'env.example.tmpl' },\n { rel: '.noir/rules/RULES.md', kind: 'rulesSeed', template: 'rules-seed.md.tmpl' },\n];\n\n/** `1.1.0 → 1.2.0`: bring unedited doc seeds up to the text this build ships.\n *\n * The decision is delegated to {@link isStaleSeed}, the same evidence the\n * manifest uses: a file is refreshed only when its bytes are an exact match for\n * a recorded past seed AND differ from the current render. A user-edited file\n * (or one already current) is left alone. The write goes through\n * {@link refreshSeed}, which overwrites the bytes while keeping the permission\n * bits the file already has.\n *\n * The working-rules seed is skipped outright when the project switched it off.\n * That switch says this project has no working rules for Noir to maintain, and\n * the refresh's evidence — \"these bytes are a seed Noir shipped, so nobody\n * edited them\" — is exactly the case the switch must not act on: it would\n * rewrite the rules text of a project that has opted out of having any, on the\n * strength of a comparison the user never asked for. `.env.example` is a\n * different file with a different owner (pure documentation, no switch), so it\n * refreshes either way. */\nconst envTemplates: MigrationScript = {\n from: '1.1.0',\n to: '1.2.0',\n description: 'refresh the .env.example doc seed and keep RULES.md in step',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n for (const seed of DOC_SEEDS) {\n if (seed.kind === 'rulesSeed' && ctx.rulesEnabled === false) {\n result.notes.push(`${seed.rel}: skipped — the working-rules switch is off`);\n continue;\n }\n const abs = join(ctx.root, seed.rel);\n\n // Absent: nothing to refresh, and creating it is NOT this migration's\n // job — the upgrade's `skipIfExists` emit phase seeds it from the\n // template, which already carries the current text.\n if (!existsSync(abs)) {\n result.notes.push(`${seed.rel}: absent — nothing to refresh`);\n continue;\n }\n\n let prev: string;\n try {\n prev = readFileSync(abs, 'utf8');\n } catch (err) {\n // Unreadable (a directory, a permission wall). Non-throwing is the\n // registry-wide contract: record and let the caller decide.\n result.conflicts.push(seed.rel);\n result.notes.push(`${seed.rel}: unreadable (${errorMessage(err)}) — left untouched`);\n continue;\n }\n\n const current = render(loadTemplate(seed.template), {});\n if (!isStaleSeed(seed.kind, prev, current)) {\n result.notes.push(`${seed.rel}: user-owned or already current — left untouched`);\n continue;\n }\n\n if (ctx.dryRun) {\n result.changed.push(seed.rel);\n result.notes.push(`${seed.rel}: would refresh to the current seed`);\n continue;\n }\n\n try {\n refreshSeed(abs, current);\n } catch (err) {\n result.conflicts.push(seed.rel);\n result.notes.push(`${seed.rel}: refresh failed (${errorMessage(err)}) — left untouched`);\n continue;\n }\n result.changed.push(seed.rel);\n result.notes.push(`${seed.rel}: refreshed to the current seed`);\n }\n return result;\n },\n};\n\n// --- 1.2.0 → 1.3.0: put a joined repo's MCP entry back on the bridge -------\n\n/** Where this repo's host keeps its MCP config, repo-relative and POSIX, or\n * `null` when the file is not there. The host is read from `.noir/config.yml`\n * exactly as the scaffold reads it; an absent or unreadable config means the\n * default host (`claude`, whose config is the root `.mcp.json`). */\nfunction mcpConfigPath(root: string): { rel: string; abs: string } | null {\n let host: HostId = 'claude';\n try {\n host = loadProjectInfo(root).config.host;\n } catch {\n // No readable project id/config — fall back to the default host rather than\n // failing the migration. Its config path is the one the older join flow used.\n }\n const abs = mcpConfigPathFor(host, root);\n if (!existsSync(abs)) return null;\n return { rel: relative(root, abs).split(sep).join('/'), abs };\n}\n\n/** `1.2.0 → 1.3.0`: rewrite the stale `http://…/mcp?p=<projectId>` pointer that\n * a repo which joined a workspace under the older flow still carries.\n *\n * That entry named a daemon by address, which cannot survive a restart: the\n * workspace daemon binds an ephemeral port and mints a fresh token every start,\n * while the config was written once, at join time. The current flow writes a\n * stdio entry naming the workspace instead, so the host reaches the daemon\n * through the bridge, which resolves the address and reads the token itself.\n *\n * No other command can repair this. `sync` re-emits the entry from the manifest\n * (so a re-scaffold keeps a joined repo joined) but leaves a file that differs\n * from the template alone unless it is forced, and the project's own\n * `.mcp.json` is not otherwise opened. The upgrade path is where a repair that\n * no emit can express belongs.\n *\n * Two conditions gate the rewrite, and both matter. The repo must carry the\n * workspace marker — a repo that deliberately chose the http transport has an\n * http entry too, and rewriting it would be wrong. And the entry must not\n * already be on the bridge: a joined repo whose entry names the workspace needs\n * nothing, which is what makes a second run a byte-level no-op.\n *\n * \"Not on the bridge\" is deliberately wider than \"is the http pointer\". The\n * same downgrade appears in a second shape: a joined repo whose `noir` entry is\n * the plain repo-scoped stdio form (`command`/`args` with no `--workspace`),\n * which is what a re-scaffold under an earlier release rewrote a joined repo's\n * entry to — the marker and the config then disagree about the same repo. A\n * marker is proof of membership, so an entry that does not name the workspace\n * is stale whichever form it is in, and both are repaired here. An entry in\n * neither shape (no `command`, no `type: 'http'`) is left alone: it is not a\n * transport this migration owns. */\nconst workspaceBridge: MigrationScript = {\n from: '1.2.0',\n to: '1.3.0',\n description: \"point a joined repo's MCP entry at the workspace bridge\",\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const name = readWorkspaceMarker(ctx.root);\n if (name === null) {\n result.notes.push('not joined to a workspace — nothing to rewrite');\n return result;\n }\n const target = mcpConfigPath(ctx.root);\n if (target === null) {\n result.notes.push(`workspace \"${name}\": no MCP config file to migrate`);\n return result;\n }\n\n let config: Record<string, unknown>;\n try {\n config = JSON.parse(readFileSync(target.abs, 'utf8')) as Record<string, unknown>;\n } catch (err) {\n // Unparseable (JSONC, a trailing comma, a directory). Non-throwing is the\n // registry-wide contract: record and let the caller decide.\n result.conflicts.push(target.rel);\n result.notes.push(`${target.rel}: unreadable (${errorMessage(err)}) — left untouched`);\n return result;\n }\n\n const servers = config.mcpServers;\n if (typeof servers !== 'object' || servers === null || Array.isArray(servers)) {\n result.notes.push(`${target.rel}: no mcpServers block — left untouched`);\n return result;\n }\n const entry = (servers as Record<string, unknown>).noir;\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n result.notes.push(`${target.rel}: no noir entry — left untouched`);\n return result;\n }\n // Which stale shape is this entry in? An http pointer names a daemon by\n // address. A stdio entry that does NOT pass `--workspace` is the repo-scoped\n // form — the downgrade a re-scaffold used to apply to a joined repo. An\n // entry that already names the workspace is the bridge entry a fresh join\n // writes, so there is nothing to repair (and a second run stays a no-op).\n const entryObj = entry as Record<string, unknown>;\n const args = entryObj.args;\n const namesWorkspace = Array.isArray(args) && args.includes('--workspace');\n const isHttpPointer = entryObj.type === 'http';\n const isRepoScopedStdio = typeof entryObj.command === 'string' && !namesWorkspace;\n if (!isHttpPointer && !isRepoScopedStdio) {\n result.notes.push(\n namesWorkspace\n ? `${target.rel}: already reaches Noir over the bridge — left untouched`\n : `${target.rel}: noir entry is not a transport this migration owns — left untouched`,\n );\n return result;\n }\n const kept = Object.entries(entryObj).filter(([key]) => !TRANSPORT_KEYS.has(key));\n\n if (ctx.dryRun) {\n result.changed.push(target.rel);\n result.notes.push(`${target.rel}: would point the noir entry at workspace \"${name}\"`);\n return result;\n }\n\n const next = {\n ...config,\n mcpServers: {\n ...(servers as Record<string, unknown>),\n noir: {\n command: resolveNoirCommand(),\n // The argv comes from the adapters' own helper, so this rewrite lands\n // on exactly the entry a fresh join writes — the name is the whole\n // address, and the bridge resolves the daemon (and reads its token)\n // itself, so none of that is written into a committed config file.\n args: noirStdioArgs(name),\n ...Object.fromEntries(kept),\n },\n },\n };\n try {\n atomicWriteFile(target.abs, `${JSON.stringify(next, null, 2)}\\n`);\n } catch (err) {\n result.conflicts.push(target.rel);\n result.notes.push(`${target.rel}: write failed (${errorMessage(err)}) — left untouched`);\n return result;\n }\n result.changed.push(target.rel);\n result.notes.push(`${target.rel}: noir entry now reaches workspace \"${name}\" over the bridge`);\n return result;\n },\n};\n\n// --- 1.3.0 → 1.4.0: route the SessionStart hook through `noir hook` ---------\n\n/** The old hook command this migration rewrites. The `.mjs` runner was emitted\n * directly-executable and named in the command, so its filename is the reliable\n * marker of \"this entry is the old Noir hook\". */\nconst OLD_HOOK_MARKER = 'noir-session-start';\n\n/** `1.3.0 → 1.4.0`: rewrite the SessionStart + SubagentStart hook command from\n * the directly-executed `.noir/hooks/noir-session-start.mjs` runner to\n * `\"<noir>\" hook`, and remove the now-orphaned runner file.\n *\n * The `.mjs` runner depended on the file's exec bit and on `node` being on the\n * spawn PATH; `noir hook` routes through the shim, which resolves the managed\n * Node by absolute path and is kept executable by the installer. The command\n * lives in `.claude/settings.local.json`, which the manifest deliberately never\n * re-emits (the user may have removed the hook), so only the upgrade path can\n * repair an existing project's entry — which is what this migration is for.\n *\n * Idempotent: a command already routed through ` hook` is left alone, and the\n * runner-file removal is `force: true`, so a second run is a byte-level no-op.\n */\nconst hookCommand: MigrationScript = {\n from: '1.3.0',\n to: '1.4.0',\n description: 'route the SessionStart hook through `noir hook` (drop the .mjs runner)',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const settingsRel = '.claude/settings.local.json';\n const settingsPath = join(ctx.root, '.claude', 'settings.local.json');\n\n // 1. Rewrite the hook command(s) in settings.local.json, when present.\n if (existsSync(settingsPath)) {\n let settings: Record<string, unknown>;\n try {\n settings = JSON.parse(readFileSync(settingsPath, 'utf8')) as Record<string, unknown>;\n } catch (err) {\n result.conflicts.push(settingsRel);\n result.notes.push(\n `${settingsRel}: unreadable (${errorMessage(err)}) — command left untouched`,\n );\n settings = {}; // still remove the orphaned runner below\n }\n\n let rewritten = false;\n const hooks = settings.hooks;\n if (typeof hooks === 'object' && hooks !== null && !Array.isArray(hooks)) {\n for (const eventEntries of Object.values(hooks as Record<string, unknown>)) {\n if (!Array.isArray(eventEntries)) continue;\n for (const group of eventEntries) {\n if (typeof group !== 'object' || group === null) continue;\n const inner = (group as Record<string, unknown>).hooks;\n if (!Array.isArray(inner)) continue;\n for (const entry of inner) {\n if (typeof entry !== 'object' || entry === null) continue;\n const hookObj = entry as Record<string, unknown>;\n if (\n typeof hookObj.command === 'string' &&\n hookObj.command.includes(OLD_HOOK_MARKER)\n ) {\n hookObj.command = `\"${resolveNoirCommand()}\" hook`;\n rewritten = true;\n }\n }\n }\n }\n }\n\n if (!rewritten) {\n result.notes.push(`${settingsRel}: no .mjs hook command to migrate`);\n } else if (ctx.dryRun) {\n result.changed.push(settingsRel);\n result.notes.push(`${settingsRel}: would route the hook through \\`noir hook\\``);\n } else {\n try {\n atomicWriteFile(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`);\n result.changed.push(settingsRel);\n result.notes.push(`${settingsRel}: hook now routes through \\`noir hook\\``);\n } catch (err) {\n result.conflicts.push(settingsRel);\n result.notes.push(`${settingsRel}: write failed (${errorMessage(err)}) — left untouched`);\n }\n }\n } else {\n result.notes.push(`${settingsRel}: absent — nothing to migrate`);\n }\n\n // 2. Remove the orphaned runner. Its command no longer points at it, so the\n // file is dead weight in a project that had the old hook emitted.\n const runnerRel = '.noir/hooks/noir-session-start.mjs';\n const runnerPath = join(ctx.root, '.noir', 'hooks', 'noir-session-start.mjs');\n if (!existsSync(runnerPath)) {\n return result;\n }\n if (ctx.dryRun) {\n result.changed.push(runnerRel);\n result.notes.push(`${runnerRel}: would remove the orphaned runner`);\n return result;\n }\n try {\n rmSync(runnerPath, { force: true });\n } catch (err) {\n result.conflicts.push(runnerRel);\n result.notes.push(`${runnerRel}: remove failed (${errorMessage(err)})`);\n return result;\n }\n result.changed.push(runnerRel);\n result.notes.push(`${runnerRel}: removed (orphaned runner)`);\n // Drop the now-empty hooks directory so no stale `.noir/hooks/` lingers.\n // Best-effort: a non-empty or already-gone directory is fine either way.\n const hooksDir = join(ctx.root, '.noir', 'hooks');\n try {\n if (readdirSync(hooksDir).length === 0) rmdirSync(hooksDir);\n } catch {\n // best-effort cleanup\n }\n return result;\n },\n};\n\n// --- 1.4.0 → 1.5.0: append the enforcement hook events ----------------------\n\n/** A hook event the migration appends to an existing host hook file, shaped for\n * that host's config (claude `{hooks:[{type,command}]}`, gemini adds a\n * `matcher` for the BeforeTool deny event). */\ntype HookEventPatch = Array<Record<string, unknown>>;\n\n/** Append a set of hook events to a host's hook config file. Idempotent: an\n * event key already present is left alone. `createIfMissing` controls whether\n * an absent file (or a file with no `hooks` object) is created — true for a\n * host whose hook is brand new (gemini), false for a host where an absent file\n * means the user removed the hook and it must not be resurrected (claude). */\nfunction applyHookEvents(\n ctx: MigrationContext,\n rel: string,\n abs: string,\n events: Record<string, HookEventPatch>,\n createIfMissing: boolean,\n): { note: string; changed: boolean; conflict: boolean } {\n let settings: Record<string, unknown>;\n if (!existsSync(abs)) {\n if (!createIfMissing) {\n return { note: `${rel}: absent — nothing to migrate`, changed: false, conflict: false };\n }\n settings = {};\n } else {\n try {\n settings = JSON.parse(readFileSync(abs, 'utf8')) as Record<string, unknown>;\n } catch (err) {\n return {\n note: `${rel}: unreadable (${errorMessage(err)}) — left untouched`,\n changed: false,\n conflict: true,\n };\n }\n }\n\n const existing = settings.hooks;\n if (typeof existing !== 'object' || existing === null || Array.isArray(existing)) {\n if (!createIfMissing) {\n return {\n note: `${rel}: no hooks object — nothing to migrate`,\n changed: false,\n conflict: false,\n };\n }\n settings.hooks = {};\n }\n const hooksObj = settings.hooks as Record<string, unknown>;\n\n let added = false;\n for (const [event, patch] of Object.entries(events)) {\n if (hooksObj[event] === undefined) {\n hooksObj[event] = patch;\n added = true;\n }\n }\n if (!added) {\n return { note: `${rel}: enforcement events already present`, changed: false, conflict: false };\n }\n if (ctx.dryRun) {\n return { note: `${rel}: would append enforcement events`, changed: true, conflict: false };\n }\n try {\n atomicWriteFile(abs, `${JSON.stringify(settings, null, 2)}\\n`);\n return { note: `${rel}: appended enforcement events`, changed: true, conflict: false };\n } catch (err) {\n return {\n note: `${rel}: write failed (${errorMessage(err)}) — left untouched`,\n changed: false,\n conflict: true,\n };\n }\n}\n\n/** `1.4.0 → 1.5.0`: append the enforcement events to an existing claude or\n * gemini hook. The hook entry is write-once — the manifest never re-emits it\n * after init — so an install created before enforcement shipped has no (or a\n * partial) hook. claude gains PreToolUse + Stop; gemini gains SessionStart +\n * BeforeTool. cursor is a regenerate-mode file, so sync re-emits it with no\n * migration. Idempotent: an event key already present is left alone. */\nconst hookEvents: MigrationScript = {\n from: '1.4.0',\n to: '1.5.0',\n description: 'append the enforcement hook events to the claude or gemini hook',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const host = readProjectConfig(ctx.root)?.host ?? 'claude';\n\n if (host !== 'claude' && host !== 'gemini') {\n result.notes.push(\n `${host}: no hook migration needed (cursor is regenerate-mode; opencode/agents-md have no hook surface)`,\n );\n return result;\n }\n\n const rel = host === 'gemini' ? '.gemini/settings.json' : '.claude/settings.local.json';\n const abs = join(ctx.root, ...rel.split('/'));\n const command =\n host === 'gemini'\n ? `\"${resolveNoirCommand()}\" hook --host gemini`\n : `\"${resolveNoirCommand()}\" hook`;\n const events: Record<string, HookEventPatch> =\n host === 'gemini'\n ? {\n SessionStart: [{ hooks: [{ type: 'command', command }] }],\n BeforeTool: [{ matcher: '.*', hooks: [{ type: 'command', command }] }],\n }\n : {\n PreToolUse: [{ hooks: [{ type: 'command', command }] }],\n Stop: [{ hooks: [{ type: 'command', command }] }],\n };\n\n const out = applyHookEvents(ctx, rel, abs, events, host === 'gemini');\n if (out.conflict) result.conflicts.push(rel);\n else if (out.changed) result.changed.push(rel);\n result.notes.push(out.note);\n return result;\n },\n};\n\n/** The registry. The runner sorts the selected window by `to`, so declaration\n * order is documentation only — oldest step first. */\nexport const MIGRATIONS: readonly MigrationScript[] = [\n synthetic,\n envPointer,\n envTemplates,\n workspaceBridge,\n hookCommand,\n hookEvents,\n];\n/** `Error.message` for anything thrown, without assuming an Error. */\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n// --- conflict-marker helpers (exported for migration authors) ---------------\n\n/** Write git-style inline conflict markers around `theirs`/`ours` so a human\n * or AI agent can resolve later. This is the CI-safe fallback —\n * no interactive prompts, ever. */\nexport function applyInlineConflict(\n ours: string,\n theirs: string,\n oursLabel = 'ours',\n theirsLabel = 'theirs',\n): string {\n return `<<<<<<< ${oursLabel}\\n${ours}=======\\n${theirs}>>>>>>> ${theirsLabel}\\n`;\n}\n\n/** Apply `(ours, theirs)` to a region: if they're equal, return `ours` (no\n * conflict); otherwise emit inline markers. Migration authors should prefer\n * this over {@link applyInlineConflict} when the \"no change needed\" case is\n * common — it keeps re-runs truly idempotent (no spurious markers on a clean\n * tree). */\nexport function applyWithConflict(\n ours: string,\n theirs: string,\n path: string,\n): {\n text: string;\n conflicted: boolean;\n} {\n if (ours === theirs) return { text: ours, conflicted: false };\n return {\n text: applyInlineConflict(ours, theirs, path, path),\n conflicted: true,\n };\n}\n","// noir-hygiene: exempt\n// The template literals below record historical seed-template bytes verbatim;\n// their banner-looking `# ---` lines are load-bearing bytes, not comments, and\n// must stay byte-identical to what users already have on disk.\n/**\n * Template history — the evidence needed to decide whether a shipped seed\n * file may be refreshed during an upgrade.\n *\n * WHY THIS EXISTS\n * `noir init` writes two files whose only job is to be read by a human:\n * `.noir/.env.example` (documents the variable set) and `.noir/rules/RULES.md`\n * (the AI working-contract seed). Both are emitted create-only-if-absent, and\n * that is normally the right contract: once a file exists, the user owns it and\n * Noir must never overwrite their edits.\n *\n * It has one bad consequence. When a later release adds a variable to\n * `.noir/.env.example`, an already-initialized project keeps the old text\n * forever — the upgrade path cannot tell \"the user rewrote this file\" apart\n * from \"the user never touched it, and the shipped text has moved on\". Those\n * two need opposite handling: leave the first alone, refresh the second. A\n * plain-content check cannot distinguish them, and nothing on disk records the\n * bytes that were originally written.\n *\n * This module supplies the missing evidence. It holds the exact bytes of the\n * seed templates as they shipped at each past scaffold version, so a caller can\n * ask: \"are the bytes on disk identical to some version Noir once wrote, and\n * different from what Noir writes today?\" If yes, the file is an unedited\n * leftover and refreshing it loses nothing.\n *\n * THE CONTRACT\n * {@link isStaleSeed} answers exactly that question. It is deliberately\n * conservative in one direction: it returns `true` only on an exact match\n * against a recorded past version, so ANY other byte difference — a user edit,\n * a re-wrapped line, even a CRLF line-ending conversion from a Windows\n * checkout — reads as \"user-owned\" and the file is left untouched. A missed\n * refresh is a documentation gap; a wrong refresh destroys user work. The\n * asymmetry is intentional and should not be \"fixed\" into a fuzzy match.\n *\n * Each entry holds the text of BOTH seeds, so the comparison takes the seed it\n * is about as an argument and scans only that field. Scanning all of them would\n * let a `.env.example` match the recorded working-rules text (or the reverse),\n * and since the caller's response to `true` is an overwrite, a mixed-up pairing\n * would discard an edit the user is entitled to keep. The caller knows which\n * file it just read; the argument makes it say so.\n *\n * RENDERING\n * Seed templates are normally interpolated through `render(template, vars)`\n * before writing. That step is a no-op for the two templates recorded here:\n * neither contains a `{{...}}` token, so `render(text, vars) === text` for\n * every possible `vars`. The registry therefore stores raw template text and\n * {@link isStaleSeed} compares it against raw file bytes — no `vars` argument\n * is needed, and callers do not have to reconstruct the context that wrote the\n * file years ago.\n *\n * That equivalence is load-bearing, so it is asserted rather than merely\n * documented: `template-history.test.ts` fails if any recorded template\n * contains a `{{` token. A future seed template that DOES interpolate needs\n * `render` applied to the history entry (and a `vars` parameter on the\n * comparison) before it is added here — the test exists to make that\n * requirement impossible to overlook.\n *\n * APPENDING TO THE HISTORY\n * Record the template bytes as they shipped, keyed by the scaffold version\n * whose `init` wrote them, and never edit an existing entry — a user's disk\n * still holds those old bytes, and rewriting history would silently reclassify\n * their file as \"current\" (no refresh) or \"unknown\" (no refresh). The same\n * reasoning that keeps npm versions immutable applies here.\n */\n\n/**\n * Which recorded seed a comparison is about. The values mirror the field names\n * of {@link SeedTemplateHistoryEntry}: every entry carries the text of both\n * seeds, and a caller must name the one whose bytes it read (see\n * {@link isStaleSeed} on why the pairing is load-bearing).\n */\nexport type SeedKind = 'envExample' | 'rulesSeed';\n\n/**\n * The seed templates as they shipped at one past scaffold version.\n *\n * Only the doc-only seeds are tracked. Files the user is expected to edit\n * heavily (`.noir/config.yml`, `.noir/project.id`) are not candidates for\n * refresh at all, and co-owned files that carry a managed region already have a\n * three-way merge path of their own.\n */\nexport interface SeedTemplateHistoryEntry {\n /**\n * Bare `x.y.z` scaffold version whose `init` wrote these bytes — the value\n * stamped into `.noir/scaffold-version` at the time, with no `v` prefix and\n * no pre-release suffix.\n *\n * Spelled out literally rather than read from `CURRENT_SCAFFOLD_VERSION`:\n * the version recorded here is a historical fact about these bytes, so it\n * must not advance when the current version does.\n */\n readonly scaffoldVersion: string;\n /** Raw text of the `env.example` template at this version (see the module\n * header on why raw text equals the rendered file for this template). */\n readonly envExample: string;\n /** Raw text of the `rules-seed.md` template at this version. */\n readonly rulesSeed: string;\n}\n\n/**\n * `.noir/.env.example` as shipped by scaffold version 1.1.0 — the first\n * version to emit it. Captured verbatim from the packaged template so the\n * bytes are byte-identical to what landed in users' repositories.\n *\n * `\\${` and `` \\` `` appear below because the text itself contains those\n * characters; the escape sequences decode back to the original bytes, which\n * `template-history.test.ts` verifies against a recorded digest.\n */\nconst ENV_EXAMPLE_1_1_0 = `# .noir/.env.example — committable documentation of the format.\n#\n# This file is a TEMPLATE and is never loaded. Noir reads \\`.noir/.env\\`, which\n# \\`noir init\\` already created for you at mode 0600 — edit that file and\n# uncomment the entries you need. Keep this example in git so the variable set\n# stays discoverable.\n#\n# ---------------------------------------------------------------------------\n# The block below is the same header \\`.noir/.env\\` carries (spec 12.1).\n# ---------------------------------------------------------------------------\n#\n# .noir/.env — project-scoped configuration for this repo.\n#\n# This file is the recommended home for project-scoped configuration and\n# secrets. Precedence:\n# 1. run profile env run.profiles.<n>.env (per-invocation; merges OVER)\n# 2. .noir/.env <- recommended home for project-scoped configuration\n# 3. real environment CI / container / launchd / shell rc\n# 4. built-in default\n#\n# A real environment variable is a FALLBACK: it applies only to keys this file\n# leaves unset. A machine-global export therefore cannot shadow a value here.\n# Run \\`noir env\\` to see which source is winning for each key.\n#\n# Keep this file private: chmod 600 .noir/.env\n# It is gitignored by Noir's managed .gitignore block, and Noir REFUSES to load\n# it if it is tracked by git (a cloned repo could redirect credentials).\n# Never commit \\`.noir/.env\\`. (THIS file is the committable one.)\n\n# --- Integrations -----------------------------------------------------------\n# ClickUp — only if the noir-clickup integration is enabled.\n# CLICKUP_API_TOKEN=pk_00000000000000000000000000000000\n#\n# CLICKUP_TEAM_ID is NOT an env var — Noir never reads it. Workspace binding\n# (team/list/space ids) is config.yml instead:\n# integrations:\n# clickup:\n# teamId: \"ABC123\" # optional — only for custom task IDs (#ABC-123)\n\n# --- Remote embedders (context.embedder.kind: remote) -----------------------\n# The default local embedder (kind: local) needs no key.\n# OPENAI_API_KEY=sk-000000000000000000000000000000000000000000000000\n# VOYAGE_API_KEY=pa-0000000000000000000000000000000000000000000000\n# COHERE_API_KEY=0000000000000000000000000000000000000000\n#\n# Ollama (kind: ollama) reads its base URL from config.yml\n# (context.embedder.baseURL); this variable is the fallback:\n# OLLAMA_BASE_URL=http://localhost:11434\n\n# --- Model provider key -----------------------------------------------------\n# There is NO fixed \"model key\" variable, and setting one is not enough on its\n# own. \\`apiKeyEnv\\` is a NAME, not an interpolation: the config stores the\n# variable NAME and Noir reads process.env[<that name>] at call time. Write the\n# bare name — never \\`\\${...}\\` — in .noir/config.yml:\n#\n# model:\n# providers:\n# anthropic:\n# apiKeyEnv: ANTHROPIC_API_KEY # -> reads $ANTHROPIC_API_KEY\n# tiers:\n# default:\n# provider: anthropic\n#\n# Only \\`run.profiles.<name>.env\\` interpolates \\`\\${VAR}\\`. With no provider\n# configured the model layer degrades to templates — Noir never makes a silent\n# paid call. \\`ANTHROPIC_API_KEY\\` is the conventional example, not a variable\n# Noir looks up on its own:\n# ANTHROPIC_API_KEY=sk-ant-0000000000000000000000000000000000000000000000\n\n# --- Run profile selection --------------------------------------------------\n# Selects a \\`run.profiles.<name>\\` entry for \\`noir run\\`.\n# Precedence: --profile flag > NOIR_PROFILE > run.defaultProfile > host default.\n# NOIR_PROFILE=work\n\n# --- Update kill-switches ---------------------------------------------------\n# NOIR_DISABLE_UPDATE_CHECK=1 # suppress the background startup check only\n# NOIR_DISABLE_UPDATES=1 # make \\`noir update\\` refuse (exit 2)\n\n# See docs/reference/environment.md for every variable Noir reads.\n`;\n\n/**\n * `.noir/.env.example` as shipped by scaffold version 1.2.0 — the env-template\n * redesign (gateway section first, a full purpose/precedence/commit guide, and\n * placeholder values that are clearly placeholders rather than fake keys).\n * Captured verbatim from the packaged template so the bytes are byte-identical\n * to what landed in users' repositories.\n */\nconst ENV_EXAMPLE_1_2_0 = `# .noir/.env.example — the committable reference for .noir/.env.\n#\n# What .noir/.env is for\n# ----------------------\n# .noir/.env is this project's configuration-and-secrets file. \\`noir init\\`\n# creates it at mode 0600 and gitignores it, and Noir loads it on every\n# command. For every key it defines, it WINS over your shell environment, so it\n# is the recommended home for project-scoped values and secrets.\n#\n# Precedence (highest wins):\n# 1. run profile env run.profiles.<n>.env (per-invocation; merges over)\n# 2. .noir/.env the project-scoped file this example documents\n# 3. real environment CI / container / launchd / shell rc\n# 4. built-in default\n#\n# A real environment variable is only a FALLBACK for keys .noir/.env leaves\n# unset. For a value scoped to one invocation, add a \\`run.profiles.<name>.env\\`\n# entry and reference it there as \\`\\${VAR}\\`, so the literal value stays out of\n# config.yml; values that must not be committed belong in \\`.noir/.env\\`\n# (gitignored) or the real environment. \\`noir env\\` reports the winning source\n# for each key.\n#\n# What to commit\n# --------------\n# Keep .noir/.env OUT of git: it holds real secrets, Noir's managed .gitignore\n# block excludes it, and Noir refuses to load a copy that IS tracked (a cloned\n# repo could redirect credentials). chmod 600 .noir/.env to keep it private.\n# Never commit the real file — commit THIS example, so the keys stay\n# discoverable and documented.\n#\n# Every line below is documentation. Copy a key to .noir/.env and set a real\n# value to enable it.\n#\n# Full reference: docs/reference/environment.md\n\n# --- Host gateway (Claude Code via Z.AI / LiteLLM / OpenRouter / Kimi ...) ---\n# Point the host at an Anthropic-shaped endpoint instead of api.anthropic.com.\n#\n# ANTHROPIC_BASE_URL — the gateway's base URL. Host ONLY: give the origin (for\n# example https://gateway.example) and NOT the /v1/messages path — the host\n# appends that itself.\n# ANTHROPIC_AUTH_TOKEN — sent as \\`Authorization: Bearer <token>\\`. It is the\n# HEADER variant of a credential: AUTH_TOKEN becomes Bearer, while\n# ANTHROPIC_API_KEY becomes x-api-key. Do not set BOTH — that is an auth\n# conflict.\n# ANTHROPIC_DEFAULT_HAIKU_MODEL / _SONNET_MODEL / _OPUS_MODEL — remap the\n# haiku / sonnet / opus aliases to the model ids your gateway serves.\n# API_TIMEOUT_MS — per-request timeout in milliseconds (Claude Code default is\n# 600000).\n# CLAUDE_CODE_AUTO_COMPACT_WINDOW — auto-compact window (100000 to 1000000).\n#\n# ANTHROPIC_BASE_URL=https://gateway.example\n# ANTHROPIC_AUTH_TOKEN=replace-with-your-gateway-token\n# ANTHROPIC_DEFAULT_HAIKU_MODEL=replace-with-your-gateway-haiku-model\n# ANTHROPIC_DEFAULT_SONNET_MODEL=replace-with-your-gateway-sonnet-model\n# ANTHROPIC_DEFAULT_OPUS_MODEL=replace-with-your-gateway-opus-model\n# API_TIMEOUT_MS=600000\n# CLAUDE_CODE_AUTO_COMPACT_WINDOW=1000000\n\n# --- Integrations ---\n# CLICKUP_API_TOKEN — the token for the noir-clickup integration; set it only\n# when that integration is enabled.\n# CLICKUP_TEAM_ID is NOT an env var. Noir never reads it — workspace binding\n# (team / list / space ids) belongs in .noir/config.yml under\n# integrations.clickup.\n# CLICKUP_API_TOKEN=replace-with-your-clickup-token\n\n# --- Remote embedders (context.embedder.kind: remote) ---\n# The default local embedder (kind: local) needs no key. The three API-key\n# variables below apply only when config.yml selects a remote embedder.\n# OPENAI_API_KEY=replace-with-your-openai-key\n# VOYAGE_API_KEY=replace-with-your-voyage-key\n# COHERE_API_KEY=replace-with-your-cohere-key\n#\n# Ollama (kind: ollama) reads its base URL from config.yml\n# (context.embedder.baseURL); this variable is the fallback:\n# OLLAMA_BASE_URL=http://localhost:11434\n\n# --- Model provider key (read via apiKeyEnv in .noir/config.yml) ---\n# There is no fixed \"model key\" variable, and setting a key is not enough on\n# its own. \\`apiKeyEnv\\` is a NAME, not an interpolation: config.yml stores the\n# variable NAME, and Noir reads process.env[<that name>] at call time. Write\n# the bare name — never \\`\\${...}\\` — in .noir/config.yml:\n#\n# model:\n# providers:\n# anthropic:\n# apiKeyEnv: ANTHROPIC_API_KEY # -> reads $ANTHROPIC_API_KEY\n#\n# Only \\`run.profiles.<name>.env\\` interpolates \\`\\${VAR}\\`. With no provider\n# configured the model layer degrades to templates — Noir never makes a silent\n# paid call. \\`ANTHROPIC_API_KEY\\` is the conventional example, not a variable\n# Noir looks up on its own.\n# ANTHROPIC_API_KEY=replace-with-your-anthropic-key\n\n# --- Run profile selection (noir run) ---\n# Selects a \\`run.profiles.<name>\\` entry for \\`noir run\\`.\n# Precedence: --profile flag > NOIR_PROFILE > run.defaultProfile > host default.\n# NOIR_PROFILE=work\n\n# --- Update kill-switches ---\n# NOIR_DISABLE_UPDATE_CHECK=1 # suppress the background startup check only\n# NOIR_DISABLE_UPDATES=1 # make \\`noir update\\` refuse (exit 2)\n\n# See docs/reference/environment.md for every variable Noir reads.\n`;\n\n/**\n * `.noir/rules/RULES.md` as shipped by scaffold version 1.1.0.\n *\n * Copy this text verbatim when recording a new version's seed: the whole value\n * of the snapshot is byte equality with what users already have on disk.\n */\nconst RULES_SEED_1_1_0 = `# Noir working rules\n\n> Canonical AI working-contract for this project. The host context file (CLAUDE.md) @imports this.\n> Keep LEAN: every line must be failure-backed, tool-enforceable, decision-encoding, or triggerable — else delete it.\n> Edit freely — Noir re-emits only the @import pointer, never this body.\n\n## Identity & scope\n- This project uses **Noir** (discipline/context/memory layer) inside **Claude Code**.\n- Noir is an orchestration layer — the host CLI is the execution engine; Noir is the spec/context/memory brain.\n- Stay in scope: do only what the current Noir task (\\`.noir/tasks/\\`) requires. Surface scope creep BEFORE acting.\n\n## Anti-assumption contract\n- **Never fabricate** facts, APIs, file contents, or command output. Use only what you have read or verified.\n- **Never assume** — if a path, signature, or convention is unclear, STOP and ask before acting.\n- Cite the file/line or command you relied on for any non-obvious claim.\n- Detect non-conventional setups; do not paper over them with assumed conventions.\n\n## Spec-Driven Development workflow\n- Follow the Noir SDD lifecycle: intake → clarify → spec → plan → execute → verify → document.\n- Gates are observable: every gate decision is recorded (approved / forced / skipped). \\`--force\\` requires a reason.\n- Do not edit/run/execute before facts are gathered AND the human confirms understanding.\n\n## Verification (run before claiming done)\n- \\`pnpm build && pnpm lint && pnpm typecheck && pnpm test\\` — all green, with real output. No hedging.\n- Report failures truthfully with the actual output; never claim green without evidence.\n\n## Coding standards & architecture\n- Follow existing patterns; match surrounding code's style, naming, comment density.\n- Architecture decisions live in \\`.noir/decisions/\\` (ADR-style) — read them before cross-cutting changes.\n- See \\`docs/roadmap.md\\` for direction and \\`docs/specs/\\` for design.\n\n## Conventions gotchas (project-specific — fill in)\n- Commits stay local until explicitly pushed.\n- The full test suite runs offline/free (no network in CI).\n`;\n\n/**\n * The recorded seed-template bytes, oldest first.\n *\n * Read-only by intent: this is an append-only ledger of shipped bytes, not\n * mutable configuration. Entries are never edited or removed (see the module\n * header on why rewriting a past entry would silently misclassify a user's\n * file), so a version that changed a seed template appends rather than\n * updates.\n */\nexport const SEED_TEMPLATE_HISTORY: readonly SeedTemplateHistoryEntry[] = [\n {\n scaffoldVersion: '1.1.0',\n envExample: ENV_EXAMPLE_1_1_0,\n rulesSeed: RULES_SEED_1_1_0,\n },\n {\n scaffoldVersion: '1.2.0',\n envExample: ENV_EXAMPLE_1_2_0,\n rulesSeed: RULES_SEED_1_1_0,\n },\n];\n\n/**\n * Does `fileBytes` look like an unedited `seed` left over from an older scaffold\n * version?\n *\n * `true` iff the bytes are an exact match for that seed's text in a recorded\n * history entry AND differ from `currentRender` (what Noir writes for that seed\n * today). Callers pass `currentRender` rather than this module deriving it,\n * because the current template text is the business of the manifest and the\n * template loader — and because the compile-time constant that names the\n * current scaffold version advances independently of the bytes recorded here.\n *\n * `seed` names the file the bytes came from. It is a safety argument, not\n * bookkeeping: because each entry records both seeds, a comparison that scanned\n * every field would report the recorded working-rules bytes as a stale\n * `.env.example`, and the caller's response to `true` is to overwrite the file.\n * Naming the seed keeps the comparison inside its own column, so a mixed-up\n * pairing cannot silently destroy an edit the user is entitled to keep.\n *\n * The `currentRender` guard is checked FIRST: it is what keeps a no-op from\n * being reported as a refresh. A project initialized at the current version\n * already holds the current bytes; those bytes also match their own history\n * entry, so without the second condition every up-to-date project would be told\n * to rewrite its seeds.\n *\n * Callers own the other half of the decision: a file that does not exist has no\n * bytes to compare and is a plain \"create the seed\", not a refresh.\n */\nexport function isStaleSeed(seed: SeedKind, fileBytes: string, currentRender: string): boolean {\n if (fileBytes === currentRender) return false;\n return SEED_TEMPLATE_HISTORY.some((entry) => entry[seed] === fileBytes);\n}\n","import { MIGRATIONS } from './index.js';\nimport type { MigrationContext, MigrationResult, MigrationScript } from './types.js';\n\n/**\n * Migration runner. Given a `from` version present on disk and a target `to`\n * version (usually {@link CURRENT_SCAFFOLD_VERSION}), walks the\n * {@link MIGRATIONS} registry forward in version order, executing each step.\n *\n * Selection rule: a script runs when its `from` is `>= fromArg` (semver-ish\n * string compare is enough at Noir's scale; we don't pull in a semver dep for\n * this) AND its `to` is `<= toArg`. Scripts strictly outside the window are\n * skipped. Within the window, scripts run sorted by `to` ascending so a\n * multi-step upgrade (1.0.0 → 1.1.0 → 1.2.0) composes in order.\n *\n * The runner NEVER throws on a per-script failure — it captures the error,\n * records a synthetic conflict entry (`<path>__error`), and continues so a\n * single broken migration doesn't block the rest of the chain. The orchestrator\n * decides whether non-empty `conflicts` is a hard failure (init --upgrade) or a\n * warning (doctor).\n *\n * Returns the aggregate of every step's `changed`/`conflicts`/`notes`.\n */\nexport function runMigrations(\n root: string,\n from: string | null,\n to: string,\n opts: { dryRun?: boolean; rulesEnabled?: boolean } = {},\n): MigrationResult & { from: string | null; to: string; ran: string[] } {\n const window = pickWindow(from ?? '0.0.0', to, MIGRATIONS);\n const ctx: MigrationContext = {\n root,\n dryRun: opts.dryRun === true,\n rulesEnabled: opts.rulesEnabled,\n };\n const aggregate: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const ran: string[] = [];\n\n for (const script of window) {\n ran.push(`${script.from}→${script.to}`);\n let res: MigrationResult;\n try {\n res = script.run(ctx);\n } catch (err) {\n // Non-fatal at the runner level: record and continue. The caller turns\n // non-empty conflicts into a CI failure with a real exit code.\n const msg = err instanceof Error ? err.message : String(err);\n aggregate.conflicts.push(`<runner>:${script.from}→${script.to} threw: ${msg}`);\n continue;\n }\n aggregate.changed.push(...res.changed);\n aggregate.conflicts.push(...res.conflicts);\n aggregate.notes.push(...res.notes);\n }\n\n return {\n ...aggregate,\n from,\n to,\n ran,\n };\n}\n\n/** Pick the ordered subset of `scripts` forming the chain `[from, to]`. */\nfunction pickWindow(\n from: string,\n to: string,\n scripts: readonly MigrationScript[],\n): MigrationScript[] {\n return scripts\n .filter((s) => compareVer(s.from) >= compareVer(from) && compareVer(s.to) <= compareVer(to))\n .sort((a, b) => compareVer(a.to) - compareVer(b.to));\n}\n\n/** Tiny numeric tuple compare: `'1.10.3'` → `[1,10,3]`, compared element-wise.\n * Pre-release suffixes (e.g. `-beta.1`) are ignored — Noir ships `x.y.z`\n * scaffold versions and the runner only needs a deterministic order. */\nfunction compareVer(v: string): number {\n const parts = v.split('-')[0]?.split('.') ?? [];\n let n = 0;\n for (let i = 0; i < 3; i++) {\n const p = Number(parts[i] ?? '0');\n n = n * 1000 + (Number.isFinite(p) ? p : 0);\n }\n return n;\n}\n","import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport {\n createProjectId,\n loadProjectInfo,\n type ManagedBlock,\n paths,\n readManagedBlock,\n readProjectConfig,\n resolveNoirCommand,\n sha256Hex12,\n} from '@noir-ai/core';\nimport { readAncestors, writeAncestors } from './ancestors.js';\nimport { detectCommands, detectEntryPoints, renderBaseline } from './baseline.js';\nimport {\n type BuildManifestContext,\n buildManifest,\n type HostTag,\n type ManifestEntry,\n REFRESHABLE_SEED_KIND,\n} from './manifest.js';\nimport { mergeThreeWay } from './merge.js';\nimport { runMigrations } from './migrations/index.js';\nimport {\n CURRENT_SCAFFOLD_VERSION,\n readScaffoldVersion,\n writeScaffoldVersion,\n} from './scaffold-version.js';\nimport { detectStack, type StackInfo } from './stack-detect.js';\nimport { render } from './template.js';\nimport { isStaleSeed, type SeedKind } from './template-history.js';\nimport { loadTemplate } from './template-loader.js';\nimport {\n buildRegion,\n type EnvMode,\n ensureOwnerOnly,\n managedBlock,\n managedBlocks,\n mergeJson,\n predictManagedBlock,\n predictManagedBlocks,\n refreshSeed,\n regenerate,\n skipIfExists,\n type WriteMode,\n} from './writers.js';\n\n/**\n * Scaffold orchestrator. One function the cli calls for `noir init`,\n * `noir create`, and `noir sync`; mode selects the manifest subset + how\n * project identity is resolved.\n *\n * High-level flow:\n * 1. Resolve project id (provided > existing > generated; sync requires existing).\n * 2. Detect stack (READ-ONLY; always runs; never throws).\n * 3. If {@link ScaffoldOptions.upgrade}, run migrations from the on-disk\n * scaffold-version → {@link CURRENT_SCAFFOLD_VERSION}.\n * 4. Build the manifest, filter by host + mode.\n * 5. For each entry: mkdir -p, render template/content, dispatch to the\n * matching writer.\n * 6. On `init`/`create`, stamp `.noir/scaffold-version` LAST so a crash leaves\n * an old/absent stamp rather than a misleading fresh one.\n *\n * The orchestrator owns dir creation (writers refuse to so that a missing dir\n * is one attributable failure, not N silent ones).\n */\n\nexport type ScaffoldMode = 'init' | 'create' | 'sync';\n\nexport interface ScaffoldOptions {\n /** Absolute repo root. For `create`, the new dir (created if absent). */\n root: string;\n mode: ScaffoldMode;\n /** Target host. Defaults to `'claude'` (the only shipped host). */\n host?: HostTag;\n /** MCP transport. Defaults to `'stdio'`. */\n transport?: 'stdio' | 'streamable-http';\n /** Required when transport is `streamable-http`. */\n url?: string;\n /** Explicit project id; bypasses generate/read. Mainly for tests + `create`\n * flows that want deterministic ids. */\n projectId?: string;\n /** `noir init --upgrade`: run migrations, then re-emit every manifest mode\n * EXCEPT `mergeJson` (`.claude/settings.local.json` is user-owned and\n * write-once at init/create). `skipIfExists` keeps its\n * create-only-if-absent semantics, so an upgrade backfills seeds added since\n * initialization without ever touching a file the user owns. Only meaningful\n * with mode `'init'`. */\n upgrade?: boolean;\n /** Re-scaffold even when the target is already initialized (bypasses\n * the already-initialized no-op guard). Does NOT bypass `assertSafeRoot`\n * — root-safety is hard, never bypassable. */\n force?: boolean;\n /** Preview: compute the same written/skipped/migrated lists without touching\n * disk. `noir doctor`/CI use this to report drift. */\n dryRun?: boolean;\n /** Drift check: a dry-run that reports only what would CHANGE (byte comparison,\n * not the full planned-emit list). Used by `noir sync --check`. */\n check?: boolean;\n /** Policy for a `regenerate` file that exists and DIFFERS from the\n * template, when no {@link onConflict} callback is provided. Default\n * `'overwrite'` is byte-backward-compatible. `'preserve'` keeps the user's\n * file (the non-TTY / CI default in the cli). */\n conflictPolicy?: 'overwrite' | 'preserve';\n /** Per-file conflict resolver — the UI seam (the engine stays UI-free;\n * the cli injects a @clack-based resolver). Called when a `regenerate`\n * file exists and differs from the template. The return type widens to\n * accept a rich shape carrying `applyToAll` — the engine then reuses the\n * decision for the rest of the run (per artifact CLASS). */\n onConflict?: (ctx: ConflictContext) => Promise<ConflictResolverReturn> | ConflictResolverReturn;\n /** Three-way merge managed regions (base/ours/theirs) using a persisted\n * ancestor snapshot (`.noir/ancestors.json`) instead of strip-replace, so a\n * hand-edit inside a `<!-- noir:* -->` region survives a template update.\n * DEFAULT TRUE: ancestor capture is unconditional, so the first merge\n * run always has a base. Set to `false` (CLI `--no-merge-regions`) to\n * restore strip-replace. Supports BOTH single-region (NOIR.md, ignores) AND\n * multi-region (CLAUDE.md CONTEXT+RULES) managed files — the multi-region\n * path merges managed regions per block (`managedBlocks` +\n * `mergeManagedRegion`). */\n mergeManagedRegions?: boolean;\n /** Explicit interactivity signal. The engine reads THIS (not\n * `process.env`), so a direct API/embedded caller in a TTY that does NOT\n * inject {@link onConflict} never hits a @clack prompt. The CLI sets it from\n * its global flags (via the `NOIR_NON_INTERACTIVE` bridge bin.ts owns);\n * `process.env` remains only a fallback bridge for the CLI layer. */\n interactive?: boolean;\n}\n\nexport interface ScaffoldResult {\n /** Repo-relative paths actually written. */\n written: string[];\n /** Repo-relative paths skipIfExists'd (already present). */\n skipped: string[];\n /** Repo-relative `regenerate` paths skipped because byte-identical to\n * the template (content-hash dedup — no rewrite). */\n identical: string[];\n /** Repo-relative doc-only seed paths REPLACED on the upgrade path because the\n * copy on disk was still byte-identical to an older Noir's shipped text —\n * i.e. the user never edited it, so a newer seed costs nothing. Reported\n * separately from {@link written} because the distinction is the whole point\n * of the refresh: a caller (and the user reading the summary) can tell\n * \"Noir updated documentation you never touched\" apart from \"Noir created\n * something new\".\n *\n * A seed the user DID edit is never listed here — it goes through the\n * ordinary conflict flow, so it appears in {@link written} only when a\n * resolver explicitly chose to replace it (or `--force` did). Empty on every\n * other run: a fresh `init`/`create` has nothing to compare against and\n * `sync` does not emit seeds. */\n refreshed: string[];\n /** True when the already-initialized guard short-circuited (a bare\n * `noir init`/`create` on an initialized project). Callers (init.ts/create.ts)\n * gate skills emission + the \"initialized\" message on this — a no-op must NOT\n * re-emit skills or claim it initialized. */\n noop: boolean;\n /** Migration steps executed (`<from>→<to>`), when upgrade ran. */\n migrationsRan: string[];\n /** Migration conflicts (repo-relative or `<runner>:…`), when upgrade ran. */\n migrationConflicts: string[];\n /** Repo-relative paths a migration CHANGED (wrote or refreshed), when upgrade\n * ran — reported separately from {@link refreshed} so callers can name\n * \"migrated\" files (e.g. `.noir/config.yml`) apart from doc seeds. */\n migrationChanged: string[];\n stack: StackInfo;\n projectId: string;\n fromVersion: string | null;\n toVersion: string;\n /** The host actually emitted (post-default). */\n host: HostTag;\n /** Structured conflict report — one record per file that existed AND\n * differed from the proposed bytes (regenerate conflict path). Populated in\n * `--json`/`--no-input`/non-TTY runs (no prompt fires) so a CI/JSON caller\n * can see exactly which files diverged + how they were resolved, without\n * grepping stderr. Empty when no conflicts occurred (also on first-run +\n * no-op). */\n conflicts: ConflictRecord[];\n /** Permission planned for each file this run CREATED at a non-default mode\n * (repo-relative path → mode), i.e. the {@link ManifestEntry.fileMode} of a\n * `skipIfExists` entry that had no file on disk. A real run reports the mode\n * it applied; a `dryRun` reports the mode it WOULD apply (the predictor's\n * job — the caller can preview `.noir/.env` at 0600 without writing it).\n * Paths the entry skipped (already present) are absent: an existing file's\n * mode is never changed. Optional so existing external constructors of this\n * type (test stubs) stay valid; `scaffold()` always populates it. */\n fileModes?: Record<string, number>;\n /** What the run did to the permissions of `.noir/.env` — the outcome of\n * {@link ensureOwnerOnly}, which is called on every run that may write, in\n * every mode, because the seeded file's 0600 mode is applied only when it is\n * created.\n * `'healed'` means a group/world-readable file was tightened to 0600, so a\n * caller can report the fix instead of leaving the user to notice it in a\n * later diagnostic; `'unchanged'` and `'unsupported'` mean nothing needed\n * doing (or the platform has no POSIX mode bits) — the `'unchanged'` a fresh\n * `create` reports, whose seed was written 0600 in the same run. Absent on a\n * dry run, which must not touch disk. Optional so existing external\n * constructors of this type stay valid. */\n envMode?: EnvMode;\n}\n\n/** One entry in {@link ScaffoldResult.conflicts}. */\nexport interface ConflictRecord {\n /** Repo-relative path of the conflicting file. */\n path: string;\n /** Artifact class — drives apply-to-all memory scope + report grouping. */\n mode: ConflictContext['mode'];\n /** LCS similarity (0-1) between existing and proposed. */\n similarity?: number;\n /** sha256 (hex, first 12 chars) of the on-disk bytes at conflict time. */\n existingSha: string;\n /** sha256 (hex, first 12 chars) of the proposed bytes. */\n proposedSha: string;\n /** Resolution the engine applied (`replace`/`preserve`/`rename`/…). */\n resolution: ConflictResolution;\n}\n\n/** Context passed to {@link ScaffoldOptions.onConflict} when a\n * `regenerate` file exists and differs from the template. */\nexport interface ConflictContext {\n /** Repo-relative path of the conflicting file. */\n relPath: string;\n /** The file's current on-disk content. */\n existing: string;\n /** The content the scaffold would write. */\n proposed: string;\n /** Artifact class — drives apply-to-all memory scope (`regenerate` shares\n * one decision across a run; `managedBlock`/`managedBlocks` stay per-file).\n * Defaults to `'regenerate'` for backward compatibility with older resolvers\n * that pre-date the field (the engine always sets it). */\n mode?:\n | 'regenerate'\n | 'managedBlock'\n | 'managedBlocks'\n | 'skipIfExists'\n | 'skill'\n | 'markdown'\n | 'artifact';\n /** LCS similarity (0-1) between existing and proposed. Cheap signal for\n * the resolver to bias toward `replace` when ~1.0 or `preserve` when ~0. */\n similarity?: number;\n /** When the 3-way merge of a managed region hit an overlap, the merged\n * bytes WITH zdiff3 conflict markers (selecting `'merge'` writes this). */\n mergedWithMarkers?: string;\n}\n\n/** How to resolve a `regenerate` conflict. */\nexport type ConflictResolution =\n | 'replace'\n | 'preserve'\n | 'rename'\n | 'duplicate'\n | 'cancel'\n | 'merge';\n\n/** The resolver may return a bare {@link ConflictResolution} (scope\n * `'one'`) OR a rich shape carrying `applyToAll`. The engine unwraps both; when\n * `applyToAll` is true it stores the choice in its per-run memory keyed by\n * artifact CLASS (`regenerate` shares one decision across a run; managedBlock\n * stays per-file regardless — user edits there need individual review). */\nexport type ConflictResolverReturn =\n | ConflictResolution\n | { resolution: ConflictResolution; applyToAll?: boolean };\n\n/** A partial emit — the two contexts that re-emit a SUBSET of the manifest.\n * A fresh `init`/`create` (and `--force`) is neither: it emits every mode, so\n * {@link EMITTED_IN} is only consulted for these. */\ntype EmitContext = 'sync' | 'upgrade';\n\n/** Which emit contexts a write mode participates in. Per-mode (never\n * special-cased by path), so a new manifest entry inherits the right behavior\n * from its mode alone.\n *\n * - `sync` re-emits the **runtime subset** — the always-safe-to-rewrite\n * entries (`regenerate` + `managedBlock`). `skipIfExists` is excluded\n * because sync is not a scaffold event: it refreshes the host pointers, it\n * does not (re)create a project's seeds.\n * - `upgrade` (`noir init --upgrade`) re-emits the runtime subset PLUS\n * `skipIfExists`, which normally creates only when the file is ABSENT — so\n * an upgrade backfills a seed added to the manifest after a project was\n * initialized while remaining unable to touch a file the user owns. The one\n * entry that goes further is a doc-only seed flagged `refreshIfStale`, whose\n * untouched older copy is replaced (see {@link ManifestEntry}).\n * `mergeJson` stays OUT: `.claude/settings.local.json` is user-owned and\n * write-ONCE by its own contract (init/create only), and the merge re-appends\n * the SessionStart hook whenever the dedup substring is gone — i.e. an\n * upgrade emit would resurrect a hook the user deliberately removed. */\nconst EMITTED_IN: Record<WriteMode, { sync: boolean; upgrade: boolean }> = {\n regenerate: { sync: true, upgrade: true },\n managedBlock: { sync: true, upgrade: true },\n skipIfExists: { sync: false, upgrade: true },\n mergeJson: { sync: false, upgrade: false },\n};\n\n/**\n * Refuse to scaffold when `root` is — or is inside — a `.noir/` directory.\n * Running `noir init`/`create`/`sync` while cwd = `.noir/` would\n * otherwise mint a FRESH project id (because `<root>/.noir/project.id` is\n * absent) and build a NESTED second project (`.noir/.noir/`, `.noir/CLAUDE.md`,\n * `.noir/.claude/skills/`, …) — the duplicate-`.noir` bug. The walk ascends the\n * ancestor chain only, so a legitimate project root that merely CONTAINS a\n * `.noir/` store is never flagged. String-based: works whether or not `root`\n * exists on disk yet (so `noir create <new-dir>` is still guarded).\n *\n * Hard against literal `.noir` path segments — NOT bypassable by `--force`\n * (which is reserved for the already-initialized no-op). NOTE: the walk is\n * string-based, so a symlink whose TARGET is inside `.noir/` is not resolved\n * (a local self-foot-gun only — the caller controls `root` — not externally\n * exploitable); in practice `--cwd`/positional roots are literal paths.\n */\nexport function assertSafeRoot(root: string): void {\n let cur = root;\n for (let i = 0; i < 64; i++) {\n if (basename(cur) === '.noir') {\n throw new Error(\n `Refusing to scaffold inside a .noir/ directory (${root}). Run \\`noir init\\` from the project root, not from inside .noir/.`,\n );\n }\n const parent = dirname(cur);\n if (parent === cur) break; // reached the filesystem root\n cur = parent;\n }\n}\n\n/** The outcome of re-asserting the owner-only mode on `.noir/.env` for this\n * run, or `undefined` when the run must not touch disk (`dryRun`).\n *\n * EVERY mode re-asserts, `create` included. The seeded file's 0600 is applied\n * by the writer only when the file is CREATED (`skipIfExists` never opens an\n * existing one), so the mode can only be trusted on a run that checks the file\n * as it finds it — and `create` reaches an existing tree too: `--force` (or a\n * plain re-run) over a project initialized earlier is the same `.noir/.env`\n * that `init` would find, and it can already be sitting at 0644. Gating the\n * heal to `init`/`sync` on the grounds that \"create writes its seed 0600 in the\n * same run\" holds only for a FRESH tree. Including `create` costs nothing on\n * that fresh tree — the file is absent or already 0600, so the heal is a no-op\n * — and closes the gap on a forced one.\n *\n * Called from both exits — the already-initialized no-op and the end of the\n * emit — because a bare `noir init` on an existing project re-emits nothing\n * but must still fix a credential file left at a wider mode by an older\n * version. Only one of the two runs per invocation. */\nfunction envOwnerOnly(opts: ScaffoldOptions): EnvMode | undefined {\n if (opts.dryRun === true) return undefined;\n // Through the layout helper, like every other `.noir/` path: the directory\n // name lives in one place, so a rename cannot leave this file behind.\n return ensureOwnerOnly(join(paths.noirDir(opts.root), '.env'));\n}\n\n/** The configured host from an existing `.noir/config.yml`, or `'claude'`.\n * A bare re-scaffold (`create --force`) must keep the host the project already\n * chose; an absent or unreadable config degrades to the default. Mirrors\n * init.ts's resolveInitHost. */\nfunction readConfiguredHost(root: string): HostTag {\n if (!existsSync(paths.config(root))) return 'claude';\n try {\n return loadProjectInfo(root).config.host;\n } catch {\n return 'claude';\n }\n}\n\n/** The `rules.enabled` switch from an existing `.noir/config.yml`, or `true`.\n * A project with no config yet (a fresh `init`/`create`) has not opted out of\n * anything, and an absent, unreadable or invalid config degrades to the schema\n * default — so the working-rules seed keeps being emitted exactly as it always\n * was.\n *\n * Reads the config directly rather than through {@link loadProjectInfo}, which\n * also demands a canonical project id: the first `init` over a config the user\n * wrote by hand is exactly the state in which the switch has to be honoured,\n * and it has no id yet. */\nfunction readConfiguredRulesEnabled(root: string): boolean {\n return readProjectConfig(root)?.rules.enabled ?? true;\n}\n\nexport async function scaffold(opts: ScaffoldOptions): Promise<ScaffoldResult> {\n // Root-safety: refuse to scaffold at/inside a .noir/ directory BEFORE\n // any write (incl. `create`'s target mkdir). Prevents the nested .noir/.noir/\n // re-init bug. Hard guard; not bypassable.\n assertSafeRoot(opts.root);\n\n // The host a bare re-scaffold emits for. `init` resolves the host itself and\n // always passes it; `create` may omit it, so a `--force`/re-run over an\n // existing project keeps the host that project already configured rather than\n // falling back to the default and emitting artifacts for the wrong host.\n // Mirrors init.ts's resolveInitHost: explicit > configured > 'claude'.\n const host: HostTag = opts.host ?? readConfiguredHost(opts.root);\n const transport: BuildManifestContext['transport'] = opts.transport ?? 'stdio';\n if (transport === 'streamable-http' && !opts.url) {\n throw new Error(\"transport 'streamable-http' requires opts.url\");\n }\n\n // 1. Root for `create` may not exist yet — mirror `init.ts`'s mkdir of .noir/.\n if (opts.mode === 'create' && !opts.dryRun) {\n mkdirSync(opts.root, { recursive: true });\n }\n\n // 2. Resolve project id. Read the on-disk stamp ONCE and reuse the result\n // for the corrupt-file heal below. sync requires a VALID existing id.\n const idFile = readProjectIdFile(opts.root);\n const projectId = resolveProjectId(opts, idFile);\n\n // A `project.id` that EXISTS but is empty/unparseable is CORRUPT, not\n // absent. The manifest writes project.id via `skipIfExists`, which would\n // preserve the empty file while NOIR.md's BRIEF_BLOCK renders the freshly\n // resolved/generated id → silent identity split (NOIR.md states an id the\n // store DB can't open). project.id is Noir-owned canonical; heal a corrupt\n // stamp by removing it so `skipIfExists` writes the resolved id fresh.\n // Absent/valid files and dryRun are left to the manifest writer.\n if (!opts.dryRun && idFile.state === 'corrupt') {\n rmSync(paths.projectId(opts.root), { force: true });\n }\n\n const fromVersion = readScaffoldVersion(opts.root);\n\n // 3. Stack detect (read-only, never throws). Always populated so callers\n // (TUI, doctor) get a single source of truth regardless of mode.\n const stack = detectStack(opts.root);\n // Compile the derivable baseline (stack + canonical commands + entry\n // points) into the markdown the `.noir/NOIR.md` brief block renders.\n const baseline = renderBaseline(stack, detectCommands(opts.root), detectEntryPoints(opts.root));\n\n // Ancestor snapshot is read + captured UNCONDITIONALLY (not just under\n // --merge) so the first merge run — now the DEFAULT — always has a base. The\n // merge APPLICATION still keys off `mergeManagedRegions` below; ancestor\n // CAPTURE is always-on. `writeAncestors` at the end dedups internally.\n const ancestors = readAncestors(opts.root);\n // mergeManagedRegions defaults to TRUE. `--no-merge-regions`\n // (CLI) restores strip-replace by setting opts.mergeManagedRegions = false.\n const mergeManagedRegions = opts.mergeManagedRegions !== false;\n\n // Widened no-op guard (was: scaffold-version present ONLY). A bare\n // `noir init`/`noir create` is a NO-OP when the project carries EITHER a\n // `.noir/scaffold-version` stamp (1.3.0+) OR a `.noir/project.id` (pre-1.3.0\n // legacy). Previously a legacy project (id present, no stamp) re-scaffolded\n // on every bare init; now it no-ops too. `--upgrade` stays the explicit\n // migration entry (a stamp-less legacy project runs the full chain from\n // `0.0.0`); `--force` re-scaffolds without migrating. Both bypass this guard.\n // sync is unaffected (it requires a valid project.id and emits the runtime subset).\n // dryRun returns the no-op shape silently so `noir doctor`/CI previews stay\n // clean.\n const hasIdentity = fromVersion !== null || idFile.state === 'valid';\n if (\n hasIdentity &&\n opts.upgrade !== true &&\n opts.force !== true &&\n (opts.mode === 'init' || opts.mode === 'create')\n ) {\n if (opts.dryRun !== true) {\n const where =\n fromVersion !== null\n ? `at scaffold ${fromVersion}`\n : '(pre-1.3.0 project; no scaffold-version stamp)';\n process.stderr.write(\n `Noir already initialized in ${opts.root} ${where}; run \\`noir init --upgrade\\` to migrate.\\n`,\n );\n }\n return {\n written: [],\n skipped: [],\n identical: [],\n refreshed: [],\n conflicts: [],\n fileModes: {},\n envMode: envOwnerOnly(opts),\n noop: true,\n migrationsRan: [],\n migrationConflicts: [],\n migrationChanged: [],\n stack,\n projectId,\n fromVersion,\n toVersion: CURRENT_SCAFFOLD_VERSION,\n host,\n };\n }\n\n // 4. Migrations (only when explicitly upgrading). A project with NO identity\n // at all — no scaffold-version stamp AND no valid project.id — is a fresh,\n // never-initialized tree: nothing to migrate, so skip entirely and report\n // no steps. A stamped project runs from its recorded version. A legacy\n // project (a valid project.id but no stamp) passes `null` through; the\n // runner treats `null` as `0.0.0`, so the full registered chain runs.\n const migrationsRan: string[] = [];\n const migrationConflicts: string[] = [];\n const migrationChanged: string[] = [];\n if (\n opts.mode === 'init' &&\n opts.upgrade === true &&\n (fromVersion !== null || idFile.state === 'valid')\n ) {\n const m = runMigrations(opts.root, fromVersion, CURRENT_SCAFFOLD_VERSION, {\n dryRun: opts.dryRun === true,\n // The migration runs BEFORE the manifest, so it cannot see the manifest's\n // own rules-gating. Hand the switch through so a migration that would\n // rewrite the working-rules seed (the doc-seed refresh keyed on recorded\n // older bytes) leaves a switch-off project's RULES.md alone.\n rulesEnabled: readConfiguredRulesEnabled(opts.root),\n });\n migrationsRan.push(...m.ran);\n migrationConflicts.push(...m.conflicts);\n migrationChanged.push(...m.changed);\n }\n\n // 5. Build manifest + filter by host + mode.\n // Resolve the MCP `command`: the absolute native shim when a native install\n // is detected (GUI MCP clients don't read shell profiles → `spawn noir\n // ENOENT`), else `'noir'`. Read-only + never throws; defaults to 'noir'.\n const command = resolveNoirCommand();\n const manifest = buildManifest({\n root: opts.root,\n projectId,\n host,\n transport,\n url: opts.url,\n command,\n stack,\n // The project's own working-rules switch: a project that set\n // `rules.enabled: false` gets no `.noir/rules/RULES.md` — not on the first\n // scaffold, and not backfilled by an upgrade. Unchanged (enabled) whenever\n // the config is absent or says nothing about rules.\n rulesEnabled: readConfiguredRulesEnabled(opts.root),\n });\n // Which subset this run emits — `null` means every mode (a fresh\n // `init`/`create`, and `--force`). `sync` re-emits the runtime subset;\n // `init --upgrade` additionally re-emits `skipIfExists`, whose\n // create-only-if-absent semantics are exactly what backfills a seed added to\n // the manifest after a project was initialized — the regression where\n // `.noir/.env.example` was reachable by no command at all. On top of that,\n // an upgrade is the only way to reach the doc-seed refresh: an entry flagged\n // `refreshIfStale` has its existing file READ and compared against the bytes\n // Noir used to ship, so a copy the user never touched gets the newer text.\n // Both branches keep the same guarantee for everything else — an unflagged\n // entry goes through the writer that does not open an existing file at all,\n // and a flagged seed the user edited only ever reaches the conflict flow.\n // `--upgrade` still excludes `mergeJson` — see {@link EMITTED_IN}.\n const emitContext: EmitContext | null =\n opts.mode === 'sync'\n ? 'sync'\n : opts.mode === 'init' && opts.upgrade === true\n ? 'upgrade'\n : null;\n const vars: BuildManifestContext = {\n root: opts.root,\n projectId,\n host,\n transport,\n url: opts.url,\n command,\n baseline,\n };\n\n const written: string[] = [];\n const skipped: string[] = [];\n const identical: string[] = [];\n const refreshed: string[] = [];\n const fileModes: Record<string, number> = {};\n // Per-run conflict memory + structured report. Memory is keyed by\n // artifact CLASS for `regenerate` (one decision shared across the run, so a\n // `noir init --upgrade` over N pointers → 1 prompt) and by per-file path for\n // `managedBlock`/`managedBlocks` (user edits there need individual review).\n const conflictMemory = new Map<string, ConflictResolution>();\n const conflictRecords: ConflictRecord[] = [];\n const recordConflict = (\n relPath: string,\n mode: NonNullable<ConflictContext['mode']>,\n existing: string,\n proposed: string,\n resolution: ConflictResolution,\n similarity?: number,\n ): void => {\n conflictRecords.push({\n path: relPath,\n mode,\n similarity,\n existingSha: sha256Hex12(existing),\n proposedSha: sha256Hex12(proposed),\n resolution,\n });\n };\n\n // GROUP applicable entries by target path (manifest order preserved within\n // each group) so files carrying MULTIPLE managed blocks (CLAUDE.md today =\n // CONTEXT + RULES) get ONE atomic multi-region write. Single-entry\n // paths keep the existing per-entry writer — byte-stable for the NOIR.md\n // brief, the ignore files, and the regenerated `.mcp.json`. dryRun uses the\n // SAME grouping so its reported paths match what a real run would write\n // (CLAUDE.md reported once, not twice).\n const groups = groupApplicableByPath(manifest, host, emitContext);\n for (const [relPath, entries] of groups) {\n const abs = join(opts.root, relPath);\n const managed = entries.filter((e) => e.mode === 'managedBlock');\n\n if (managed.length >= 2) {\n // Multi-managed-block file: ONE atomic write of all regions.\n if (opts.dryRun) {\n if (opts.check === true) {\n // Drift check: compare the would-be merged bytes without writing.\n const onDisk = readOptional(abs);\n const regions = managed.map((e) => {\n const block = e.block;\n if (!block) {\n throw new Error(`manifest entry ${e.path}: managedBlock mode missing 'block'`);\n }\n const theirs = buildRegion(block, renderEntry(e, vars));\n const merged = mergeManagedRegions\n ? mergeManagedRegion(abs, e.path, block, theirs, ancestors)\n : { text: theirs, conflict: false, cleanTheirs: theirs };\n return { block, regionText: merged.text };\n });\n const predicted = predictManagedBlocks(onDisk ?? '', regions);\n if (onDisk !== undefined && predicted === onDisk) identical.push(relPath);\n else written.push(relPath);\n continue;\n }\n written.push(relPath);\n continue;\n }\n mkdirSync(dirname(abs), { recursive: true });\n const onDisk = readOptional(abs);\n let hadMergeConflict = false;\n const regions = managed.map((e) => {\n const block = e.block;\n if (!block) {\n throw new Error(`manifest entry ${e.path}: managedBlock mode missing 'block'`);\n }\n const theirs = buildRegion(block, renderEntry(e, vars));\n const merged = mergeManagedRegions\n ? mergeManagedRegion(abs, e.path, block, theirs, ancestors)\n : { text: theirs, conflict: false, cleanTheirs: theirs };\n if (merged.conflict) hadMergeConflict = true;\n // Capture ancestor unconditionally (pre-write snapshot) so a later\n // merge run has a base even if this run was strip-replace.\n ancestors[`${e.path}::${block.begin}`] = theirs;\n return { block, regionText: merged.text };\n });\n // Managed-region conflict (per-file; never apply-to-all). Hand the\n // resolver the merged-with-markers bytes (the 6th \"merge\" option's\n // payload). Default behavior when no resolver is wired (or non-interactive):\n // write the merged-with-markers bytes + the stderr note (unchanged).\n if (hadMergeConflict) {\n const ctxMode: NonNullable<ConflictContext['mode']> = 'managedBlocks';\n const resolution = await resolveManagedConflict(relPath, ctxMode, opts);\n if (resolution === 'cancel') {\n throw new Error(`scaffold cancelled by user at conflicting file ${relPath}`);\n }\n // 'merge' / undefined → write the marked bytes (current behavior).\n if (resolution !== undefined && resolution !== 'merge') {\n // 'preserve' → skip the write entirely (user's overlap stands).\n // 'replace'/'duplicate'/'rename' → unsupported for managedBlocks\n // (managed regions are inside co-owned files); treat as 'merge'.\n process.stderr.write(\n `noir: managed-region conflict in ${relPath} — wrote inline markers; resolve manually.\\n`,\n );\n } else {\n process.stderr.write(\n `noir: managed-region conflict in ${relPath} — wrote inline markers; resolve manually.\\n`,\n );\n }\n }\n // Content-hash dedup: if the would-be-written bytes equal the on-disk\n // file, skip the write entirely (no mtime/git churn). `noir sync` on an\n // unchanged tree writes NOTHING.\n const predicted = predictManagedBlocks(onDisk ?? '', regions);\n if (onDisk !== undefined && predicted === onDisk) {\n identical.push(relPath);\n } else {\n managedBlocks(abs, regions);\n written.push(relPath);\n }\n continue;\n }\n\n // Per-entry path: single-region managed / regenerate / skipIfExists.\n if (!opts.dryRun) mkdirSync(dirname(abs), { recursive: true });\n for (const entry of entries) {\n if (opts.dryRun) {\n if (opts.check === true && (entry.mode === 'regenerate' || entry.mode === 'managedBlock')) {\n // Drift check: compare the would-be bytes without writing.\n const body = renderEntry(entry, vars);\n const onDisk = readOptional(abs);\n if (entry.mode === 'regenerate') {\n if (onDisk === undefined || onDisk !== body) written.push(entry.path);\n else identical.push(entry.path);\n } else {\n const block = entry.block;\n if (!block) {\n throw new Error(`manifest entry ${entry.path}: managedBlock mode missing 'block'`);\n }\n const theirs = buildRegion(block, body);\n const merged = mergeManagedRegions\n ? mergeManagedRegion(abs, entry.path, block, theirs, ancestors)\n : { text: theirs, conflict: false, cleanTheirs: theirs };\n const predicted = predictManagedBlock(onDisk ?? '', block, merged.text);\n if (onDisk !== undefined && predicted === onDisk) identical.push(entry.path);\n else written.push(entry.path);\n }\n continue;\n }\n if (entry.mode !== 'skipIfExists') {\n written.push(entry.path);\n continue;\n }\n const seedKind = refreshableSeedKind(entry, emitContext);\n if (seedKind === undefined) {\n if (existsSync(abs)) {\n skipped.push(entry.path);\n } else {\n written.push(entry.path);\n // Dry-run predictor: report the mode a real run WOULD apply,\n // without writing (`.noir/.env` → 0600).\n if (entry.fileMode !== undefined) fileModes[entry.path] = entry.fileMode;\n }\n continue;\n }\n // Refreshable seed. The comparison needs the current render, so this is\n // the one dry-run case that renders a body — a preview that reported a\n // different outcome from the run it previews would be worse than none.\n // An edited seed reads as \"already present\": the interactive conflict\n // prompt cannot run under a preview, and leaving the user's bytes alone\n // is what the same run does without a resolver.\n const plan = planSeedWrite(abs, seedKind, renderEntry(entry, vars));\n if (plan === 'create') {\n written.push(entry.path);\n if (entry.fileMode !== undefined) fileModes[entry.path] = entry.fileMode;\n } else if (plan === 'refresh') {\n refreshed.push(entry.path);\n } else {\n skipped.push(entry.path);\n }\n continue;\n }\n const body = renderEntry(entry, vars);\n if (entry.mode === 'regenerate') {\n const out = await writeWithConflict(abs, entry.path, body, opts, {\n memory: conflictMemory,\n record: recordConflict,\n });\n written.push(...out.written);\n skipped.push(...out.skipped);\n identical.push(...out.identical);\n } else if (entry.mode === 'managedBlock') {\n const block = entry.block;\n if (!block) {\n throw new Error(`manifest entry ${entry.path}: managedBlock mode missing 'block'`);\n }\n // A legacy .noir/NOIR.md (written before the artifact table shipped) is\n // a whole-file auto-brief with NO managed markers. The normal path\n // would treat the old brief as user content and append a SECOND managed\n // brief → two \"Project\n // id:\" lines. Self-heal: when the existing file has NO noir managed\n // marker at all, wipe it first so the managed write emits a clean\n // single brief. (That legacy NOIR.md was 100% auto-generated, so\n // there is no user content to preserve in that legacy shape.)\n if (isNoirMdPath(entry.path)) healLegacyNoirMd(abs);\n const theirs = buildRegion(block, body);\n const merged = mergeManagedRegions\n ? mergeManagedRegion(abs, entry.path, block, theirs, ancestors)\n : { text: theirs, conflict: false, cleanTheirs: theirs };\n const regionText = merged.text;\n // Capture ancestor unconditionally (pre-write snapshot).\n ancestors[`${entry.path}::${block.begin}`] = theirs;\n // Managed-region conflict (per-file; never apply-to-all). Hand the\n // resolver the merged-with-markers bytes (the 6th \"merge\" option's\n // payload). Default behavior when no resolver is wired (or\n // non-interactive): write the merged-with-markers bytes + the\n // stderr note (unchanged from v1.2).\n if (merged.conflict) {\n const ctxMode: NonNullable<ConflictContext['mode']> = 'managedBlock';\n const onDiskRegion = readOptional(abs);\n const ctx: ConflictContext = {\n relPath: entry.path,\n existing: onDiskRegion ?? '',\n proposed: theirs,\n mode: ctxMode,\n similarity: similarity(onDiskRegion ?? '', merged.text),\n mergedWithMarkers: merged.text,\n };\n const resolution = await resolveManagedConflictCtx(ctx, opts);\n if (resolution === 'cancel') {\n throw new Error(`scaffold cancelled by user at conflicting file ${entry.path}`);\n }\n if (resolution === 'preserve') {\n identical.push(entry.path); // user's overlap stands; skip the write.\n continue;\n }\n process.stderr.write(\n `noir: managed-region conflict in ${entry.path} — wrote inline markers; resolve manually.\\n`,\n );\n }\n // Content-hash dedup (post-heal: re-read, the heal may have wiped).\n // If the would-be-written bytes equal the on-disk file, skip the write\n // (no mtime/git churn) — a no-op `noir sync` writes NOTHING.\n const onDisk = readOptional(abs);\n const predicted = predictManagedBlock(onDisk ?? '', block, regionText);\n if (onDisk !== undefined && predicted === onDisk) {\n identical.push(entry.path);\n } else {\n managedBlock(abs, block, regionText);\n written.push(entry.path);\n }\n } else if (entry.mode === 'mergeJson') {\n // SessionStart hook: merge-aware JSON write (preserves the user's\n // permissions/env/enabledPlugins, appends the hook entry once, deduped\n // by command substring). `body` is the rendered JSON patch; the dedup\n // marker comes from the entry.\n const patch = JSON.parse(body) as Record<string, unknown>;\n const out = mergeJson(abs, patch, entry.dedupSubstring);\n if (out.written) written.push(entry.path);\n else identical.push(entry.path);\n } else {\n // `skipIfExists`: create the seed when it is absent, otherwise leave it\n // untouched. A refreshable doc seed is the one exception, and only on\n // an upgrade — see `refreshableSeedKind`.\n const seedKind = refreshableSeedKind(entry, emitContext);\n if (seedKind === undefined) {\n // `entry.fileMode` rides along so a created `.noir/.env` is 0600 from\n // the moment it exists — see `skipIfExists`.\n const out = skipIfExists(abs, body, entry.fileMode);\n if (out.written) {\n written.push(entry.path);\n if (entry.fileMode !== undefined) fileModes[entry.path] = entry.fileMode;\n } else {\n skipped.push(entry.path);\n }\n continue;\n }\n const plan = planSeedWrite(abs, seedKind, body);\n if (plan === 'create') {\n skipIfExists(abs, body, entry.fileMode);\n written.push(entry.path);\n if (entry.fileMode !== undefined) fileModes[entry.path] = entry.fileMode;\n } else if (plan === 'refresh') {\n // The bytes on disk are an unedited copy of an older seed, so the\n // newer text costs the user nothing. The file's mode is left as it is.\n refreshSeed(abs, body);\n refreshed.push(entry.path);\n } else if (plan === 'keep') {\n skipped.push(entry.path);\n } else {\n // Edited by the user, so it is theirs: run the same conflict flow the\n // other write modes use rather than replacing anything silently. The\n // artifact class is `skipIfExists`, which scopes an \"apply to all\"\n // answer to the seeds alone — a decision about these doc files must\n // not ride along into `.mcp.json`'s pointer-file conflicts.\n const out = await writeWithConflict(abs, entry.path, body, opts, {\n memory: conflictMemory,\n record: recordConflict,\n artifactClass: 'skipIfExists',\n // No resolver answered (no TTY, `--no-input`, CI): keep the user's\n // file. An explicit overwrite policy is an instruction rather than a\n // missing answer, so `--force` still replaces.\n defaultResolution: opts.conflictPolicy === 'overwrite' ? 'replace' : 'preserve',\n });\n written.push(...out.written);\n skipped.push(...out.skipped);\n identical.push(...out.identical);\n }\n }\n }\n }\n\n // 6. Stamp scaffold-version on init/create (NOT sync). Written last so a\n // crash leaves the previous stamp. Upgrade rewrites it to current — but\n // ONLY when every migration actually applied. A non-empty\n // `migrationConflicts` means a transformation did NOT land, so stamping\n // \"current\" would make that failure permanent and invisible: the migration\n // would never retry (the window closes) and `noir doctor` would stop\n // reporting drift. Leaving the OLD stamp keeps both honest — the retry and\n // the drift report. The conflicts themselves are already reported to the\n // caller via `ScaffoldResult.migrationConflicts` (`--json` included).\n if (\n (opts.mode === 'init' || opts.mode === 'create') &&\n !opts.dryRun &&\n migrationConflicts.length === 0\n ) {\n writeScaffoldVersion(opts.root, CURRENT_SCAFFOLD_VERSION);\n }\n\n // Persist the ancestor snapshot UNCONDITIONALLY (every init/create/sync\n // seeds it) so the first merge run — now the default — has a base.\n // `writeAncestors` dedups internally: when the serialized bytes equal the\n // on-disk file, the rewrite is skipped, so a no-op sync leaves ancestors.json\n // untouched too (true zero-write idempotency).\n if (!opts.dryRun) {\n writeAncestors(opts.root, ancestors);\n }\n\n return {\n written,\n skipped,\n identical,\n refreshed,\n conflicts: conflictRecords,\n fileModes,\n // Re-asserted here, after the emit, so the mode on disk is the one this run\n // leaves behind: the seed writer only applies 0600 at creation (see\n // `envOwnerOnly`).\n envMode: envOwnerOnly(opts),\n noop: false,\n migrationsRan,\n migrationConflicts,\n migrationChanged,\n stack,\n projectId,\n fromVersion,\n toVersion: CURRENT_SCAFFOLD_VERSION,\n host,\n };\n}\n\n/** LCS-based similarity ratio in [0,1]. 1.0 = byte-identical lines, 0.0 =\n * wholly disjoint. Cheap signal the resolver can use to bias toward `replace`\n * when ~1 (cosmetic drift) vs `preserve` when ~0 (substantive edit). Reuses\n * the SAME LCS as {@link mergeThreeWay} so there is one line-diff algorithm. */\nfunction similarity(a: string, b: string): number {\n if (a === b) return 1;\n const A = a.split('\\n');\n const B = b.split('\\n');\n if (A.length === 0 && B.length === 0) return 1;\n // `lcsMatch` returns the paired anchor indices; |anchors| / max(len) is the\n // similarity. Re-derive here (without importing merge.ts) so the engine stays\n // decoupled from the merge module's diff3 specifics.\n const n = A.length;\n const m = B.length;\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0));\n for (let i = n - 1; i >= 0; i--) {\n const dpi = dp[i] ?? [];\n const dpi1 = dp[i + 1] ?? [];\n const ai = A[i] ?? '';\n for (let j = m - 1; j >= 0; j--) {\n const bj = B[j] ?? '';\n dpi[j] = ai === bj ? (dpi1[j + 1] ?? 0) + 1 : Math.max(dpi1[j] ?? 0, dpi[j + 1] ?? 0);\n }\n }\n const lcs = dp[0]?.[0] ?? 0;\n return n + m === 0 ? 1 : (2 * lcs) / (n + m);\n}\n\n// --- helpers -----------------------------------------------------------------\n\n/** Three-way merge a managed region against the persisted ancestor\n * (`base`). `theirs` is the freshly-rendered template region (with markers);\n * `ours` is the region currently on disk. With no ancestor / no existing\n * region this is a no-op (returns `theirs`). On conflict, returns the merged\n * bytes WITH zdiff3 markers as `text` AND `conflict: true`, so the per-file\n * scaffold site can hand the resolver a 6th \"merge (with conflict markers)\"\n * option. `cleanTheirs` is the un-merged template — what the\n * `'replace'` resolution would write. */\nfunction mergeManagedRegion(\n abs: string,\n relPath: string,\n block: ManagedBlock,\n theirs: string,\n ancestors: Record<string, string>,\n): { text: string; conflict: boolean; cleanTheirs: string } {\n const base = ancestors[`${relPath}::${block.begin}`];\n if (base === undefined) return { text: theirs, conflict: false, cleanTheirs: theirs };\n const ours = readManagedBlock(abs, block);\n if (ours === null) return { text: theirs, conflict: false, cleanTheirs: theirs };\n // `readManagedBlock` returns the matched region WITHOUT the trailing `\\n`\n // after `end`, while `buildRegion`/`theirs` INCLUDE it. Feeding the raw `ours`\n // into mergeThreeWay makes every merge return a region missing that newline,\n // so the writer would drop it on each sync (byte drift) AND the content-hash\n // dedup could never fire (predicted !== onDisk by one `\\n`). Normalize `ours`\n // to `theirs`'s shape (always end with `\\n`) so an unchanged region stays\n // byte-identical and `noir sync` on an unchanged tree is a true no-op.\n const oursNormalized = ours.endsWith('\\n') ? ours : `${ours}\\n`;\n // zdiff3 markers so the resolver's 6th option shows the base section.\n const res = mergeThreeWay(base, oursNormalized, theirs, 'zdiff3');\n return { text: res.merged, conflict: res.conflict, cleanTheirs: theirs };\n}\n\n/** Consult {@link ScaffoldOptions.onConflict} for a managed-region merge\n * conflict (single-block path). Per-file (never apply-to-all — user edits\n * inside a `<!-- noir:* -->` region need individual review). When no resolver\n * is wired OR the engine is non-interactive, returns `undefined` so the caller\n * falls through to the earlier behavior (write the merged-with-markers bytes +\n * the stderr note). Returns the resolver's bare resolution choice. */\nasync function resolveManagedConflictCtx(\n ctx: ConflictContext,\n opts: ScaffoldOptions,\n): Promise<ConflictResolution | undefined> {\n if (opts.onConflict === undefined) return undefined;\n const ret = await opts.onConflict(ctx);\n const resolution = typeof ret === 'string' ? ret : ret.resolution;\n if (resolution === 'rename' || resolution === 'duplicate') {\n // Not meaningful for a region INSIDE a co-owned file. Treat as 'merge'\n // (write the marked bytes — the user keeps both sides, manually resolves).\n return 'merge';\n }\n if (resolution === 'replace') {\n // 'replace' on a managed region → write the clean template (clobber the\n // user's overlap inside this region). The caller writes `cleanTheirs`\n // (the un-merged template region) instead of the marked merge.\n return 'replace';\n }\n // 'merge' / 'preserve' / 'cancel' — pass through.\n return resolution;\n}\n\n/** Consult {@link ScaffoldOptions.onConflict} for a managed-region merge\n * conflict (multi-block path). Simpler than the single-block path: the\n * multi-block file is written atomically as ONE unit, so 'rename'/'duplicate'\n * /'replace' have no clean meaning (the regions live alongside user content in\n * a co-owned file). Only 'merge' (write the marked bytes), 'preserve' (skip),\n * and 'cancel' (abort) are honored; others fall through to 'merge'. */\nasync function resolveManagedConflict(\n relPath: string,\n mode: NonNullable<ConflictContext['mode']>,\n opts: ScaffoldOptions,\n): Promise<ConflictResolution | undefined> {\n if (opts.onConflict === undefined) return undefined;\n const ret = await opts.onConflict({ relPath, existing: '', proposed: '', mode });\n const resolution = typeof ret === 'string' ? ret : ret.resolution;\n return resolution;\n}\n\n/** Read the `.noir/project.id` stamp ONCE and classify it for BOTH id\n * resolution and the corrupt-file heal. `absent` (ENOENT) and `valid`\n * (non-empty) are the normal cases; `corrupt` (file exists but trims to empty)\n * is healed by the orchestrator before the manifest loop so `skipIfExists`\n * writes the resolved id fresh instead of preserving the empty file. */\nfunction readProjectIdFile(\n root: string,\n): { state: 'absent'; id: null } | { state: 'valid'; id: string } | { state: 'corrupt'; id: null } {\n let raw: string;\n try {\n raw = readFileSync(paths.projectId(root), 'utf8');\n } catch {\n return { state: 'absent', id: null };\n }\n const trimmed = raw.trim();\n return trimmed.length > 0 ? { state: 'valid', id: trimmed } : { state: 'corrupt', id: null };\n}\n\nfunction resolveProjectId(\n opts: ScaffoldOptions,\n idFile: ReturnType<typeof readProjectIdFile>,\n): string {\n if (opts.projectId !== undefined) return opts.projectId;\n if (idFile.state === 'valid') return idFile.id;\n // absent OR corrupt → resolve a fresh id. sync still requires a valid\n // pre-existing id (a corrupt stamp can't be trusted to name the store DB).\n if (opts.mode === 'sync') {\n throw new Error(`Noir is not initialized in ${opts.root}. Run \\`noir init\\` first.`);\n }\n return createProjectId();\n}\n\n/** What a refreshable seed's write should do, decided from the bytes on disk.\n * Split out so the dry-run predictor and the real write path agree by\n * construction: they are the same decision, evaluated twice. */\ntype SeedPlan = 'create' | 'keep' | 'refresh' | 'conflict';\n\n/**\n * Decide what to do with a refreshable seed, from the file currently on disk:\n * - absent → `create` — the ordinary seed backfill.\n * - an exact copy of a RECORDED → `refresh` — Noir wrote these bytes and\n * older version the user never changed them.\n * - an exact copy of what Noir → `keep` — already current, nothing to do.\n * writes today\n * - present but unreadable → `keep` — the bytes cannot be compared,\n * so they cannot be shown to be Noir's.\n * - anything else → `conflict` — these are the user's bytes,\n * and only the user may replace them.\n *\n * The order matters only in that `keep` is checked after the staleness test,\n * which already excludes the current render; every non-matching byte pattern\n * falls through to `conflict`, the conservative branch.\n *\n * A read that fails for any reason other than \"no file there\" resolves to\n * `keep` rather than propagating. Deciding whether to replace a file requires\n * reading it, and writing one whose bytes could not be read is the one outcome\n * this whole path exists to prevent — so an unreadable seed (permissions, a\n * symlink loop, a directory in the way) is left exactly as it is and reported\n * as skipped, the same as any other file the run declines to touch. Letting the\n * error escape would instead abort the emit loop part-way through, leaving the\n * project half-upgraded and its version stamp unwritten.\n *\n * `conflict` is NOT the fallback for this case even though the conflict flow\n * defaults to preserving: that path reads the file itself and treats a failed\n * read as \"no file\", which would write the new seed over the unreadable one.\n */\nfunction planSeedWrite(abs: string, seed: SeedKind, currentRender: string): SeedPlan {\n let existing: string | undefined;\n try {\n existing = readOptional(abs);\n } catch {\n return 'keep';\n }\n if (existing === undefined) return 'create';\n if (isStaleSeed(seed, existing, currentRender)) return 'refresh';\n if (existing === currentRender) return 'keep';\n return 'conflict';\n}\n\n/** Which recorded seed this entry's bytes may be compared against, or\n * `undefined` when the entry is not refreshable in this run.\n *\n * Refresh is an upgrade-only behavior, and that condition lives here rather\n * than in the manifest so one place defines \"which run may replace a seed\": a\n * fresh `init`/`create` has no already-written seed to compare against (its\n * entry either creates the file or leaves it alone), and `sync` does not emit\n * seeds at all.\n *\n * Throws when a flagged entry declares no seed kind. Such an entry could only\n * ever be compared against the wrong seed's bytes, and the consequence of that\n * comparison is an overwrite — so it fails loudly instead of guessing. */\nfunction refreshableSeedKind(\n entry: ManifestEntry,\n emitContext: EmitContext | null,\n): SeedKind | undefined {\n if (emitContext !== 'upgrade' || entry.refreshIfStale !== true) return undefined;\n const kind = REFRESHABLE_SEED_KIND[entry.path];\n if (kind === undefined) {\n throw new Error(\n `manifest entry ${entry.path}: 'refreshIfStale' is set but no recorded seed is declared for that path`,\n );\n }\n return kind;\n}\n\n/**\n * Write a file whose bytes DIFFER from what is on disk, honoring conflict\n * resolution when the target already exists. Identical bytes (or a missing\n * file) write straight through (content-hash dedup is not applied at this\n * level — identical still \"writes\" today to keep sync/--upgrade byte-stable).\n * Resolution:\n * - `replace` — overwrite.\n * - `preserve`/`cancel` — keep the user's file; report skipped.\n * - `rename` — move the user's file to `<path>.local`, write the new\n * bytes in place.\n * - `duplicate` — write the new bytes to `<path>.noir`, keep the user's.\n * Returns the repo-relative paths to record as written / skipped.\n *\n * Both callers are user-owned targets that must not be clobbered on a whim: a\n * `regenerate` pointer file and a refreshable doc seed whose bytes no longer\n * match Noir's record. The two differ only in their class key and in what \"no\n * answer\" means, which is why those are arguments rather than a second copy of\n * this switch.\n */\nasync function writeWithConflict(\n abs: string,\n relPath: string,\n proposed: string,\n opts: ScaffoldOptions,\n internals: {\n /** Per-run memory keyed by artifact CLASS — populated when a resolver\n * returns `applyToAll`. */\n memory: Map<string, ConflictResolution>;\n /** Append a structured record for `ScaffoldResult.conflicts`. */\n record: (\n relPath: string,\n mode: NonNullable<ConflictContext['mode']>,\n existing: string,\n proposed: string,\n resolution: ConflictResolution,\n similarity?: number,\n ) => void;\n /** Artifact class this write belongs to. Decides the scope of an\n * \"apply to all\" answer and the label on the conflict record; defaults to\n * `'regenerate'` (a pointer file, the historical caller). */\n artifactClass?: NonNullable<ConflictContext['mode']>;\n /** Resolution to apply when there is no resolver AND the policy does not\n * already say `preserve`. Defaults to `'replace'`: a pointer file is\n * Noir-owned and exists to be re-emitted. A caller writing a file the USER\n * owns passes `'preserve'`, so an unattended run (no TTY, `--no-input`,\n * CI) leaves their bytes alone. */\n defaultResolution?: ConflictResolution;\n },\n): Promise<{ written: string[]; skipped: string[]; identical: string[] }> {\n let existing: string | undefined;\n try {\n existing = readFileSync(abs, 'utf8');\n } catch {\n existing = undefined;\n }\n if (existing === undefined) {\n regenerate(abs, proposed);\n return { written: [relPath], skipped: [], identical: [] };\n }\n if (existing === proposed) {\n // content-hash dedup: byte-identical → skip the rewrite entirely (no disk IO).\n return { written: [], skipped: [], identical: [relPath] };\n }\n // Apply-to-all memory: files of one class share a single decision across the\n // run (so a `noir init --upgrade` over N pointers → 1 prompt).\n const MODE: NonNullable<ConflictContext['mode']> = internals.artifactClass ?? 'regenerate';\n const sim = similarity(existing, proposed);\n let resolution: ConflictResolution;\n const remembered = internals.memory.get(MODE);\n if (remembered !== undefined) {\n resolution = remembered;\n } else if (opts.onConflict !== undefined) {\n const ret = await opts.onConflict({ relPath, existing, proposed, mode: MODE, similarity: sim });\n const unwrapped: ConflictResolution = typeof ret === 'string' ? ret : ret.resolution;\n if (typeof ret !== 'string' && ret.applyToAll === true) {\n internals.memory.set(MODE, unwrapped);\n }\n resolution = unwrapped;\n } else {\n resolution =\n opts.conflictPolicy === 'preserve' ? 'preserve' : (internals.defaultResolution ?? 'replace');\n }\n // Record the conflict ALWAYS (interactive or not) so `--json`/CI can see\n // exactly which files diverged + how the engine resolved them. The prompt\n // never fires under non-interactive (buildConflictOpts returns preserve w/o\n // onConflict); the record still lands.\n internals.record(relPath, MODE, existing, proposed, resolution, sim);\n switch (resolution) {\n case 'replace':\n regenerate(abs, proposed);\n return { written: [relPath], skipped: [], identical: [] };\n case 'merge': {\n // `merge` is only meaningful when ctx.mergedWithMarkers was populated\n // (managed-region path). For a bare regenerate conflict without markers\n // the resolver should not pick `merge`; defensively fall back to replace\n // (better than dropping the user's bytes).\n regenerate(abs, proposed);\n return { written: [relPath], skipped: [], identical: [] };\n }\n case 'rename': {\n // Preserve the user's file aside at a UNIQUE path. Review fix: a bare\n // `renameSync(abs, abs.local)` would silently clobber a pre-existing\n // `.local` (POSIX rename replaces) or throw EEXIST mid-scaffold (win32) —\n // the very data-loss the conflict flow exists to prevent. uniqueAside\n // picks a fresh\n // `.local` (then `.local.1`, …) so the move is always safe.\n const aside = uniqueAside(abs, relPath, '.local');\n renameSync(abs, aside.abs);\n regenerate(abs, proposed);\n return { written: [relPath], skipped: [aside.rel], identical: [] };\n }\n case 'duplicate': {\n // Write the template ALONGSIDE at a unique path; keep the user's file\n // untouched. (Same unique-suffix safeguard as `rename`.)\n const aside = uniqueAside(abs, relPath, '.noir');\n regenerate(aside.abs, proposed);\n return { written: [aside.rel], skipped: [relPath], identical: [] };\n }\n case 'preserve':\n return { written: [], skipped: [relPath], identical: [] };\n case 'cancel':\n // Review fix: Cancel ABORTS the whole scaffold. It used to fall through\n // to \"skip this file\" and keep writing the remaining entries — a contract\n // violation (Cancel/Escape must stop the run). Throwing propagates out of\n // scaffold(); the cli reports it. Entries written before this conflict\n // remain on disk, as with any cancelled operation.\n throw new Error(`scaffold cancelled by user at conflicting file ${relPath}`);\n default:\n return { written: [], skipped: [relPath], identical: [] };\n }\n}\n\n/** Pick a fresh `<abs><suffix>` aside path (plus its repo-relative form) that\n * does NOT exist: tries `<suffix>`, then `<suffix>.1`, `<suffix>.2`, … so the\n * `rename`/`duplicate` resolutions never silently overwrite a prior backup\n * (data-loss) and never hit win32 EEXIST. */\nfunction uniqueAside(abs: string, relPath: string, suffix: string): { abs: string; rel: string } {\n const make = (s: string): { abs: string; rel: string } => ({\n abs: `${abs}${s}`,\n rel: `${relPath}${s}`,\n });\n let candidate = make(suffix);\n for (let n = 1; existsSync(candidate.abs); n++) candidate = make(`${suffix}.${n}`);\n return candidate;\n}\n\n/** Group applicable manifest entries by target path, preserving manifest order\n * within each group. JS `Map` preserves insertion order, so iterating groups\n * visits paths in the same sequence the manifest declares them (CONTEXT before\n * RULES inside the CLAUDE.md group). */\nfunction groupApplicableByPath(\n manifest: readonly ManifestEntry[],\n host: HostTag,\n /** The partial emit this run performs, or `null` for \"every mode\". Modes not\n * listed in {@link EMITTED_IN} for the context are dropped here — the single\n * place the manifest subset is decided. */\n emitContext: EmitContext | null,\n): Map<string, ManifestEntry[]> {\n const groups = new Map<string, ManifestEntry[]>();\n for (const entry of manifest) {\n if (entry.host !== undefined && entry.host !== host) continue; // host filter\n if (emitContext !== null && !EMITTED_IN[entry.mode][emitContext]) continue;\n const list = groups.get(entry.path);\n if (list) list.push(entry);\n else groups.set(entry.path, [entry]);\n }\n return groups;\n}\n\n/** True for the canonical NOIR.md path the manifest emits (the BRIEF_BLOCK\n * target). Scoped so the legacy-heal only fires for that one file — we must\n * not wipe arbitrary co-owned managed files. */\nfunction isNoirMdPath(relPath: string): boolean {\n return relPath === '.noir/NOIR.md';\n}\n\n/** Self-heal: wipe a legacy whole-file NOIR.md before the managed write.\n * Legacy shape = file exists but contains NO `<!-- noir:<name> begin -->`\n * managed marker (the whole file was the auto-brief). Files that already have\n * markers, or are absent, are left untouched (normal managed-block path or\n * fresh write respectively). */\nfunction healLegacyNoirMd(absPath: string): void {\n let content: string;\n try {\n content = readFileSync(absPath, 'utf8');\n } catch {\n return; // absent — fresh write, nothing to heal\n }\n if (/<!-- noir:[a-z]+ begin -->/.test(content)) return; // already managed-shape\n rmSync(absPath, { force: true });\n}\n\nfunction renderEntry(entry: ManifestEntry, vars: BuildManifestContext): string {\n if (entry.template !== undefined) {\n return render(loadTemplate(entry.template), vars);\n }\n if (entry.content !== undefined) return entry.content;\n throw new Error(`manifest entry ${entry.path}: must define 'content' or 'template'`);\n}\n\n/** Read a file as UTF-8, returning `undefined` when it is absent (ENOENT).\n * Used by the content-hash dedup to read the on-disk bytes ONCE per managed\n * target and feed them to the predictor + the byte-equality check. Any other\n * read error re-throws (a real IO failure should not be silenced into a\n * mistaken \"identical → skip write\"). */\nfunction readOptional(absPath: string): string | undefined {\n try {\n return readFileSync(absPath, 'utf8');\n } catch (err) {\n if (err instanceof Error && (err as NodeJS.ErrnoException).code === 'ENOENT') {\n return undefined;\n }\n throw err;\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { StackInfo } from './stack-detect.js';\n\n/**\n * Project-baseline compilation: the derivable facts (`detectStack`'s stack\n * summary plus canonical commands and entry points) rendered into the short\n * markdown block the host's `@import`ed `.noir/NOIR.md` carries. Read-only and\n * total — a foreign or empty dir still renders a minimal \"unknown\" block, never\n * throws.\n */\n\n/** package.json script keys we surface as canonical commands. */\nconst COMMAND_KEYS: ReadonlyArray<[name: string, key: string]> = [\n ['build', 'build'],\n ['test', 'test'],\n ['lint', 'lint'],\n ['typecheck', 'typecheck'],\n ['format', 'format'],\n ['start', 'start'],\n ['dev', 'dev'],\n];\n\n/** Well-known entry-point files probed at the project root only. */\nconst ENTRY_POINTS: ReadonlyArray<string> = [\n 'src/index.ts',\n 'src/index.tsx',\n 'src/index.js',\n 'src/index.jsx',\n 'src/main.ts',\n 'src/main.js',\n 'src/main.py',\n 'main.ts',\n 'main.js',\n 'main.py',\n 'app/main.py',\n 'src/app.py',\n 'cmd/main.go',\n 'src/lib.rs',\n 'src/main.rs',\n];\n\n/** Canonical commands found in package.json scripts (plus Makefile targets). */\nexport function detectCommands(root: string): Record<string, string> {\n const out: Record<string, string> = {};\n const pjPath = join(root, 'package.json');\n if (existsSync(pjPath)) {\n let pj: { scripts?: Record<string, string> } | undefined;\n try {\n pj = JSON.parse(readFileSync(pjPath, 'utf8')) as { scripts?: Record<string, string> };\n } catch {\n pj = undefined;\n }\n for (const [name, key] of COMMAND_KEYS) {\n const value = pj?.scripts?.[key];\n if (typeof value === 'string' && value !== '') out[name] = value;\n }\n }\n const makefile = join(root, 'Makefile');\n if (existsSync(makefile)) {\n try {\n const raw = readFileSync(makefile, 'utf8');\n for (const name of ['build', 'test', 'lint']) {\n if (!(name in out) && new RegExp(`^${name}:`, 'm').test(raw)) out[name] = `make ${name}`;\n }\n } catch {\n // unreadable Makefile — skip\n }\n }\n return out;\n}\n\n/** Entry-point files found at the project root. */\nexport function detectEntryPoints(root: string): string[] {\n return ENTRY_POINTS.filter((p) => existsSync(join(root, p)));\n}\n\n/** Render the short baseline block, always at least one line. */\nexport function renderBaseline(\n stack: StackInfo,\n commands: Record<string, string>,\n entryPoints: string[],\n): string {\n const lang = stack.languages.length > 0 ? stack.languages.join(', ') : 'unknown';\n const framework = stack.frameworks.length > 0 ? ` (${stack.frameworks.join(', ')})` : '';\n const lines = [\n `- **Stack:** ${lang}${framework}${stack.monorepo ? ' (monorepo)' : ''}`,\n `- **Package manager:** ${stack.packageManager ?? 'none'}`,\n ];\n if (stack.ci) lines.push(`- **CI:** ${stack.ci}`);\n const entries = Object.entries(commands);\n if (entries.length > 0) {\n lines.push('- **Commands:**');\n for (const [name, command] of entries) lines.push(` - \\`${name}\\`: \\`${command}\\``);\n }\n if (entryPoints.length > 0) lines.push(`- **Entry points:** ${entryPoints.join(', ')}`);\n return lines.join('\\n');\n}\n","import { mkdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { NOIR_DIR } from '@noir-ai/core';\nimport { regenerate } from './writers.js';\n\n/**\n * Scaffold version stamp — Noir's equivalent of Copier's `last-applied`.\n * Stored at `.noir/scaffold-version` as a single `noir-scaffold=<semver>` line\n * so `noir init --upgrade` / `noir doctor` can diff against\n * {@link CURRENT_SCAFFOLD_VERSION} and decide which migrations to run.\n *\n * Format is line-oriented (not YAML) on purpose: it must be readable before\n * `config.yml` is parsed (doctor runs even with a broken config), and the\n * `key=value` shape is trivial to grep from a shell.\n */\n\n/** The scaffold version this build of @noir-ai/create ships. Bumped atomically\n * whenever a manifest entry, template, or migration changes shape. 1.3.0 adds\n * the migration that points a joined repo's MCP entry back at the workspace\n * bridge; 1.4.0 adds the migration that moves the SessionStart hook command off\n * the `.mjs` runner and onto `noir hook`; 1.5.0 adds the migration that appends\n * the enforcement events to an existing claude hook and backfills the gemini\n * hook (see migrations/index.ts). */\nexport const CURRENT_SCAFFOLD_VERSION = '1.5.0';\n\nconst PREFIX = 'noir-scaffold=';\n\n/** Path to the stamp file under `root`. Exposed for tests + doctor. */\nexport function scaffoldVersionPath(root: string): string {\n return join(root, NOIR_DIR, 'scaffold-version');\n}\n\n/** Read the applied scaffold version, or `null` if the stamp is absent/unparseable.\n * Never throws — doctor must keep reporting even on a malformed stamp. */\nexport function readScaffoldVersion(root: string): string | null {\n let raw: string;\n try {\n raw = readFileSync(scaffoldVersionPath(root), 'utf8');\n } catch {\n return null;\n }\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed.startsWith(PREFIX)) {\n const v = trimmed.slice(PREFIX.length).trim();\n if (v.length > 0) return v;\n }\n }\n return null;\n}\n\n/** Write the stamp, creating `.noir/` if needed. The orchestrator writes it\n * LAST so a crash mid-scaffold leaves an old/absent stamp rather than a\n * misleading fresh one.\n *\n * Routed through the package's atomic `regenerate()` writer (tmp+rename in\n * the same dir) for consistency with the rest of the engine's durable writes —\n * a half-written stamp would mislead `noir doctor`/`init --upgrade`, so the\n * stamp deserves the same crash-atomicity as `.mcp.json` and the NOIR.md brief. */\nexport function writeScaffoldVersion(root: string, version: string): void {\n const file = scaffoldVersionPath(root);\n mkdirSync(dirname(file), { recursive: true });\n regenerate(file, `${PREFIX}${version}\\n`);\n}\n","import { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * READ-ONLY stack detection. Probes for well-known marker files under `root`\n * and reports ONLY what is present — never assumes. The result feeds path\n * adaptation (where to drop `.claude/` vs `.cursor/` etc., future), ignore-file\n * selection, and the onboarding TUI's confirm step.\n *\n * Design rules:\n * - Never throws. A foreign/empty dir returns `{ languages: [], monorepo:\n * false, frameworks: [], packageManager: null }`.\n * - Never opens network, never parses code beyond a `package.json`/`pyproject`\n * dependency list. Marker files + top-level manifests only.\n * - Frameworks are reported only when both the marker file is present AND the\n * framework's dependency is listed (avoids false positives from a stale\n * `package.json`).\n */\n\nexport interface StackInfo {\n /** Lower-cased language ids found via marker files:\n * `typescript` | `javascript` | `python` | `go` | `rust`. */\n languages: string[];\n /** True when a workspace manifest is present (pnpm-workspace, npm/yarn\n * workspaces in package.json, turbo.json, nx.json). */\n monorepo: boolean;\n /** Lower-cased framework ids (e.g. `next`, `vite`, `express`, `fastapi`,\n * `actix`). Empty when none detected. */\n frameworks: string[];\n /** `pnpm` | `npm` | `yarn` when a lockfile is present, else null. */\n packageManager: string | null;\n /**\n * Source of the resolved PM. `packageManager-field`\n * (package.json#packageManager), `lockfile`, `user-agent` (invoke-time\n * npm_config_user_agent), or `unknown`.\n */\n pmSource: 'packageManager-field' | 'lockfile' | 'user-agent' | 'unknown';\n /**\n * Detected CI. `github`/`gitlab`/`circleci`/\n * `jenkins`/`none`.\n */\n ci: string | null;\n /**\n * Existing AI instruction files found under root.\n * Never clobbered — the scaffold confirms the write strategy (skip/add-section/\n * standalone).\n */\n existingAiFiles: { path: string; kind: string }[];\n /** True when the invoke-time PM conflicts with the detected project-state PM. */\n pmConflict: boolean;\n}\n\n/** Frameworks looked up against `package.json#dependencies`+`devDependencies`.\n * Keyed by the dependency name as published on npm. */\nconst NODE_FRAMEWORKS: ReadonlyArray<[dep: string, id: string]> = [\n ['next', 'next'],\n ['vite', 'vite'],\n ['express', 'express'],\n ['fastify', 'fastify'],\n ['nuxt', 'nuxt'],\n ['remix', 'remix'],\n ['@sveltejs/kit', 'sveltekit'],\n ['@angular/core', 'angular'],\n ['react', 'react'],\n ['vue', 'vue'],\n];\n\n/** Frameworks looked up against `[project] dependencies` /\n * `[project.optional-dependencies]` / `[tool.poetry.dependencies]` in\n * `pyproject.toml`. Keyed by the PyPI package name. */\nconst PYTHON_FRAMEWORKS: ReadonlyArray<[dep: string, id: string]> = [\n ['fastapi', 'fastapi'],\n ['flask', 'flask'],\n ['django', 'django'],\n ['sanic', 'sanic'],\n ['starlette', 'starlette'],\n ['tornado', 'tornado'],\n ['aiohttp', 'aiohttp'],\n ['bottle', 'bottle'],\n ['pyramid', 'pyramid'],\n ['falcon', 'falcon'],\n];\n\n/** Read+parse JSON without throwing; returns undefined on any error. JSON5/ESM\n * `package.json` with comments would land here too — `package.json` is plain\n * JSON in practice so a failed `JSON.parse` genuinely means \"not a node\n * project\" or \"broken file\", both of which we report as \"absent\". */\nfunction readJson<T = unknown>(file: string): T | undefined {\n try {\n return JSON.parse(readFileSync(file, 'utf8')) as T;\n } catch {\n return undefined;\n }\n}\n\ninterface PackageJson {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n workspaces?: string[] | { packages?: string[] };\n packageManager?: string;\n}\n\nexport function detectStack(root: string): StackInfo {\n const languages = new Set<string>();\n const frameworks = new Set<string>();\n let monorepo = false;\n let packageManager: string | null = null;\n\n // Node / JS / TS — gated on package.json presence.\n const pjPath = join(root, 'package.json');\n if (existsSync(pjPath)) {\n const pj = readJson<PackageJson>(pjPath);\n if (pj) {\n const hasTs =\n Boolean(pj.devDependencies?.typescript) || existsSync(join(root, 'tsconfig.json'));\n languages.add(hasTs ? 'typescript' : 'javascript');\n\n const deps = new Set([\n ...Object.keys(pj.dependencies ?? {}),\n ...Object.keys(pj.devDependencies ?? {}),\n ]);\n for (const [dep, id] of NODE_FRAMEWORKS) {\n if (deps.has(dep)) frameworks.add(id);\n }\n\n const ws = pj.workspaces;\n if (\n Array.isArray(ws) ||\n (typeof ws === 'object' && ws !== null && Array.isArray(ws.packages))\n ) {\n monorepo = true;\n }\n if (typeof pj.packageManager === 'string' && pj.packageManager.length > 0) {\n // `packageManager: pnpm@10.12.4` → `pnpm`. Yarn/npm similarly.\n const m = pj.packageManager.split('@')[0];\n if (m) packageManager = m;\n }\n }\n }\n\n // Workspace manifests (these override/confirm monorepo independent of pj).\n if (existsSync(join(root, 'pnpm-workspace.yaml'))) {\n monorepo = true;\n packageManager = packageManager ?? 'pnpm';\n }\n if (existsSync(join(root, 'turbo.json')) || existsSync(join(root, 'nx.json'))) {\n monorepo = true;\n }\n\n // Lockfiles pin packageManager when `package.json#packageManager` didn't.\n if (!packageManager) {\n if (existsSync(join(root, 'pnpm-lock.yaml'))) packageManager = 'pnpm';\n else if (existsSync(join(root, 'yarn.lock'))) packageManager = 'yarn';\n else if (existsSync(join(root, 'package-lock.json'))) packageManager = 'npm';\n }\n\n // Python — pyproject.toml / requirements.txt / Pipfile / setup.py.\n if (\n existsSync(join(root, 'pyproject.toml')) ||\n existsSync(join(root, 'requirements.txt')) ||\n existsSync(join(root, 'Pipfile')) ||\n existsSync(join(root, 'setup.py'))\n ) {\n languages.add('python');\n // Framework detection scans pyproject.toml for PEP 508 dependency names\n // under `[project] dependencies` / `[project.optional-dependencies]` /\n // `[tool.poetry.dependencies]`. A full section-aware TOML parse is overkill\n // at v1 (and would need a dep we don't ship); the boundary-aware text scan\n // in `pyprojectHasDep` is robust to the three formats users actually write\n // and never throws on malformed TOML (degrades to \"no match\").\n if (existsSync(join(root, 'pyproject.toml'))) {\n const raw = safeRead(join(root, 'pyproject.toml'));\n if (raw) {\n for (const [dep, id] of PYTHON_FRAMEWORKS) {\n if (pyprojectHasDep(raw, dep)) frameworks.add(id);\n }\n }\n }\n }\n\n // Go — go.mod.\n if (existsSync(join(root, 'go.mod'))) {\n languages.add('go');\n packageManager = packageManager ?? 'go-modules';\n }\n\n // Rust — Cargo.toml.\n if (existsSync(join(root, 'Cargo.toml'))) {\n languages.add('rust');\n const raw = safeRead(join(root, 'Cargo.toml'));\n if (raw) {\n // Cargo.toml deps are always `name = \"ver\"` or `name = { … }`, so\n // require the `=` after the crate name. The old `/^\\s*actix\\b/m` matched\n // `actix-web` (word boundary between `x` and `-`) and falsely reported\n // `actix`. Treat the hyphenated runtime (`actix-web`) as its OWN id and\n // gate bare `actix` on the equals form. Same equals-form tightening is\n // applied to `axum`/`rocket` so `axum-extra`-style crates don't trip the\n // same bug. The `/m` flag makes `^` match any line (the previous\n // `/^actix\\s*=/` had no `/m` and only matched a string starting with\n // `actix`, i.e. effectively never — dead code).\n if (/^\\s*actix-web\\b/m.test(raw)) frameworks.add('actix-web');\n if (/^\\s*actix\\s*=/m.test(raw)) frameworks.add('actix');\n if (/^\\s*axum\\s*=/m.test(raw)) frameworks.add('axum');\n if (/^\\s*rocket\\s*=/m.test(raw)) frameworks.add('rocket');\n }\n packageManager = packageManager ?? 'cargo';\n }\n\n // Invoke-time PM via npm_config_user_agent (fallback\n // after lockfile + packageManager field). `pmConflict` flags when invoke-time\n // differs from project-state (the \"no surprise\" principle).\n let pmSource: StackInfo['pmSource'] = 'unknown';\n let pmConflict = false;\n if (packageManager) {\n if (pjPath && existsSync(pjPath)) {\n const pj = readJson<PackageJson>(pjPath);\n if (pj?.packageManager?.split('@')[0] === packageManager) {\n pmSource = 'packageManager-field';\n } else {\n pmSource = 'lockfile';\n }\n } else {\n pmSource = 'lockfile';\n }\n }\n // Invoke-time fallback: only when the dir has recognizable languages\n // (user-agent alone doesn't define a project — an empty dir gets no PM).\n if (!packageManager && languages.size > 0) {\n const ua = process.env.npm_config_user_agent;\n if (ua) {\n if (ua.startsWith('pnpm')) packageManager = 'pnpm';\n else if (ua.startsWith('yarn')) packageManager = 'yarn';\n else if (ua.startsWith('npm')) packageManager = 'npm';\n else if (ua.startsWith('bun')) packageManager = 'bun';\n pmSource = 'user-agent';\n }\n }\n // Conflict detection: invoke-time PM differs from project-state PM.\n {\n const ua = process.env.npm_config_user_agent;\n if (ua && packageManager) {\n const invokePm = ua.startsWith('pnpm')\n ? 'pnpm'\n : ua.startsWith('yarn')\n ? 'yarn'\n : ua.startsWith('npm')\n ? 'npm'\n : ua.startsWith('bun')\n ? 'bun'\n : null;\n if (invokePm && invokePm !== packageManager) pmConflict = true;\n }\n }\n\n // CI detection.\n let ci: string | null = null;\n if (existsSync(join(root, '.github', 'workflows'))) ci = 'github';\n else if (existsSync(join(root, '.gitlab-ci.yml'))) ci = 'gitlab';\n else if (existsSync(join(root, '.circleci', 'config.yml'))) ci = 'circleci';\n else if (existsSync(join(root, 'Jenkinsfile'))) ci = 'jenkins';\n\n // Existing AI instruction files (never clobbered).\n const aiFiles: [string, string][] = [\n ['AGENTS.md', 'agents-md'],\n ['CLAUDE.md', 'claude'],\n ['.cursorrules', 'cursor'],\n ['GEMINI.md', 'gemini'],\n ['.github/copilot-instructions.md', 'copilot'],\n ];\n const existingAiFiles: StackInfo['existingAiFiles'] = [];\n for (const [p, kind] of aiFiles) {\n const full = join(root, p);\n if (existsSync(full)) existingAiFiles.push({ path: p, kind });\n }\n // .cursor/rules/**/*.md — probe the dir (shallow).\n const cursorRulesDir = join(root, '.cursor', 'rules');\n try {\n if (existsSync(cursorRulesDir)) {\n const entries = readdirSync(cursorRulesDir);\n for (const e of entries) {\n if (e.endsWith('.md'))\n existingAiFiles.push({ path: `.cursor/rules/${e}`, kind: 'cursor-rules' });\n }\n }\n } catch {\n // ignore\n }\n\n return {\n languages: [...languages].sort(),\n monorepo,\n frameworks: [...frameworks].sort(),\n packageManager,\n pmSource,\n pmConflict,\n ci,\n existingAiFiles,\n };\n}\n\nfunction safeRead(file: string): string | undefined {\n try {\n return readFileSync(file, 'utf8');\n } catch {\n return undefined;\n }\n}\n\n/** True iff `dep` appears as a PEP 508 dependency name in `pyproject.toml`\n * text — handles PEP 621 `dependencies`/`optional-dependencies` list entries\n * (`\"fastapi\"`, `\"fastapi[all]>=0.100\"`) AND Poetry `[tool.poetry.dependencies]`\n * bare keys (`fastapi = \"^0.100\"`).\n *\n * The `before` boundary (line-start, quote, or whitespace) plus the `after`\n * PEP 508 boundary (whitespace, quote, version specifier `<>=!~`, extras `[`,\n * marker `;`, or end-of-string) prevent substring mismatches — `flask` will\n * NOT match `flask-restful`, and `fastapi` will NOT match `x-fastapi`.\n *\n * Never throws: `dep` is escaped, the regex is constructed from a controlled\n * template, and `String.prototype.test` is total on string input. Malformed\n * TOML simply yields no matches. */\nfunction pyprojectHasDep(raw: string, dep: string): boolean {\n const esc = dep.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const before = `(?:^|[\"'\\\\s])`;\n const after = `(?=\\\\s|[\"'<>=!~;]|\\\\[|$)`;\n return new RegExp(`${before}${esc}${after}`, 'm').test(raw);\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readFileSync","dirname","join","existsSync","readFileSync","dirname","join","join","mkdirSync","dirname","existsSync","readFileSync","out","existsSync","join","resolveAdapter","managedBlock","readFileSync","dirname","join","managedBlock","resolveAdapter","existsSync","join","i","j","existsSync","readFileSync","rmSync","writeFileSync","join","relative","atomicWriteFile","readWorkspaceMarker","resolveNoirCommand","join","existsSync","readFileSync","writeFileSync","relative","readWorkspaceMarker","resolveNoirCommand","atomicWriteFile","rmSync","existsSync","mkdirSync","readFileSync","renameSync","rmSync","basename","dirname","join","loadProjectInfo","paths","readProjectConfig","resolveNoirCommand","existsSync","readFileSync","join","mkdirSync","readFileSync","dirname","join","NOIR_DIR","join","NOIR_DIR","readFileSync","mkdirSync","dirname","existsSync","readdirSync","readFileSync","join","basename","dirname","join","paths","existsSync","loadProjectInfo","readProjectConfig","mkdirSync","rmSync","resolveNoirCommand","similarity","onDisk","regions","predicted","body","readFileSync","renameSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/ancestors.ts","../src/hooks.ts","../src/writers.ts","../src/manifest.ts","../src/template.ts","../src/template-loader.ts","../src/merge.ts","../src/migrations/index.ts","../src/template-history.ts","../src/migrations/runner.ts","../src/scaffold.ts","../src/baseline.ts","../src/scaffold-version.ts","../src/stack-detect.ts"],"sourcesContent":["// Ancestor store for three-way managed-region merge.\n//\n// Persists the last-emitted managed-region text per (file, block) so a later\n// `noir init`/`sync` can three-way merge (base/ours/theirs) instead of\n// strip-replacing. Stored at `.noir/ancestors.json` as a flat map.\n//\n// Ancestor capture is UNCONDITIONAL (every init/create/sync writes a\n// snapshot of the managed regions it touched), so the first merge run: now the\n// DEFAULT: always has a base. `writeAncestors` is content-hash idempotent: a\n// no-op sync leaves the file untouched (no mtime/git churn).\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nconst ANCESTORS_REL = '.noir/ancestors.json';\n\n/** Absolute path of the ancestor store under `root`. */\nexport function ancestorsPath(root: string): string {\n return join(root, ANCESTORS_REL);\n}\n\n/** Read the ancestor map (`{ \"${relPath}::${blockBegin}\": regionText }`).\n * Returns `{}` for a missing/corrupt file (never throws). */\nexport function readAncestors(root: string): Record<string, string> {\n if (!existsSync(ancestorsPath(root))) return {};\n try {\n const raw = readFileSync(ancestorsPath(root), 'utf8');\n const obj: unknown = JSON.parse(raw);\n if (obj && typeof obj === 'object' && !Array.isArray(obj)) {\n return obj as Record<string, string>;\n }\n return {};\n } catch {\n return {};\n }\n}\n\n/** Write the ancestor map. Ensures `.noir/` exists (a standalone caller may\n * invoke this before the scaffold has created the dir). Derived state, so the\n * plain write (no tmp) is fine. Content-hash dedup: when the serialized\n * bytes equal what is already on disk, the rewrite is skipped so a no-op sync\n * leaves ancestors.json untouched (no mtime/git churn). */\nexport function writeAncestors(root: string, map: Record<string, string>): void {\n const path = ancestorsPath(root);\n const next = `${JSON.stringify(map, null, 2)}\\n`;\n try {\n if (readFileSync(path, 'utf8') === next) return; // unchanged → skip\n } catch {\n /* missing → fall through to write */\n }\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, next, 'utf8');\n}\n","import { existsSync, mkdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { type HostId, resolveAdapter } from '@noir-ai/adapters';\nimport { resolveNoirCommand } from '@noir-ai/core';\nimport { mergeJson, regenerate } from './writers.js';\n\n/**\n * Emit a host's lifecycle-hook config on demand: the write-once `mergeJson`\n * hook the scaffold deliberately does NOT re-emit on `noir init --upgrade` or\n * `noir sync`. This is the explicit restore path (`noir hooks sync`): it runs\n * the same `adapter.emitHooks` seam the scaffold uses and dispatches each entry\n * to the writer its mode names (`regenerate` for cursor, `mergeJson` for\n * claude/gemini). With `force`, an existing Noir-owned hook entry (one whose\n * command carries the dedup marker) is refreshed to the currently resolved\n * command instead of being left as-is; the user's other keys and non-Noir hooks\n * are never touched.\n *\n * Pure filesystem work: no daemon, no store, no prompt. A host with no hook\n * surface (agents-md, opencode) returns an empty outcome list.\n */\n\n/** One host hook-config write performed by {@link emitHooksToFile}. */\nexport interface HookEmitOutcome {\n /** Repo-relative path of the host's hook config file. */\n path: string;\n /** `mergeJson` = merged into a user-owned file; `regenerate` = rewritten. */\n mode: 'mergeJson' | 'regenerate';\n /** True when the bytes on disk changed (a write happened). */\n written: boolean;\n}\n\nexport interface EmitHooksResult {\n host: HostId;\n outcomes: HookEmitOutcome[];\n}\n\nexport function emitHooksToFile(\n root: string,\n host: HostId,\n opts?: { force?: boolean },\n): EmitHooksResult {\n const adapter = resolveAdapter(host);\n const emissions = adapter.emitHooks?.({ command: resolveNoirCommand() }) ?? null;\n if (emissions === null) return { host, outcomes: [] };\n\n const outcomes: HookEmitOutcome[] = emissions.map((e) => {\n const abs = join(root, e.path);\n // The writers create a temp sibling in the target's directory and rename it\n // in; the directory must exist first (mirrors the scaffold's mkdir -p).\n mkdirSync(dirname(abs), { recursive: true });\n if (e.mode === 'regenerate') {\n const content = `${JSON.stringify(e.content, null, 2)}\\n`;\n // `regenerate` always rewrites; skip a byte-identical file so `written`\n // stays honest and a repeat run is a true no-op (the scaffold tracks this\n // as \"identical\" at the orchestrator level, which this path bypasses).\n if (existsSync(abs) && readFileSync(abs, 'utf8') === content) {\n return { path: e.path, mode: 'regenerate', written: false };\n }\n const out = regenerate(abs, content);\n return { path: e.path, mode: 'regenerate', written: out.written };\n }\n const out = mergeJson(abs, e.content, e.dedupSubstring, opts?.force === true);\n return { path: e.path, mode: 'mergeJson', written: out.written };\n });\n\n return { host, outcomes };\n}\n","import {\n closeSync,\n existsSync,\n openSync,\n readFileSync,\n renameSync,\n rmSync,\n writeSync,\n} from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport type { FileModeOutcome, ManagedBlock } from '@noir-ai/core';\nimport { atomicWriteFile, stripManagedBlock, writeManagedRegion } from '@noir-ai/core';\n\n/**\n * The three-mode writer: generalizes the earlier `writeManagedRegion` into\n * the declarative dispatch the scaffold manifest drives. Each mode maps 1:1 to\n * an artifact class in the artifact matrix:\n *\n * - {@link regenerate} : pure pointers (`.mcp.json`, `NOIR.md` brief, …).\n * Always overwritten, atomically.\n * - {@link managedBlock} : co-owned files (`CLAUDE.md` context/rules,\n * `.gitignore` noir block, …). DELEGATES to\n * core's `writeManagedRegion` so user content\n * outside the markers is preserved byte-for-byte and\n * re-runs are idempotent. Never duplicate the\n * managed-region logic.\n * - {@link skipIfExists} : user-owned seeds (`RULES.md`, `config.yml`,\n * `project.id`). Write once; never clobber.\n *\n * The orchestrator (`scaffold.ts`) is the only intended caller; the per-mode\n * functions are exported so the cli and tests can drive them directly\n * when a one-off write is needed outside the manifest.\n */\n\nexport type WriteMode = 'regenerate' | 'managedBlock' | 'skipIfExists' | 'mergeJson';\n\nexport interface WriteOutcome {\n /** The absolute path that was written. */\n path: string;\n mode: WriteMode;\n /** true when bytes hit disk; false for skipIfExists no-ops. regenerate and\n * managedBlock always write (managedBlock may write identical bytes: that\n * still counts as a write for telemetry purposes; the file IS up to date). */\n written: boolean;\n}\n\n/** Atomic overwrite. Writes to `<file>.tmp.<pid>.<rnd>` in the same directory,\n * fsyncs, then renames over the target so a crash never leaves a half-written\n * file (the pointer files this is used for are read by the host agent first —\n * a truncated CLAUDE.md/.mcp.json would break the very startup Noir serves).\n *\n * Parent directories are NOT created here: the orchestrator does that once\n * for the whole manifest so a missing dir is a single, attributable failure\n * rather than N silent ones inside the writer.\n */\nexport function regenerate(absPath: string, content: string): WriteOutcome {\n const dir = dirname(absPath);\n const tmp = join(\n dir,\n `.${basename(absPath)}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`,\n );\n // Open with 'w' truncates; writeSync + closeSync before rename so the bytes\n // are durable pre-swap. O_SYNC would be stronger but is platform-flaky; the\n // rename is the real atomicity guarantee on POSIX (and practical-enough on\n // the win32 targets Noir supports).\n //\n // The tmp MUST be cleaned up on EVERY exit path. The previous shape only\n // ran `rmSync(tmp)` when `renameSync` threw, so a `writeSync` failure (disk\n // full, EPERM, …) left the tmp behind. A single try/finally with `force:true`\n // rmSync (no-op ENOENT after a successful rename consumed the file) covers\n // both.\n let fd: number | undefined;\n try {\n fd = openSync(tmp, 'w');\n writeSync(fd, content, 0, 'utf8');\n closeSync(fd);\n fd = undefined; // closed cleanly: don't re-close in finally\n renameSync(tmp, absPath);\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n /* best-effort: the rename/rm below are the meaningful cleanups */\n }\n }\n try {\n rmSync(tmp, { force: true });\n } catch {\n /* best-effort */\n }\n }\n return { path: absPath, mode: 'regenerate', written: true };\n}\n\n/** Re-emit a managed region, delegating to core's `writeManagedRegion`.\n * `regionText` MUST already include the begin/end markers (matches the shape\n * `writeManagedRegion` expects and that `IGNORE_BLOCK`/`CONTEXT_BLOCK`\n * callers build in core/cli). Use {@link buildRegion} to assemble it from a\n * block + body. */\nexport function managedBlock(\n absPath: string,\n block: ManagedBlock,\n regionText: string,\n): WriteOutcome {\n writeManagedRegion(absPath, block, regionText);\n return { path: absPath, mode: 'managedBlock', written: true };\n}\n\n/** Atomically (re)emit MULTIPLE managed regions into the SAME file in one\n * pass. Used when a co-owned target carries more than one managed block —\n * today only `CLAUDE.md` (CONTEXT + RULES).\n *\n * WHY this exists: calling {@link managedBlock} twice on the same file is\n * NOT byte-idempotent. The 2nd call strips ONLY its own block, treats the 1st\n * region (and the `\\n\\n` separator) as user content, `trimEnd`s it, and\n * re-appends a fresh `\\n\\n` separator. Re-runs therefore accumulate ~2 leading\n * `\\n` bytes per init (verified: 158→168 over 5 runs). Doing both regions in a\n * SINGLE read → strip-all → append-all pass removes the interleaving: after\n * stripping BOTH blocks the only thing left is real user content, so re-runs\n * produce identical bytes.\n *\n * Strategy:\n * 1. Read the file (missing → empty).\n * 2. Strip EVERY named block (via core's `stripManagedBlock`, in the given\n * order): what remains is user content + any managed blocks outside this\n * group.\n * 3. Append all `regionText`s in the GIVEN ORDER, joined by a single `\\n`.\n * Each `regionText` already ends with `\\n` (buildRegion appends the end\n * marker's trailing newline), so `\\n` between regions yields exactly one\n * blank-line separator (`END\\n` + `\\n` + `BEGIN`): byte-identical to what\n * the single-block path emits on a first run, so the CONTEXT/RULES parity\n * gates against `claudeAdapter.emitContext/emitRules` keep passing.\n * 4. Write the result atomically (via core's `atomicWriteFile`: temp sibling +\n * rename). This target is a file the user also owns, so an interrupted\n * write must not leave the user's own content half-written: and the swap\n * keeps the file's existing permissions, which a fresh temp file would\n * otherwise reset to the umask default.\n *\n * Single-region files (NOIR.md brief, ignore files) do NOT route through here\n * : the orchestrator only calls this for groups of ≥2 managed blocks, so\n * single-region byte-stability (delegated to core's `writeManagedRegion`)\n * is unchanged. */\nexport function managedBlocks(\n absPath: string,\n regions: ReadonlyArray<{ block: ManagedBlock; regionText: string }>,\n): WriteOutcome {\n if (regions.length === 0) {\n throw new Error('managedBlocks requires at least one region');\n }\n if (regions.length === 1) {\n const only = regions[0];\n if (!only) throw new Error('managedBlocks: undefined region');\n return managedBlock(absPath, only.block, only.regionText);\n }\n let content = '';\n try {\n content = readFileSync(absPath, 'utf8');\n } catch {\n /* missing → treat as empty */\n }\n let stripped = content;\n for (const r of regions) {\n stripped = stripManagedBlock(stripped, r.block);\n }\n const regionsJoined = regions.map((r) => r.regionText).join('\\n');\n // Whitespace-only remainder (typical on re-run after both blocks are\n // stripped) → emit just the regions, no leading separator.\n const next =\n stripped.trim().length > 0 ? `${stripped.trimEnd()}\\n\\n${regionsJoined}` : regionsJoined;\n atomicWriteFile(absPath, next);\n return { path: absPath, mode: 'managedBlock', written: true };\n}\n\n/** Assemble `<begin>\\n<body>\\n<end>\\n` for a managed block. Centralized here so\n * every caller (manifest rendering, tests, future migrations) produces the\n * exact byte shape `writeManagedRegion` strips/expects. The trailing newline\n * is part of the contract: `stripManagedBlock`'s regex eats a trailing `\\n`\n * so re-runs stay idempotent instead of accumulating blank lines.\n *\n * `body` is `trimEnd()`-ed before wrapping so template authors can keep the\n * conventional trailing newline in `.tmpl` files without producing a\n * double-newline before the end marker. This keeps the output byte-identical\n * to `claudeAdapter.emitContext`/`emitRules` and core's `syncIgnores`, which\n * the cli refactor relies on for diff-free output. */\nexport function buildRegion(block: ManagedBlock, body: string): string {\n return `${block.begin}\\n${body.trimEnd()}\\n${block.end}\\n`;\n}\n\n/** Compute the exact byte content a {@link managedBlock} write would produce,\n * WITHOUT touching disk. Mirrors core's `writeManagedRegion` byte-for-byte\n * (`stripManagedBlock` + `trimEnd` + `\\n\\n` separator + `regionText`) so the\n * orchestrator's content-hash dedup (skip an unchanged managed region) and\n * the writer always agree on every byte. Pass the CURRENT on-disk content;\n * the predictor never reads the file itself (the orchestrator reads once and\n * reuses the bytes for both predict + compare). */\nexport function predictManagedBlock(\n currentContent: string,\n block: ManagedBlock,\n regionText: string,\n): string {\n const stripped = stripManagedBlock(currentContent, block);\n return stripped ? `${stripped.trimEnd()}\\n\\n${regionText}` : regionText;\n}\n\n/** Compute the exact byte content a {@link managedBlocks} (multi-region) write\n * would produce, WITHOUT touching disk. Mirrors the multi-region writer's\n * strip-all + append-all pass (the single-region case delegates to\n * {@link predictManagedBlock}). Used by the content-hash dedup so a\n * multi-region file (CLAUDE.md CONTEXT+RULES) that is already up to date is\n * skipped: `noir sync` on an unchanged tree writes NOTHING. */\nexport function predictManagedBlocks(\n currentContent: string,\n regions: ReadonlyArray<{ block: ManagedBlock; regionText: string }>,\n): string {\n if (regions.length === 0) {\n throw new Error('predictManagedBlocks requires at least one region');\n }\n if (regions.length === 1) {\n const only = regions[0];\n if (!only) throw new Error('predictManagedBlocks: undefined region');\n return predictManagedBlock(currentContent, only.block, only.regionText);\n }\n let stripped = currentContent;\n for (const r of regions) {\n stripped = stripManagedBlock(stripped, r.block);\n }\n const regionsJoined = regions.map((r) => r.regionText).join('\\n');\n return stripped.trim().length > 0 ? `${stripped.trimEnd()}\\n\\n${regionsJoined}` : regionsJoined;\n}\n\n/** Write `content` to `absPath` only if no file exists there. Returns whether\n * bytes were written. Parent dirs are created by {@link atomicWriteFile}.\n *\n * `fileMode` is the permission for the NEWLY created file —\n * `0o600` for the `.noir/.env` credential seed. It is applied to the temp file\n * BEFORE the rename (see `AtomicWriteOptions.mode`), so the file never exists\n * at its final path with a laxer mode. It is deliberately ignored when the\n * target already exists: this writer's whole contract is \"never touch an\n * existing file\", and that includes its permissions. POSIX-only: Windows\n * permissions are ACL-based and the mode is dropped there.\n *\n * The write goes through `atomicWriteFile` (tmp + rename) rather than a bare\n * `writeFileSync`: a requested mode must land on the temp, and an interrupted\n * seed write should not leave a half-written `.noir/.env` behind. Byte output\n * is unchanged for every existing caller (same content, umask default 0o644\n * when no mode is requested).\n *\n * An EXISTING file keeps this writer's create-only contract, but its mode is\n * not left to drift: the orchestrator calls {@link ensureOwnerOnly} after the\n * emit, which tightens a group/world-readable credential to 0600. */\nexport function skipIfExists(absPath: string, content: string, fileMode?: number): WriteOutcome {\n if (existsSync(absPath)) {\n return { path: absPath, mode: 'skipIfExists', written: false };\n }\n atomicWriteFile(absPath, content, fileMode !== undefined ? { mode: fileMode } : {});\n return { path: absPath, mode: 'skipIfExists', written: true };\n}\n\n/** What happened when the owner-only mode was re-asserted on a credential\n * file: see {@link ensureOwnerOnly}. */\nexport type EnvMode = FileModeOutcome;\n\n/** Re-exported from `@noir-ai/core`, where the re-assert lives: the embedded\n * store needs the same \"tighten a lax permission on every open\" behaviour for\n * its database and directory, and a copy per package would drift. `EnvMode` is\n * the name the scaffold result has always used for the outcome; the canonical\n * name is core's `FileModeOutcome`.\n *\n * A `.noir/.env` needs it because {@link skipIfExists} applies its 0600 only\n * when it creates the file and no writer ever opens an existing one, so a file\n * seeded by an earlier Noir, or rewritten by an editor that saves by rename,\n * would keep a lax mode indefinitely and make the environment diagnostic warn\n * on every command. */\nexport { ensureOwnerOnly } from '@noir-ai/core';\n\n/** Overwrite an existing seed's bytes while keeping the permission bits it\n * already has. The `skipIfExists` refresh variant: the file exists (a refresh\n * is only ever decided from bytes already on disk), its content is Noir's own\n * shipped text, and only that text changes: so, unlike {@link regenerate},\n * which writes a fresh temp file at the umask default and renames over the\n * target, this leaves the mode alone.\n *\n * `atomicWriteFile` is what makes that safe: it stats the existing target and\n * restores that mode after the rename, so a crash mid-write cannot leave a\n * half-written seed behind and no permission is silently changed. Parent\n * directories are the orchestrator's job, created once for the whole manifest.\n *\n * The staleness decision is NOT made here: the writer is handed bytes that\n * the caller has already established are an unedited older copy of the seed. */\nexport function refreshSeed(absPath: string, content: string): WriteOutcome {\n atomicWriteFile(absPath, content);\n return { path: absPath, mode: 'skipIfExists', written: true };\n}\n\n/**\n * Merge-aware JSON write (the SessionStart hook the scaffold emits). `settings.local.json` is JSON\n * and CANNOT carry `<!-- noir:* -->` managed markers, so it is neither\n * `managedBlock` (would need comments) nor `regenerate` (would clobber the\n * user's `permissions`/`env`/`enabledPlugins`) nor `skipIfExists` (would go\n * stale). This path:\n * 1. Reads the existing file (missing → `{}`).\n * 2. Preserves EVERY existing top-level key (`permissions`, `env`, `hooks`, …).\n * 3. Merges the provided `patch` object: for `hooks` arrays, the existing\n * arrays are kept and entries whose command already contains the given\n * `dedupSubstring` are NOT re-added (context-mode's alreadyRegistered\n * pattern: the entry is written once, never clobbered).\n * 4. Writes atomically via the same tmp+rename as `regenerate` when the merged\n * bytes differ (content-hash dedup: an unchanged `noir sync` writes nothing).\n *\n * `patch` is a deep-merge target: top-level keys merge by key; `hooks.*` arrays\n * append-with-dedup. Other arrays replace only when provided. Returns the same\n * `WriteOutcome` shape. */\nexport function mergeJson(\n absPath: string,\n patch: Record<string, unknown>,\n dedupSubstring?: string,\n force?: boolean,\n): WriteOutcome {\n let existing: Record<string, unknown> = {};\n if (existsSync(absPath)) {\n try {\n existing = JSON.parse(readFileSync(absPath, 'utf8')) as Record<string, unknown>;\n } catch {\n // Corrupt/unparseable existing file: treat as empty, the merge will\n // produce a valid file (the old bytes are preserved in git if versioned).\n existing = {};\n }\n }\n\n const merged = deepMergePreservingHooks(existing, patch, dedupSubstring, force);\n const content = `${JSON.stringify(merged, null, 2)}\\n`;\n\n // Content-hash dedup: a no-op sync writes nothing.\n if (existsSync(absPath)) {\n try {\n if (readFileSync(absPath, 'utf8') === content) {\n return { path: absPath, mode: 'mergeJson', written: false };\n }\n } catch {\n /* fall through and write */\n }\n }\n\n regenerate(absPath, content); // reuse the atomic tmp+rename path\n return { path: absPath, mode: 'mergeJson', written: true };\n}\n\n/** True when a `hooks.<Event>` entry carries the dedup marker: either as its\n * own `command` or inside the matcher group's inner `hooks` array, the shape\n * Claude Code stores (`{hooks:[{type:'command',command:'…'}]}`). Checking only\n * the top level never matched that shape, so a re-emit (init `--force`)\n * appended a second copy of the hook. */\nfunction entryHasCommand(entry: unknown, marker: string): boolean {\n if (typeof entry !== 'object' || entry === null) return false;\n const obj = entry as { command?: unknown; hooks?: unknown };\n if (typeof obj.command === 'string' && obj.command.includes(marker)) return true;\n if (!Array.isArray(obj.hooks)) return false;\n return obj.hooks.some((h) => {\n if (typeof h !== 'object' || h === null) return false;\n const command = (h as { command?: unknown }).command;\n return typeof command === 'string' && command.includes(marker);\n });\n}\n\n/** Deep-merge `patch` into `existing`. `hooks.*` arrays append-with-dedup\n * (an entry whose command contains `dedupSubstring` is kept as-is, not\n * re-added); other arrays replace only when present in the patch. With\n * `force`, an existing entry whose command carries the marker is DROPPED\n * before the patch entries are appended, so a drifted Noir-owned entry is\n * refreshed in place while entries the user added stay untouched. */\nfunction deepMergePreservingHooks(\n existing: Record<string, unknown>,\n patch: Record<string, unknown>,\n dedupSubstring?: string,\n force?: boolean,\n): Record<string, unknown> {\n const out: Record<string, unknown> = { ...existing };\n for (const [key, value] of Object.entries(patch)) {\n if (key === 'hooks' && typeof value === 'object' && value !== null) {\n const existingHooks = (out.hooks as Record<string, unknown> | undefined) ?? {};\n const mergedHooks: Record<string, unknown> = { ...existingHooks };\n for (const [event, entries] of Object.entries(value as Record<string, unknown>)) {\n const existingEntries = Array.isArray(mergedHooks[event])\n ? (mergedHooks[event] as unknown[])\n : [];\n const patchEntries = Array.isArray(entries) ? entries : [];\n const hasMarker = (x: unknown): boolean =>\n dedupSubstring !== undefined && entryHasCommand(x, dedupSubstring);\n // Append-only with dedup: only add patch entries whose command does NOT\n // already contain the dedup marker (the Noir hook was registered before —\n // write-once, never clobbered). A patch entry whose command itself is\n // missing is still added (caller bug, but non-fatal). `force` instead\n // drops every marker-carrying entry first, then re-adds the patch.\n const keptEntries =\n force === true ? existingEntries.filter((x) => !hasMarker(x)) : existingEntries;\n const alreadyRegistered = force !== true && existingEntries.some(hasMarker);\n const toAdd = alreadyRegistered ? [] : patchEntries;\n mergedHooks[event] = [...keptEntries, ...toAdd];\n }\n out.hooks = mergedHooks;\n } else {\n // Non-hooks keys: patch wins, but only when the patch actually provides it.\n out[key] = value;\n }\n }\n return out;\n}\n","import { existsSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport {\n AGENTS_MD_FILENAME,\n type EmitContext,\n emitAgentsMd,\n type HostAdapter,\n type HostId,\n mcpConfigPathForAdapter,\n resolveAdapter,\n} from '@noir-ai/adapters';\nimport {\n CONTEXT_BLOCK,\n IGNORE_BLOCK,\n type ManagedBlock,\n managedBlock,\n NOIR_DIR,\n paths,\n RULES_BLOCK,\n readWorkspaceMarker,\n} from '@noir-ai/core';\nimport type { StackInfo } from './stack-detect.js';\nimport { render } from './template.js';\nimport type { SeedKind } from './template-history.js';\nimport { loadTemplate } from './template-loader.js';\nimport type { WriteMode } from './writers.js';\n\n/**\n * Declarative scaffold manifest: the single source of truth for what\n * `init` / `create` / `sync` emit. Each entry is one artifact, tagged with its\n * write {@link WriteMode} so the orchestrator can dispatch without knowing\n * what's inside.\n *\n * FAITHFULNESS CONTRACT: this table is a strict superset of\n * the artifacts `packages/cli/src/{init,sync}.ts` wrote before the manifest\n * refactor. The cli refactor replaced those ad-hoc writers with a call into\n * `scaffold()`; the byte-for-byte output MUST stay equivalent for first-run\n * init. The manifest is HOST-PARAMETRIC: {@link buildManifest} returns\n * host-agnostic entries + a {@link buildHostArtifacts} call that materializes\n * per-host files\n * (CLAUDE.md/GEMINI.md for claude/gemini; AGENTS.md + .cursor/.../opencode.json\n * for agents-md/cursor/opencode) via the resolved adapter. The claude default\n * `noir init` stays BYTE-IDENTICAL to v1.1: REMOVED the additive\n * root `AGENTS.md` (it was double-importing `.noir/NOIR.md` + RULES.md via\n * CLAUDE.md's existing @-imports; claude's native surface is CLAUDE.md alone).\n *\n * Path-derivation: repo-relative POSIX strings that mirror\n * `@noir-ai/core/layout.ts` (`paths.*`). The test suite asserts\n * `join(root, entry.path) === paths.X(root)` for every entry layout knows\n * about, so a layout rename is caught here instead of silently drifting.\n */\n\n/** `HostTag` is the SAME `HostId` enum the adapter registry uses\n * (re-exported so existing imports keep working); it used to be the\n * literal `'claude'`. Widening it to `HostId` lets one manifest serve every\n * host via the orchestrator's host filter + {@link buildHostArtifacts}. */\nexport type HostTag = HostId;\n\nexport interface ManifestEntry {\n /** Repo-relative POSIX path (forward slashes). Orchestrator joins with root. */\n path: string;\n mode: WriteMode;\n /** Opt-in flag for a DOC-ONLY seed: a file whose only job is to be read by a\n * human. On an upgrade, a seed still byte-identical to what an older Noir\n * shipped is refreshed in place rather than staying frozen at that older\n * text forever; one the user edited is never refreshed (it goes to the\n * ordinary conflict flow, which keeps the user's bytes by default).\n *\n * Honored on the upgrade emit only. A fresh `init`/`create` either creates\n * the file or leaves it alone, and `sync` does not emit seeds at all, so\n * neither can observe the flag: the behavior change is confined to the one\n * command a user runs to bring an initialized project up to date.\n *\n * DOC-ONLY SEEDS ONLY. A file the user owns the contents of must never carry\n * this flag: `.noir/.env`, `.noir/config.yml` and `.noir/project.id` are\n * theirs to shape, and an upgrade that rewrote one of them would destroy\n * exactly the work this engine exists to protect. Co-owned files (managed\n * blocks) must not set it either: they have a three-way merge that\n * preserves user edits, which is the better tool for a shared file.\n *\n * Which recorded seed the bytes are compared against is declared separately\n * in {@link REFRESHABLE_SEED_KIND}. */\n refreshIfStale?: true;\n /** Host tag; entry is skipped when opts.host !== entry.host.\n * Undefined = host-agnostic (every host emits it). */\n host?: HostTag;\n /** Required for `managedBlock` mode: the named block to re-emit. */\n block?: ManagedBlock;\n /** Literal content (`regenerate`/`skipIfExists`) or literal region BODY\n * (`managedBlock`: the orchestrator wraps it with the block markers).\n * Mutually exclusive with {@link template}. */\n content?: string;\n /** Template name (resolved by `template-loader`) for content/body.\n * Mutually exclusive with {@link content}. */\n template?: string;\n /** One-line human description for `noir doctor` + logs. */\n description?: string;\n /** Permission for a NEWLY created file (e.g. `0o600` for a credential seed).\n * Applied only on creation: the `skipIfExists` contract means an existing\n * file is not even opened. The orchestrator forwards this to the writer and\n * reports it on the result as {@link ScaffoldResult.fileModes}; the mode of\n * an existing credential file is re-asserted separately (`ensureOwnerOnly`).\n * Ignored by every other write mode, whose targets are regenerated rather\n * than seeded.\n * NOTE: POSIX-only. Windows permissions are ACL-based and ignore it. */\n fileMode?: number;\n /** Required for `mergeJson` mode: the JSON patch object (or a template that\n * renders to one). Merged into the existing file, preserving user keys. */\n patch?: Record<string, unknown>;\n /** Required for `mergeJson` mode: dedup substring for `hooks.*` entries: an\n * existing entry whose command contains this is NOT re-added. */\n dedupSubstring?: string;\n}\n\nexport type BuildManifestContext = {\n /** Absolute repo root. Needed so {@link buildHostArtifacts} can resolve\n * absolute adapter paths (`adapter.mcpConfigPath({root})`, etc.) to the\n * manifest's repo-relative POSIX shape. */\n root: string;\n /** Canonical project id (already created/read by the orchestrator). */\n projectId: string;\n /** Target host. Drives {@link buildHostArtifacts} via `resolveAdapter(host)`. */\n host: HostTag;\n /** MCP transport the host should use to reach Noir. */\n transport: 'stdio' | 'streamable-http';\n /** Required when transport is `streamable-http`. */\n url?: string;\n /** The `command` value emitted into the host's MCP config for the stdio\n * server. Defaults to `'noir'`; the orchestrator passes the absolute native\n * shim path (`~/.noir/bin/noir`) when a native install is detected, so GUI\n * MCP clients that don't read shell profiles can spawn the server. See\n * `resolveNoirCommand()` in @noir-ai/core. */\n command: string;\n /** Detected stack: drives stack-aware ignore emission (.npmignore /\n * .prettierignore only for JS; .dockerignore only when a Dockerfile is\n * present; an unknown/empty stack ⇒ all four, for backward compat). */\n stack?: StackInfo;\n /** Compiled project baseline (stack + canonical commands + entry points),\n * rendered into the `.noir/NOIR.md` brief block as `{{baseline}}`. */\n baseline?: string;\n /** The `rules.enabled` switch from the project's `.noir/config.yml`. False\n * drops the working-rules seed from the manifest, so no command emits or\n * backfills `.noir/rules/RULES.md` for a project that opted out. Undefined\n * (the schema's default state, and every caller that predates the switch)\n * means enabled: a project with no `rules:` block emits the seed exactly\n * as before. A file already on disk is never removed: dropping the entry\n * removes the emission, not the file. */\n rulesEnabled?: boolean;\n};\n\n// --- named managed blocks ----------------------------------------------------\n\n/** Co-owned NOIR.md auto-brief region. Defined locally (not exported from\n * core) because core's own named block instances cover only the three\n * regions core itself writes (context/rules/ignore); the brief is the\n * scaffold engine's own. Uses the SAME `managedBlock()` factory so marker\n * shape stays consistent with the rest of the family. */\nexport const BRIEF_BLOCK: ManagedBlock = managedBlock('brief', 'html');\n\n/** Co-owned `.noir/README.md` runtime map. Defined locally\n * for the same reason as {@link BRIEF_BLOCK}: the named\n * instances in core cover only the regions core itself writes. The map describes\n * paths that appear LATER in the project's life, so it must be re-emitted —\n * `regenerate` would clobber a user's annotations and `skipIfExists` would\n * freeze it at init time; a managed block keeps both sides honest. */\nexport const README_BLOCK: ManagedBlock = managedBlock('readme', 'html');\n\n/** The one row of the `.noir/README.md` store map that describes the\n * working-rules seed. The map ships as a single template because it documents\n * the whole canonical store at once, but the rules row describes a file that\n * only exists while `rules.enabled` is on. It is therefore dropped from the\n * rendered text rather than kept in a second template variant: one template\n * keeps the wording (and the surrounding table) in one place, and a test\n * asserts the template still carries exactly one row with this prefix, so a\n * reworded or renamed row fails loudly instead of silently surviving a\n * switch-off emit.\n *\n * A prefix match, not a full-line constant: the row's prose is free to change\n * without a second edit here. */\nconst RULES_MAP_ROW_PREFIX = '| `rules/RULES.md` |';\n\n/** Render the `.noir/README.md` store map: the shipped template, minus the\n * working-rules row when the project has switched the seed off. With the\n * switch on (and for every project that predates it) this is exactly what\n * {@link renderEntry} produced from the plain template. */\nfunction readmeMapContent(ctx: BuildManifestContext): string {\n const rendered = render(loadTemplate('noir-readme.md.tmpl'), ctx);\n if (ctx.rulesEnabled !== false) return rendered;\n return rendered\n .split('\\n')\n .filter((line) => !line.startsWith(RULES_MAP_ROW_PREFIX))\n .join('\\n');\n}\n\n// --- repo-relative path constants (mirror @noir-ai/core/layout.ts) -----------\n// Inlined as string literals so the manifest has zero runtime dep on layout\n// for path strings; the test suite cross-checks against `paths.*`.\n\nconst P = {\n projectId: `${NOIR_DIR}/project.id`,\n config: `${NOIR_DIR}/config.yml`,\n envExample: `${NOIR_DIR}/.env.example`,\n env: `${NOIR_DIR}/.env`,\n noirMd: `${NOIR_DIR}/NOIR.md`,\n rulesMd: `${NOIR_DIR}/rules/RULES.md`,\n readme: `${NOIR_DIR}/README.md`,\n} as const;\n\n// Aliases for the parity test (kept here so a layout rename breaks the test\n// at the same site the literal lives, not in a far-off helper).\nexport const MANIFEST_PATH_PARITY: ReadonlyArray<\n [entryPath: string, layoutFn: (root: string) => string]\n> = [\n [P.projectId, paths.projectId],\n [P.config, paths.config],\n [P.noirMd, paths.noirMd],\n [P.rulesMd, paths.rulesMd],\n];\n\n/**\n * Which recorded seed each refreshable entry is compared against, keyed by the\n * path that emits it: see {@link ManifestEntry.refreshIfStale}.\n *\n * The history entries record the text of BOTH seeds, so the comparison has to\n * be told which one it is looking at. The manifest is what knows which path\n * holds which seed, and keeping that pairing in a table beside the path\n * constants means a path change breaks at the same site instead of silently\n * comparing `.noir/.env.example` against the recorded working-rules text: a\n * mismatch whose only symptom would be a file the user edited, overwritten.\n *\n * `manifest.test.ts` asserts this table and the flagged entries cover each\n * other, so a refreshable entry cannot be added without naming its seed.\n */\nexport const REFRESHABLE_SEED_KIND: Readonly<Record<string, SeedKind>> = {\n [P.envExample]: 'envExample',\n [P.rulesMd]: 'rulesSeed',\n};\n\n/**\n * Build the manifest for a given ctx. Pure (no I/O). The orchestrator calls\n * this once per scaffold run; tests assert the shape is stable.\n *\n * The manifest is `[...hostAgnosticEntries(ctx), ...hostSpecificEntries(ctx)]`\n * where the host-specific half comes from {@link buildHostArtifacts} (driven by\n * `resolveAdapter(ctx.host)`). The host-agnostic half is unchanged from v1.1\n * (canonical `.noir/` store + ignore files). {@link buildHostArtifacts}\n * emits AGENTS.md ONLY for agents-md/cursor/opencode (claude/gemini use their\n * own CLAUDE.md/GEMINI.md: emitting AGENTS.md too would double-import .noir/).\n *\n * Mode-tagging rationale per artifact:\n * - `project.id` → skipIfExists. First init writes a fresh id; re-init MUST\n * NOT overwrite: that would orphan the indexed store DB named after it.\n * - `config.yml` → skipIfExists. User-owned; the seed is written once.\n * (The seed renders `host: {{host}}` so a `--host gemini` init persists the\n * chosen host for `noir sync` to read back.)\n * - `NOIR.md` → managedBlock (BRIEF_BLOCK). Auto-brief is co-owned.\n * - `RULES.md` → skipIfExists. User-owned working-contract seed.\n * - `README.md` → managedBlock (README_BLOCK). The `.noir/` runtime map is\n * regenerated as the layout grows; user notes outside the markers survive.\n * - ignore files → managedBlock (IGNORE_BLOCK). Matches syncIgnores.\n * - host entries → SEE {@link buildHostArtifacts} (regenerate / managedBlock).\n */\nexport function buildManifest(ctx: BuildManifestContext): ManifestEntry[] {\n return [...hostAgnosticEntries(ctx), ...buildHostArtifacts(resolveAdapter(ctx.host), ctx)];\n}\n\n/** The host-agnostic canonical-store + ignore entries: identical bytes for\n * every host. Split out so {@link buildHostArtifacts} can be unit-tested in\n * isolation and so the doctor's host-artifacts check can reason about the\n * host-specific half alone. */\nfunction hostAgnosticEntries(ctx: BuildManifestContext): ManifestEntry[] {\n const entries: ManifestEntry[] = [\n {\n path: P.projectId,\n mode: 'skipIfExists',\n content: `${ctx.projectId}\\n`,\n description: 'canonical project id (store DB is named after it)',\n },\n {\n path: P.config,\n mode: 'skipIfExists',\n template: 'config.yml.tmpl',\n description: 'user config seed (host + mode)',\n },\n {\n // Refreshable: the variable set Noir documents grows between releases,\n // and this file is pure documentation the user is never asked to edit —\n // so an upgrade may replace an untouched older copy of it.\n path: P.envExample,\n mode: 'skipIfExists',\n template: 'env.example.tmpl',\n refreshIfStale: true,\n description: '.noir/.env.example committable documentation (never loaded)',\n },\n {\n // init creates the REAL env file, not just the example. The body is\n // all-comment, so the file parses to an EMPTY overlay and creating it\n // changes no behaviour: it exists so the user never has to copy the\n // example by hand. `fileMode: 0o600` because the file holds tokens the\n // moment the user edits it. The mode is applied when the file is created;\n // a copy that predates this contract (or that an editor saved by rename)\n // has its mode re-asserted on every init/sync/create run: see `ensureOwnerOnly`.\n path: P.env,\n mode: 'skipIfExists',\n template: 'config.env.tmpl',\n fileMode: 0o600,\n description: 'project-scoped env file (gitignored, 0600)',\n },\n {\n path: P.noirMd,\n mode: 'managedBlock',\n block: BRIEF_BLOCK,\n template: 'noir.md.tmpl',\n description: 'NOIR.md auto-brief (project id pointer)',\n },\n {\n // Refreshable: shipped as a starting contract the user is INVITED to\n // rewrite, so only the never-edited copy may be swapped for a newer one.\n path: P.rulesMd,\n mode: 'skipIfExists',\n template: 'rules-seed.md.tmpl',\n refreshIfStale: true,\n description: 'AI working-rules seed',\n },\n {\n // The `.noir/` runtime map: what init just wrote,\n // what appears later (and which command creates it), and where to go\n // next. Host-agnostic: it describes the canonical store, which every\n // host shares. Co-owned (README_BLOCK) so user notes survive while the\n // map stays current through `noir sync` / `init --upgrade`.\n //\n // Rendered here rather than left to `renderEntry` because one row of the\n // map depends on the project: the working-rules row is dropped when\n // `rules.enabled` is off (see {@link readmeMapContent}).\n path: P.readme,\n mode: 'managedBlock',\n block: README_BLOCK,\n content: readmeMapContent(ctx),\n description: '.noir/ runtime map (what exists now / what appears later)',\n },\n\n // --- ignore files (host-agnostic; co-owned via IGNORE_BLOCK) ------------\n {\n path: '.gitignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'gitignore.tmpl',\n description: '.gitignore noir managed block',\n },\n {\n path: '.dockerignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'dockerignore.tmpl',\n description: '.dockerignore noir managed block',\n },\n {\n path: '.npmignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'npmignore.tmpl',\n description: '.npmignore noir managed block',\n },\n {\n path: '.prettierignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'prettierignore.tmpl',\n description: '.prettierignore noir managed block',\n },\n ];\n // Stack-aware ignore emission. Only emit the ignore\n // files relevant to the detected stack. An unknown/empty stack (no language\n // markers, no package manager: e.g. a blank dir or undetectable project) ⇒\n // emit all four (backward-compatible with the pre-fix behavior).\n const isEmpty = (ctx.stack?.languages?.length ?? 0) === 0 && !ctx.stack?.packageManager;\n const isJs =\n isEmpty ||\n (ctx.stack?.languages?.some((l) => l === 'typescript' || l === 'javascript') ?? false) ||\n ['npm', 'pnpm', 'yarn'].includes(ctx.stack?.packageManager ?? '');\n const hasDocker =\n isEmpty ||\n existsSync(join(ctx.root, 'Dockerfile')) ||\n existsSync(join(ctx.root, 'docker-compose.yml')) ||\n existsSync(join(ctx.root, 'compose.yaml'));\n // The working-rules seed follows the project's own `rules.enabled` switch.\n const rulesEnabled = ctx.rulesEnabled !== false;\n return entries.filter((e) => {\n if (e.path === P.rulesMd) return rulesEnabled;\n if (e.path === '.npmignore' || e.path === '.prettierignore') return isJs;\n if (e.path === '.dockerignore') return hasDocker;\n return true;\n });\n}\n\n// Host-specific artifact generation. One entry point: `buildHostArtifacts`.\n\n/** Context shape passed to {@link buildHostArtifacts}. A strict subset of\n * {@link BuildManifestContext} (no `projectId`/`host`: the adapter IS the\n * resolved host, and host artifacts never need the project id). Exported\n * separately so callers + tests can name the narrower contract. */\nexport interface BuildHostArtifactsContext {\n root: string;\n transport: 'stdio' | 'streamable-http';\n url?: string;\n /** The `command` for the stdio MCP server entry (absolute native shim when a\n * native install is detected, else `'noir'`). Passed through to\n * `adapter.emitMcpConfig`. */\n command: string;\n /** The project's `rules.enabled` switch. False drops the rules `@`-import a\n * host would otherwise emit (CLAUDE.md's rules block, GEMINI.md's rules\n * block, AGENTS.md's rules import) so no host file points at a\n * `.noir/rules/RULES.md` that the switch withheld. Undefined means enabled —\n * byte-identical to every build that predates the switch. */\n rulesEnabled?: boolean;\n}\n\n/**\n * Materialize the host-specific manifest entries from a resolved adapter.\n * SINGLE entry point: no scattered `if (host === '…')` conditionals in the\n * orchestrator. Returns entries in emission order:\n *\n * 1. **AGENTS.md** (universal baseline): `regenerate` at\n * `adapter.agentsMdPath(ctx)` (default `<root>/AGENTS.md`), content from\n * the shared `emitAgentsMd(ctx)` helper. Emitted ONLY for hosts whose\n * `emitContext` IS the AGENTS.md content (agents-md, cursor, opencode) —\n * for them AGENTS.md is the SINGLE native context surface AND carries the\n * Noir working rules via its `@.noir/rules/RULES.md` import. claude and\n * gemini have their OWN native context file (CLAUDE.md / GEMINI.md) that\n * `@`-imports the canonical `.noir/` sources; emitting AGENTS.md too\n * would IMPORT THOSE FILES TWICE into the host's context (2× tokens +\n * drift risk), so for those two hosts AGENTS.md is SKIPPED. (Claude Code\n * still discovers AGENTS.md at the repo root when present: users who\n * want the universal file can drop one in by hand; Noir's auto-emission\n * stays single-source per host.)\n * 2. **Host-native context file**: emitted ONLY for hosts whose `emitContext`\n * is NOT the AGENTS.md content (i.e. the host has its OWN context file\n * with a distinct syntax). Concretely: claude → `CLAUDE.md` (CONTEXT +\n * RULES managed blocks, byte-identical to v1.1 via templates); gemini →\n * `GEMINI.md` (CONTEXT + RULES managed blocks with Gemini's bare `@`\n * import syntax). For `agents-md`/`cursor`/`opencode` the context IS the\n * AGENTS.md (already emitted in step 1) → SKIP to avoid a duplicate.\n * Rules live INSIDE the host's context file: claude's in CLAUDE.md,\n * gemini's in GEMINI.md, agents-md/cursor/opencode's in AGENTS.md: NO\n * host emits a separate rules file. (The prior cursor\n * `.cursor/rules/noir-contract.mdc` host-rules pointer was REMOVED: it\n * collided with the cursor flat-skill prune of `noir-*.mdc` under\n * `.cursor/rules/`, and cursor's rules are already delivered via\n * AGENTS.md's `@.noir/rules/RULES.md` import.)\n *\n * That rules import/block is dropped for every host when\n * `ctx.rulesEnabled` is false: the switch withholds the seed, so pointing\n * a host at it would resolve to nothing. The context import is unaffected\n * : the switch gates the rules, not the brief. A host file that already\n * carries the rules region from when the switch was on keeps it: dropping\n * the entry stops the emission, and nothing here rewrites a region out of\n * an existing file.\n * 3. **Host MCP config**: `regenerate` at `adapter.mcpConfigPath(ctx)`\n * (default `<root>/.mcp.json` for claude), content from\n * `adapter.emitMcpConfig(ctx, {transport,url})`. Claude KEEPS the template\n * path (byte-identical parity with v1.1 + the .mcp.json parity test that\n * compares against `claudeAdapter.emitMcpConfig`) for a repo that has not\n * joined a workspace; a joined repo: one carrying a `.noir/workspace.json`\n * marker: gets the workspace entry instead, on every host, whatever\n * transport the run asked for (see the entry's own comment below).\n *\n * Skills are OUT OF SCOPE here: the cli composes `emitSkillsToDir` with\n * `adapter.skillsDir` + the host's `CompileTarget` (claude → `.claude/skills/`\n * as SKILL.md; cursor → `.cursor/rules/<skill>.mdc` FLAT; gemini/\n * agents-md/opencode have no skill dir → skip).\n */\nexport function buildHostArtifacts(\n adapter: HostAdapter,\n ctx: BuildHostArtifactsContext,\n): ManifestEntry[] {\n const rulesEnabled = ctx.rulesEnabled !== false;\n const ectx: EmitContext = { root: ctx.root, rulesEnabled };\n const host = adapter.id;\n const entries: ManifestEntry[] = [];\n\n // 1. AGENTS.md: emitted for hosts whose emitContext IS the AGENTS.md content\n // (agents-md, cursor, opencode). SKIPPED for claude/gemini: their native\n // CLAUDE.md / GEMINI.md already @-import the canonical .noir/ sources, so\n // a root AGENTS.md would double-import (2× context tokens, drift risk).\n // This also restores the claude default `noir init` to byte-identity with\n // v1.1 (the prior additive AGENTS.md delta is removed).\n const emitsAgentsMd = host === 'agents-md' || host === 'cursor' || host === 'opencode';\n if (emitsAgentsMd) {\n entries.push({\n path: hostRel(adapter.agentsMdPath?.(ectx) ?? join(ctx.root, AGENTS_MD_FILENAME), ctx.root),\n mode: 'regenerate',\n host,\n content: emitAgentsMd(ectx),\n description: `AGENTS.md (${host}'s native context surface; @-imports .noir/)`,\n });\n }\n\n // 2. Host-native context file (when distinct from AGENTS.md) + folded rules.\n switch (host) {\n case 'claude':\n // CLAUDE.md keeps template-based bodies: byte-identical to v1.1 (the\n // scaffold.test.ts parity gates compare against claudeAdapter.emitContext\n // + emitRules; the templates render to the same body bytes).\n entries.push({\n path: 'CLAUDE.md',\n mode: 'managedBlock',\n host,\n block: CONTEXT_BLOCK,\n template: 'claude-context-block.md.tmpl',\n description: 'CLAUDE.md context @import block',\n });\n // The rules block only exists when the project HAS a rules file. With the\n // switch off the block is not emitted: a fresh project gets no dangling\n // `@import`, and a project that already carries the block from when the\n // switch was on keeps it (off stops the emission; it never cuts a region\n // out of a user's file).\n if (rulesEnabled) {\n entries.push({\n path: 'CLAUDE.md',\n mode: 'managedBlock',\n host,\n block: RULES_BLOCK,\n template: 'claude-rules-block.md.tmpl',\n description: 'CLAUDE.md rules @import block',\n });\n }\n break;\n case 'gemini':\n // GEMINI.md carries CONTEXT_BLOCK + RULES_BLOCK with Gemini's bare\n // `@file` import syntax (no `@import` keyword, no quotes: distinct from\n // Claude's form). Emitted as TWO managed regions so user content outside\n // the markers survives `noir sync` (same write path as CLAUDE.md: the\n // multi-region atomic `managedBlocks` writer).\n entries.push({\n path: 'GEMINI.md',\n mode: 'managedBlock',\n host,\n block: CONTEXT_BLOCK,\n content: '@.noir/NOIR.md',\n description: 'GEMINI.md context @-import block',\n });\n // Same rule as claude's: no rules file, no rules import.\n if (rulesEnabled) {\n entries.push({\n path: 'GEMINI.md',\n mode: 'managedBlock',\n host,\n block: RULES_BLOCK,\n content: '@.noir/rules/RULES.md',\n description: 'GEMINI.md rules @-import block',\n });\n }\n break;\n case 'agents-md':\n case 'cursor':\n case 'opencode':\n // emitContext IS the AGENTS.md content (already emitted in step 1) →\n // no separate context file. Rules are carried by AGENTS.md's\n // `@.noir/rules/RULES.md` import (agents-md/cursor/opencode share that\n // universal surface: NO host emits a separate rules file).\n break;\n }\n\n // 3. Host MCP config. Claude keeps the template path for a repo that has not\n // joined a workspace (byte-identical parity gate); every other case goes\n // through the adapter, so the entry is rendered by the same code the host's\n // own emitter uses.\n //\n // A repo that has JOINED a workspace gets the workspace entry, and that\n // decision belongs to the marker: not to the transport this run asked for.\n // Membership is what decides how the host reaches the daemon (through the\n // stdio bridge, which reads the token itself), so re-scaffolding must never\n // drop a repo out of a workspace the marker still says it belongs to, nor\n // write an address into its config. Reading the marker HERE, where the entry\n // is chosen, means no caller can bypass it: `sync`, `init --force`,\n // `create --force` and the doctor's expectation check all follow membership\n // for free.\n const mcpAbs = mcpConfigPathForAdapter(adapter, ctx.root);\n const mcpRel = hostRel(mcpAbs, ctx.root);\n const workspace = readWorkspaceMarker(ctx.root);\n if (host === 'claude' && workspace === null) {\n const mcpTemplate =\n ctx.transport === 'streamable-http' ? 'mcp.http.json.tmpl' : 'mcp.stdio.json.tmpl';\n entries.push({\n path: mcpRel,\n mode: 'regenerate',\n host,\n template: mcpTemplate,\n description: 'host MCP server pointer',\n });\n } else {\n const mcpContent = `${adapter.emitMcpConfig(\n ectx,\n workspace !== null\n ? // Joined: stdio + the workspace name, whatever transport was asked for.\n { transport: 'stdio', command: ctx.command, workspace }\n : {\n transport: ctx.transport,\n command: ctx.command,\n ...(ctx.url !== undefined ? { url: ctx.url } : {}),\n },\n )}\\n`;\n entries.push({\n path: mcpRel,\n mode: 'regenerate',\n host,\n content: mcpContent,\n description:\n workspace !== null\n ? `${host} MCP server pointer (workspace \"${workspace}\")`\n : `${host} MCP server pointer`,\n });\n }\n\n // 4. Lifecycle hooks: per host, via the adapter seam. The shared `noir hook`\n // dispatcher carries all policy (brief injection, deny-list, Stop gate);\n // each adapter returns only the thin per-host config that invokes it with a\n // `--host` dialect. Hosts with no hook surface (agents-md, opencode) return\n // none. The `.noir/router.md` + `.noir/rules/anti-slop.md` sources feed the\n // hook's compiled brief, so they are emitted for any host that emits hooks.\n const hookEmissions = adapter.emitHooks?.({ command: ctx.command }) ?? null;\n if (hookEmissions !== null) {\n entries.push({\n path: '.noir/router.md',\n mode: 'managedBlock',\n host,\n block: CONTEXT_BLOCK,\n template: 'router.md.tmpl',\n description: 'skill router contract (co-owned managed block)',\n });\n if (rulesEnabled) {\n entries.push({\n path: '.noir/rules/anti-slop.md',\n mode: 'regenerate',\n host,\n template: 'anti-slop.md.tmpl',\n description: 'always-on anti-slop + laziness ruleset (hook-injected, not @imported)',\n });\n }\n for (const emission of hookEmissions) {\n const json = JSON.stringify(emission.content, null, 2);\n entries.push({\n path: emission.path,\n mode: emission.mode,\n host,\n content: emission.mode === 'regenerate' ? `${json}\\n` : json,\n ...(emission.dedupSubstring !== undefined\n ? { dedupSubstring: emission.dedupSubstring }\n : {}),\n description: emission.description,\n });\n }\n }\n\n return entries;\n}\n\n/** Convert an absolute path under `root` to a repo-relative POSIX string (the\n * manifest's path shape). Throws if `abs` is NOT under `root` so a future\n * adapter that returns a stray path fails loudly instead of producing a\n * malformed manifest entry. */\nfunction hostRel(abs: string, root: string): string {\n const rel = relative(root, abs);\n if (rel.length === 0 || rel.startsWith('..') || rel.startsWith('/')) {\n throw new Error(`buildHostArtifacts: path '${abs}' is not under root '${root}'`);\n }\n // Normalize any platform separators to POSIX (manifest paths are POSIX).\n return rel.replace(/\\\\/g, '/');\n}\n","/**\n * Hand-rolled `{{var}}` interpolation. No mustache/handlebars dependency —\n * Noir's markdown/yaml/json templates are simple enough that a tokenizer +\n * map lookup is sufficient and keeps `@noir-ai/create` dependency-light.\n *\n * Semantics (documented):\n * - Tokens are `{{ name }}` / `{{name}}`: any amount of ASCII whitespace\n * inside the braces is permitted. The name is trimmed.\n * - Known vars (value is a string) are substituted verbatim. NO escaping is\n * applied: callers must pre-escape for the target format (JSON/YAML/MD).\n * This is intentional: the engine renders into multiple formats and a\n * single universal escaper would be wrong for at least one of them.\n * - Unknown vars (not in `vars`, or value `undefined`) → the original token\n * is LEFT IN PLACE. Rationale: drift between template and ctx becomes\n * visible (an unrendered `{{projectId}}` in CLAUDE.md is immediately\n * obvious), rather than silently swallowed to empty. The spec called this\n * out as the preferred failure mode.\n * - Non-string var values are stringified via `String(value)` so a stray\n * number/boolean still interpolates rather than printing `[object Object]`.\n * - A lone `{{` with no closing `}}` is left untouched (not a token).\n */\n\nconst TOKEN = /\\{\\{\\s*([^}{}]+?)\\s*\\}\\}/g;\n\nexport function render(template: string, vars: Record<string, unknown>): string {\n return template.replace(TOKEN, (whole, name: string) => {\n if (!Object.hasOwn(vars, name)) return whole; // unknown → leave token\n const v = vars[name];\n if (v === undefined || v === null) return whole; // treat as unknown → leave token\n return String(v);\n });\n}\n","import { readFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Template loader. Templates ship at `<pkg>/templates/` (see `files` in\n * package.json) and are read at runtime so non-code changes (copy tweaks, new\n * pointers) don't require a rebuild.\n *\n * Path resolution must work identically in three layouts:\n * - source (vitest, tsx): this file at `packages/create/src/template-loader.ts`\n * → `../templates/<name>`.\n * - built (tsup): this module is inlined into the package's single bundle at\n * `packages/create/dist/index.js` → `../templates/<name>`. `dist/` and\n * `src/` are both direct children of the package root, so the relative\n * offset is unchanged and `import.meta.url`: which points at the bundle,\n * not at a `template-loader.js`: still resolves correctly.\n * - packed (npm tarball): `templates/` is included per `files`, dist/ layout\n * preserved → same as built.\n *\n * `NOIR_TEMPLATES_DIR` overrides everything (used by tests + downstream packs\n * that want to substitute their own template set without forking the engine).\n */\n\nconst DEFAULT_TEMPLATES_DIR = resolveTemplatesDir();\n\nfunction resolveTemplatesDir(): string {\n const override = process.env.NOIR_TEMPLATES_DIR;\n if (override && override.length > 0) return resolve(override);\n // `import.meta.url` is this module's URL. Going up one level reaches the\n // package root in both source and built layouts (see file header).\n const here = dirname(fileURLToPath(import.meta.url));\n return join(here, '..', 'templates');\n}\n\n/** Read a template's raw text by name (e.g. `noir.md.tmpl`). Throws on missing\n * : an unknown template is a manifest bug and should fail loudly, not render\n * an empty string silently. */\nexport function loadTemplate(name: string): string {\n return readFileSync(join(DEFAULT_TEMPLATES_DIR, name), 'utf8');\n}\n\n/** Resolve a template name to its absolute path (for diagnostics + tests). */\nexport function templatesDir(): string {\n return DEFAULT_TEMPLATES_DIR;\n}\n","// Three-way merge for managed-block regions.\n//\n// When a user hand-edits the INSIDE of a `<!-- noir:* -->` managed region and a\n// later `noir init`/`sync` updates the template, this merges (base/ours/theirs)\n// instead of strip-replacing (which would silently clobber the user's edit).\n// Line-level diff3: disjoint line changes merge cleanly; overlapping changes\n// surface as inline `<<<<<<< / ======= / >>>>>>>` markers for manual resolution.\n// Never silently drops either side.\n\nexport interface MergeResult {\n /** The merged text. When `conflict` is true, contains inline markers. */\n merged: string;\n /** True when ours and theirs diverged in the same region (markers emitted). */\n conflict: boolean;\n}\n\n/** Whole-string short-circuits for the trivial cases (also covers empty). */\nfunction trivial(base: string, ours: string, theirs: string): MergeResult | null {\n if (ours === base) return { merged: theirs, conflict: false };\n if (theirs === base) return { merged: ours, conflict: false };\n if (ours === theirs) return { merged: ours, conflict: false };\n return null;\n}\n\n/** Read index `i` of `arr` as a number (0 when out of range): sidesteps\n * noUncheckedIndexedAccess without non-null assertions. */\nfunction numAt(arr: readonly number[], i: number): number {\n return arr[i] ?? 0;\n}\n\n/** Longest-common-subsequence match pairs between two line arrays. */\nexport function lcsMatch(a: readonly string[], b: readonly string[]): Array<[number, number]> {\n const n = a.length;\n const m = b.length;\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0));\n for (let i = n - 1; i >= 0; i--) {\n const dpi = dp[i] ?? [];\n const dpi1 = dp[i + 1] ?? [];\n const ai = a[i] ?? '';\n for (let j = m - 1; j >= 0; j--) {\n const bj = b[j] ?? '';\n dpi[j] = ai === bj ? numAt(dpi1, j + 1) + 1 : Math.max(numAt(dpi1, j), numAt(dpi, j + 1));\n }\n }\n const out: Array<[number, number]> = [];\n let i = 0;\n let j = 0;\n while (i < n && j < m) {\n if ((a[i] ?? '') === (b[j] ?? '')) {\n out.push([i, j]);\n i++;\n j++;\n } else if (numAt(dp[i + 1] ?? [], j) >= numAt(dp[i] ?? [], j + 1)) {\n i++;\n } else {\n j++;\n }\n }\n return out;\n}\n\n/**\n * Unified line diff for the conflict resolver's stderr preview. Returns a\n * sequence of {type:'eq'|'del'|'add', line} records, LCS-based, equivalent in\n * shape to `git diff --no-color`. Pure (no IO) so it unit-tests cleanly and the\n * resolver can render it through {@link packages/cli/src/theme.ts} (`+` green,\n * `-` red, context dim). Reuses the SAME LCS machinery {@link mergeThreeWay}\n * does so there is exactly one line-diff algorithm in the package.\n *\n * `context` (default 3): number of unchanged lines to keep around each hunk\n * (git-style). 0 → every non-equal line stands alone; large → whole-file blob.\n * The resolver uses the default; tests pin to a specific shape.\n */\nexport interface DiffLine {\n type: 'eq' | 'del' | 'add';\n line: string;\n}\nexport function lineDiff(base: string, head: string, context = 3): DiffLine[] {\n const A = base.split('\\n');\n const B = head.split('\\n');\n const pairs = lcsMatch(A, B);\n const inA = new Set<number>(pairs.map((p) => p[0]));\n const inB = new Set<number>(pairs.map((p) => p[1]));\n // Walk both sequences in lockstep, emitting del/eq/add per the LCS anchors.\n const out: DiffLine[] = [];\n let i = 0;\n let j = 0;\n // Build a baseIdx→headIdx map so we can advance along matched pairs.\n const bToH = new Map<number, number>();\n for (const [bi, hi] of pairs) bToH.set(bi, hi);\n while (i < A.length || j < B.length) {\n const matched = bToH.get(i);\n if (matched !== undefined && matched === j) {\n out.push({ type: 'eq', line: A[i] ?? '' });\n i++;\n j++;\n } else if (matched !== undefined) {\n // Head is behind: emit adds up to the matched head index.\n out.push({ type: 'add', line: B[j] ?? '' });\n j++;\n } else if (i < A.length && !inA.has(i)) {\n // A-only line (deleted).\n out.push({ type: 'del', line: A[i] ?? '' });\n i++;\n } else if (j < B.length && !inB.has(j)) {\n // B-only line (added).\n out.push({ type: 'add', line: B[j] ?? '' });\n j++;\n } else {\n // Both at matched anchors but not paired: advance the smaller index.\n if (i < A.length) i++;\n else j++;\n }\n }\n return context > 0 ? collapseContext(out, context) : out;\n}\n\n/** Collapse runs of >2*`context` unchanged lines into a single `...` hunk\n * separator so the preview stays readable on long files. Pure trim: never\n * drops a del/add. */\nfunction collapseContext(lines: readonly DiffLine[], context: number): DiffLine[] {\n const out: DiffLine[] = [];\n const keep = new Array<boolean>(lines.length).fill(false);\n // Mark `context` lines around each del/add.\n for (let i = 0; i < lines.length; i++) {\n const dl = lines[i];\n if (dl && dl.type !== 'eq') {\n for (let k = Math.max(0, i - context); k <= Math.min(lines.length - 1, i + context); k++) {\n keep[k] = true;\n }\n }\n }\n let inRun = false;\n for (let i = 0; i < lines.length; i++) {\n const dl = lines[i];\n if (!dl) continue;\n if (keep[i]) {\n out.push(dl);\n inRun = false;\n } else {\n if (!inRun) {\n out.push({ type: 'eq', line: '…' });\n inRun = true;\n }\n }\n }\n return out;\n}\n\n/**\n * zdiff3-style conflict markers for an unresolved overlap. Shape:\n * `<<<<<<< ours`\n * <ours lines>\n * `||||||| base`\n * <base lines>\n * `=======`\n * <theirs lines>\n * `>>>>>>> theirs`\n *\n * `diff3(zdiff3)` writes the SAME shape: included here so the engine + clack\n * resolver have a single source of truth for marker layout. Returns the marked\n * region body (no outer delimiters); the caller wraps it with surrounding\n * context.\n */\nexport function zdiff3Region(\n ours: readonly string[],\n base: readonly string[],\n theirs: readonly string[],\n): string[] {\n return ['<<<<<<< ours', ...ours, '||||||| base', ...base, '=======', ...theirs, '>>>>>>> theirs'];\n}\n\nconst eq = (a: readonly string[], b: readonly string[]): boolean =>\n a.length === b.length && a.every((v, k) => v === b[k]);\n\n/**\n * Three-way merge of `ours` (the user's current region) against `theirs` (the\n * new template) with `base` (the last-emitted ancestor). Line-level diff3:\n * changes in disjoint line ranges merge; overlapping changes become inline\n * conflict markers. Default marker shape is the minimal diff3 form\n * (`<<<<<<< ours` / `=======` / `>>>>>>> theirs`): byte-identical to v1.2 so\n * {@link mergeManagedRegion} stays regression-anchored. Pass `'zdiff3'` to\n * include the base section (`||||||| base`): used by the conflict resolver's\n * 6th \"merge (with conflict markers)\" option. Pure + deterministic\n * (no IO) so it's unit-testable.\n */\nexport function mergeThreeWay(\n base: string,\n ours: string,\n theirs: string,\n style: 'minimal' | 'zdiff3' = 'minimal',\n): MergeResult {\n const t = trivial(base, ours, theirs);\n if (t) return t;\n\n const B = base.split('\\n');\n const O = ours.split('\\n');\n const T = theirs.split('\\n');\n const oMap = new Map<number, number>(); // baseIdx → oursIdx\n for (const [bi, oi] of lcsMatch(B, O)) oMap.set(bi, oi);\n const tMap = new Map<number, number>(); // baseIdx → theirsIdx\n for (const [bi, ti] of lcsMatch(B, T)) tMap.set(bi, ti);\n const isAnchor = (bi: number): boolean => oMap.has(bi) && tMap.has(bi);\n\n const anchors: number[] = [];\n for (let b = 0; b < B.length; b++) if (isAnchor(b)) anchors.push(b);\n const pts: number[] = [-1, ...anchors, B.length];\n\n const out: string[] = [];\n let conflict = false;\n for (let k = 0; k < pts.length - 1; k++) {\n const aBase = pts[k];\n const bBase = pts[k + 1];\n if (aBase === undefined || bBase === undefined) break;\n const baseSeg = B.slice(aBase + 1, bBase);\n const oStart = aBase >= 0 ? (oMap.get(aBase) ?? -1) + 1 : 0;\n const oEnd = bBase < B.length ? (oMap.get(bBase) ?? 0) : O.length;\n const tStart = aBase >= 0 ? (tMap.get(aBase) ?? -1) + 1 : 0;\n const tEnd = bBase < B.length ? (tMap.get(bBase) ?? 0) : T.length;\n const oSeg = O.slice(oStart, oEnd);\n const tSeg = T.slice(tStart, tEnd);\n if (eq(oSeg, baseSeg) && eq(tSeg, baseSeg)) out.push(...baseSeg);\n else if (eq(oSeg, baseSeg)) out.push(...tSeg);\n else if (eq(tSeg, baseSeg)) out.push(...oSeg);\n else if (eq(oSeg, tSeg)) out.push(...oSeg);\n else {\n conflict = true;\n if (style === 'zdiff3') {\n out.push(...zdiff3Region(oSeg, baseSeg, tSeg));\n } else {\n out.push('<<<<<<< ours', ...oSeg, '=======', ...tSeg, '>>>>>>> theirs');\n }\n }\n if (bBase < B.length) out.push(B[bBase] ?? '');\n }\n return { merged: out.join('\\n'), conflict };\n}\n","import { existsSync, readdirSync, readFileSync, rmdirSync, rmSync, writeFileSync } from 'node:fs';\nimport { join, relative, sep } from 'node:path';\nimport { type HostId, mcpConfigPathFor, noirStdioArgs, TRANSPORT_KEYS } from '@noir-ai/adapters';\nimport {\n atomicWriteFile,\n loadProjectInfo,\n readProjectConfig,\n readWorkspaceMarker,\n resolveNoirCommand,\n} from '@noir-ai/core';\nimport { render } from '../template.js';\nimport { isStaleSeed, type SeedKind } from '../template-history.js';\nimport { loadTemplate } from '../template-loader.js';\nimport { refreshSeed } from '../writers.js';\nimport type { MigrationContext, MigrationResult, MigrationScript } from './types.js';\n\nexport { runMigrations } from './runner.js';\nexport type { MigrationContext, MigrationResult, MigrationScript } from './types.js';\n\n/**\n * Migration registry: the linear history of scaffold-version upgrades.\n *\n * `CURRENT_SCAFFOLD_VERSION` is `1.5.0`; a project stamped at an older version\n * migrates through every entry whose window covers it. Six entries ship today:\n * the synthetic `1.0.0 → 1.0.0` runner-proof, the first REAL migration\n * `1.0.0 → 1.1.0` (which performs the transformation `skipIfExists` cannot),\n * `1.1.0 → 1.2.0`, which refreshes the doc-only seed whose text changed in\n * that release and carries the unchanged RULES.md seed forward for parity,\n * `1.2.0 → 1.3.0`, which repairs the stale workspace pointer a repo that joined\n * a workspace under the older flow still carries, `1.3.0 → 1.4.0`, which moves\n * the SessionStart hook command onto `noir hook`, and `1.4.0 → 1.5.0`, which\n * appends the enforcement events to the claude hook and backfills the gemini\n * hook.\n *\n * Convention:\n * - `from`/`to` are bare `x.y.z` (no `v` prefix, no pre-release); the runner\n * compares them numerically.\n * - Every `run` MUST be idempotent and non-throwing (capture failures into\n * `result.conflicts`). See {@link types.ts}.\n * - Conflict resolution writes git-style markers inline: see\n * {@link applyWithConflict} for the canonical helper.\n * - Paths in `changed`/`conflicts` are repo-relative and POSIX-separated, like\n * every other path the engine reports.\n */\n\n/** Synthetic 1.0.0 → 1.0.0 migration. Proves the runner wires up; also\n * demonstrates the conflict-marker path with a guarded, idempotent touch on\n * `.noir/scaffold-version` only when explicitly asked via\n * `NOIR_TEST_FORCE_CONFLICT`. It STAYS now that a real migration has landed:\n * the real entry is a one-shot transformation of user content, while this one\n * is the registry's permanent, side-effect-free smoke test for the runner and\n * its conflict-marker plumbing. */\nconst synthetic: MigrationScript = {\n from: '1.0.0',\n to: '1.0.0',\n description: 'no-op synthetic migration (runner smoke test)',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n // The only \"real\" thing it does: when the env var is set, write a conflict\n // marker into `.noir/scaffold-version` so the runner's conflict plumbing is\n // exercised by tests. In normal operation this branch never fires and the\n // script is a true no-op.\n if (process.env.NOIR_TEST_FORCE_CONFLICT === '1' && !ctx.dryRun) {\n const file = join(ctx.root, '.noir', 'scaffold-version');\n if (existsSync(file)) {\n const prev = readFileSync(file, 'utf8');\n const merged = applyInlineConflict(prev, 'noir-scaffold=1.0.0\\n', 'ours', 'theirs');\n writeFileSync(file, merged, 'utf8');\n result.conflicts.push('.noir/scaffold-version');\n }\n }\n result.notes.push('synthetic 1.0.0→1.0.0 migration ran');\n return result;\n },\n};\n\n// --- 1.0.0 → 1.1.0: the first real migration -------------------------------\n\n/** Marker line that guards the `.noir/.env` pointer block in `config.yml`.\n * Its presence means the block is already on disk: emitted by\n * `config.yml.tmpl` for a new project, or appended by an earlier run of this\n * migration: so the append is skipped. That guard is what makes the\n * migration idempotent.\n *\n * `config.yml.tmpl` carries the same literal (a template cannot import a\n * TypeScript constant); `migration-1_0_0.test.ts` asserts the two agree so the\n * guard cannot drift out from under the template. */\nexport const ENV_POINTER_MARKER = '# noir:env-pointer';\n\n/** The comment block appended to an EXISTING `.noir/config.yml`. It states the\n * two things a reader of that file needs: it is committable, and secrets go in\n * `.noir/.env`. Deliberately comment-only: appending YAML keys would change\n * the parsed config, which a migration must never do. */\nconst ENV_POINTER_BLOCK = [\n ENV_POINTER_MARKER,\n '# This file is safe to commit: it holds no secrets.',\n '# Project-scoped values and secrets belong in .noir/.env (gitignored, mode',\n '# 0600). .noir/.env.example documents the full variable set; `noir env`',\n '# reports which source wins for each key.',\n '',\n].join('\\n');\n\n/** Append `block` to `prev` with exactly one blank line between the user's\n * content and the block (no blank line at all when the file is empty). */\nfunction appendSeparated(prev: string, block: string): string {\n if (prev.length === 0) return block;\n if (prev.endsWith('\\n\\n')) return `${prev}${block}`;\n return prev.endsWith('\\n') ? `${prev}\\n${block}` : `${prev}\\n\\n${block}`;\n}\n\n/** `1.0.0 → 1.1.0`: point an existing `.noir/config.yml` at `.noir/.env`.\n *\n * Scoped to the one transformation `skipIfExists` structurally cannot perform.\n * `config.yml` is a user-owned seed written once at init, so the manifest\n * never opens an existing one: a project initialized before this migration\n * existed would therefore never learn where secrets belong. Everything else\n * this version adds is *creation* (a `skipIfExists` backfill) or *managed\n * blocks* (`managedBlock` re-emission); the migration duplicates neither. */\nconst envPointer: MigrationScript = {\n from: '1.0.0',\n to: '1.1.0',\n description: 'point .noir/config.yml at .noir/.env (the secrets home)',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const rel = '.noir/config.yml';\n const abs = join(ctx.root, '.noir', 'config.yml');\n\n // Absent: nothing to transform, and creating it is NOT this migration's\n // job: an absent `config.yml` is backfilled from the template (which\n // already carries the pointer) by the `skipIfExists` emit phase.\n if (!existsSync(abs)) {\n result.notes.push(`${rel}: absent: the template seed carries the pointer`);\n return result;\n }\n\n let prev: string;\n try {\n prev = readFileSync(abs, 'utf8');\n } catch (err) {\n // Unreadable (a directory, a permission wall). Non-throwing is the\n // registry-wide contract: record and let the caller decide.\n result.conflicts.push(rel);\n result.notes.push(`${rel}: unreadable (${errorMessage(err)}): left untouched`);\n return result;\n }\n\n // The idempotency guard: a second run (or a file the template already\n // stamped) sees the marker and writes nothing at all: byte-level no-op.\n if (prev.includes(ENV_POINTER_MARKER)) {\n result.notes.push(`${rel}: already carries ${ENV_POINTER_MARKER}`);\n return result;\n }\n\n if (ctx.dryRun) {\n result.changed.push(rel);\n result.notes.push(`${rel}: would append the .noir/.env pointer`);\n return result;\n }\n\n try {\n writeFileSync(abs, appendSeparated(prev, ENV_POINTER_BLOCK), 'utf8');\n } catch (err) {\n result.conflicts.push(rel);\n result.notes.push(`${rel}: write failed (${errorMessage(err)}): left untouched`);\n return result;\n }\n result.changed.push(rel);\n result.notes.push(`${rel}: appended the .noir/.env pointer`);\n return result;\n },\n};\n\n// --- 1.1.0 → 1.2.0: refresh the doc-only seeds ------------------------------\n\n/** The doc-only seeds this migration walks. Each maps a repo-relative path to\n * the seed kind it is recorded under and the template that renders its current\n * bytes. Only these two are tracked: they are written once at init and never\n * opened again by the manifest. `.env.example` changed in this release, so an\n * unedited older copy must be brought forward. RULES.md is carried in the same\n * loop although its text did NOT change here: its current render equals the\n * recorded bytes, so the refresh decision leaves it alone today, and a future\n * release that does change it is already handled. It is skipped entirely for a\n * project that switched the working rules off; see {@link envTemplates}. */\nconst DOC_SEEDS: ReadonlyArray<{ rel: string; kind: SeedKind; template: string }> = [\n { rel: '.noir/.env.example', kind: 'envExample', template: 'env.example.tmpl' },\n { rel: '.noir/rules/RULES.md', kind: 'rulesSeed', template: 'rules-seed.md.tmpl' },\n];\n\n/** `1.1.0 → 1.2.0`: bring unedited doc seeds up to the text this build ships.\n *\n * The decision is delegated to {@link isStaleSeed}, the same evidence the\n * manifest uses: a file is refreshed only when its bytes are an exact match for\n * a recorded past seed AND differ from the current render. A user-edited file\n * (or one already current) is left alone. The write goes through\n * {@link refreshSeed}, which overwrites the bytes while keeping the permission\n * bits the file already has.\n *\n * The working-rules seed is skipped outright when the project switched it off.\n * That switch says this project has no working rules for Noir to maintain, and\n * the refresh's evidence: \"these bytes are a seed Noir shipped, so nobody\n * edited them\": is exactly the case the switch must not act on: it would\n * rewrite the rules text of a project that has opted out of having any, on the\n * strength of a comparison the user never asked for. `.env.example` is a\n * different file with a different owner (pure documentation, no switch), so it\n * refreshes either way. */\nconst envTemplates: MigrationScript = {\n from: '1.1.0',\n to: '1.2.0',\n description: 'refresh the .env.example doc seed and keep RULES.md in step',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n for (const seed of DOC_SEEDS) {\n if (seed.kind === 'rulesSeed' && ctx.rulesEnabled === false) {\n result.notes.push(`${seed.rel}: skipped: the working-rules switch is off`);\n continue;\n }\n const abs = join(ctx.root, seed.rel);\n\n // Absent: nothing to refresh, and creating it is NOT this migration's\n // job: the upgrade's `skipIfExists` emit phase seeds it from the\n // template, which already carries the current text.\n if (!existsSync(abs)) {\n result.notes.push(`${seed.rel}: absent: nothing to refresh`);\n continue;\n }\n\n let prev: string;\n try {\n prev = readFileSync(abs, 'utf8');\n } catch (err) {\n // Unreadable (a directory, a permission wall). Non-throwing is the\n // registry-wide contract: record and let the caller decide.\n result.conflicts.push(seed.rel);\n result.notes.push(`${seed.rel}: unreadable (${errorMessage(err)}): left untouched`);\n continue;\n }\n\n const current = render(loadTemplate(seed.template), {});\n if (!isStaleSeed(seed.kind, prev, current)) {\n result.notes.push(`${seed.rel}: user-owned or already current: left untouched`);\n continue;\n }\n\n if (ctx.dryRun) {\n result.changed.push(seed.rel);\n result.notes.push(`${seed.rel}: would refresh to the current seed`);\n continue;\n }\n\n try {\n refreshSeed(abs, current);\n } catch (err) {\n result.conflicts.push(seed.rel);\n result.notes.push(`${seed.rel}: refresh failed (${errorMessage(err)}): left untouched`);\n continue;\n }\n result.changed.push(seed.rel);\n result.notes.push(`${seed.rel}: refreshed to the current seed`);\n }\n return result;\n },\n};\n\n// --- 1.2.0 → 1.3.0: put a joined repo's MCP entry back on the bridge -------\n\n/** Where this repo's host keeps its MCP config, repo-relative and POSIX, or\n * `null` when the file is not there. The host is read from `.noir/config.yml`\n * exactly as the scaffold reads it; an absent or unreadable config means the\n * default host (`claude`, whose config is the root `.mcp.json`). */\nfunction mcpConfigPath(root: string): { rel: string; abs: string } | null {\n let host: HostId = 'claude';\n try {\n host = loadProjectInfo(root).config.host;\n } catch {\n // No readable project id/config: fall back to the default host rather than\n // failing the migration. Its config path is the one the older join flow used.\n }\n const abs = mcpConfigPathFor(host, root);\n if (!existsSync(abs)) return null;\n return { rel: relative(root, abs).split(sep).join('/'), abs };\n}\n\n/** `1.2.0 → 1.3.0`: rewrite the stale `http://…/mcp?p=<projectId>` pointer that\n * a repo which joined a workspace under the older flow still carries.\n *\n * That entry named a daemon by address, which cannot survive a restart: the\n * workspace daemon binds an ephemeral port and mints a fresh token every start,\n * while the config was written once, at join time. The current flow writes a\n * stdio entry naming the workspace instead, so the host reaches the daemon\n * through the bridge, which resolves the address and reads the token itself.\n *\n * No other command can repair this. `sync` re-emits the entry from the manifest\n * (so a re-scaffold keeps a joined repo joined) but leaves a file that differs\n * from the template alone unless it is forced, and the project's own\n * `.mcp.json` is not otherwise opened. The upgrade path is where a repair that\n * no emit can express belongs.\n *\n * Two conditions gate the rewrite, and both matter. The repo must carry the\n * workspace marker: a repo that deliberately chose the http transport has an\n * http entry too, and rewriting it would be wrong. And the entry must not\n * already be on the bridge: a joined repo whose entry names the workspace needs\n * nothing, which is what makes a second run a byte-level no-op.\n *\n * \"Not on the bridge\" is deliberately wider than \"is the http pointer\". The\n * same downgrade appears in a second shape: a joined repo whose `noir` entry is\n * the plain repo-scoped stdio form (`command`/`args` with no `--workspace`),\n * which is what a re-scaffold under an earlier release rewrote a joined repo's\n * entry to: the marker and the config then disagree about the same repo. A\n * marker is proof of membership, so an entry that does not name the workspace\n * is stale whichever form it is in, and both are repaired here. An entry in\n * neither shape (no `command`, no `type: 'http'`) is left alone: it is not a\n * transport this migration owns. */\nconst workspaceBridge: MigrationScript = {\n from: '1.2.0',\n to: '1.3.0',\n description: \"point a joined repo's MCP entry at the workspace bridge\",\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const name = readWorkspaceMarker(ctx.root);\n if (name === null) {\n result.notes.push('not joined to a workspace: nothing to rewrite');\n return result;\n }\n const target = mcpConfigPath(ctx.root);\n if (target === null) {\n result.notes.push(`workspace \"${name}\": no MCP config file to migrate`);\n return result;\n }\n\n let config: Record<string, unknown>;\n try {\n config = JSON.parse(readFileSync(target.abs, 'utf8')) as Record<string, unknown>;\n } catch (err) {\n // Unparseable (JSONC, a trailing comma, a directory). Non-throwing is the\n // registry-wide contract: record and let the caller decide.\n result.conflicts.push(target.rel);\n result.notes.push(`${target.rel}: unreadable (${errorMessage(err)}): left untouched`);\n return result;\n }\n\n const servers = config.mcpServers;\n if (typeof servers !== 'object' || servers === null || Array.isArray(servers)) {\n result.notes.push(`${target.rel}: no mcpServers block: left untouched`);\n return result;\n }\n const entry = (servers as Record<string, unknown>).noir;\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n result.notes.push(`${target.rel}: no noir entry: left untouched`);\n return result;\n }\n // Which stale shape is this entry in? An http pointer names a daemon by\n // address. A stdio entry that does NOT pass `--workspace` is the repo-scoped\n // form: the downgrade a re-scaffold used to apply to a joined repo. An\n // entry that already names the workspace is the bridge entry a fresh join\n // writes, so there is nothing to repair (and a second run stays a no-op).\n const entryObj = entry as Record<string, unknown>;\n const args = entryObj.args;\n const namesWorkspace = Array.isArray(args) && args.includes('--workspace');\n const isHttpPointer = entryObj.type === 'http';\n const isRepoScopedStdio = typeof entryObj.command === 'string' && !namesWorkspace;\n if (!isHttpPointer && !isRepoScopedStdio) {\n result.notes.push(\n namesWorkspace\n ? `${target.rel}: already reaches Noir over the bridge: left untouched`\n : `${target.rel}: noir entry is not a transport this migration owns: left untouched`,\n );\n return result;\n }\n const kept = Object.entries(entryObj).filter(([key]) => !TRANSPORT_KEYS.has(key));\n\n if (ctx.dryRun) {\n result.changed.push(target.rel);\n result.notes.push(`${target.rel}: would point the noir entry at workspace \"${name}\"`);\n return result;\n }\n\n const next = {\n ...config,\n mcpServers: {\n ...(servers as Record<string, unknown>),\n noir: {\n command: resolveNoirCommand(),\n // The argv comes from the adapters' own helper, so this rewrite lands\n // on exactly the entry a fresh join writes: the name is the whole\n // address, and the bridge resolves the daemon (and reads its token)\n // itself, so none of that is written into a committed config file.\n args: noirStdioArgs(name),\n ...Object.fromEntries(kept),\n },\n },\n };\n try {\n atomicWriteFile(target.abs, `${JSON.stringify(next, null, 2)}\\n`);\n } catch (err) {\n result.conflicts.push(target.rel);\n result.notes.push(`${target.rel}: write failed (${errorMessage(err)}): left untouched`);\n return result;\n }\n result.changed.push(target.rel);\n result.notes.push(`${target.rel}: noir entry now reaches workspace \"${name}\" over the bridge`);\n return result;\n },\n};\n\n// --- 1.3.0 → 1.4.0: route the SessionStart hook through `noir hook` ---------\n\n/** The old hook command this migration rewrites. The `.mjs` runner was emitted\n * directly-executable and named in the command, so its filename is the reliable\n * marker of \"this entry is the old Noir hook\". */\nconst OLD_HOOK_MARKER = 'noir-session-start';\n\n/** `1.3.0 → 1.4.0`: rewrite the SessionStart + SubagentStart hook command from\n * the directly-executed `.noir/hooks/noir-session-start.mjs` runner to\n * `\"<noir>\" hook`, and remove the now-orphaned runner file.\n *\n * The `.mjs` runner depended on the file's exec bit and on `node` being on the\n * spawn PATH; `noir hook` routes through the shim, which resolves the managed\n * Node by absolute path and is kept executable by the installer. The command\n * lives in `.claude/settings.local.json`, which the manifest deliberately never\n * re-emits (the user may have removed the hook), so only the upgrade path can\n * repair an existing project's entry: which is what this migration is for.\n *\n * Idempotent: a command already routed through ` hook` is left alone, and the\n * runner-file removal is `force: true`, so a second run is a byte-level no-op.\n */\nconst hookCommand: MigrationScript = {\n from: '1.3.0',\n to: '1.4.0',\n description: 'route the SessionStart hook through `noir hook` (drop the .mjs runner)',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const settingsRel = '.claude/settings.local.json';\n const settingsPath = join(ctx.root, '.claude', 'settings.local.json');\n\n // 1. Rewrite the hook command(s) in settings.local.json, when present.\n if (existsSync(settingsPath)) {\n let settings: Record<string, unknown>;\n try {\n settings = JSON.parse(readFileSync(settingsPath, 'utf8')) as Record<string, unknown>;\n } catch (err) {\n result.conflicts.push(settingsRel);\n result.notes.push(\n `${settingsRel}: unreadable (${errorMessage(err)}): command left untouched`,\n );\n settings = {}; // still remove the orphaned runner below\n }\n\n let rewritten = false;\n const hooks = settings.hooks;\n if (typeof hooks === 'object' && hooks !== null && !Array.isArray(hooks)) {\n for (const eventEntries of Object.values(hooks as Record<string, unknown>)) {\n if (!Array.isArray(eventEntries)) continue;\n for (const group of eventEntries) {\n if (typeof group !== 'object' || group === null) continue;\n const inner = (group as Record<string, unknown>).hooks;\n if (!Array.isArray(inner)) continue;\n for (const entry of inner) {\n if (typeof entry !== 'object' || entry === null) continue;\n const hookObj = entry as Record<string, unknown>;\n if (\n typeof hookObj.command === 'string' &&\n hookObj.command.includes(OLD_HOOK_MARKER)\n ) {\n hookObj.command = `\"${resolveNoirCommand()}\" hook`;\n rewritten = true;\n }\n }\n }\n }\n }\n\n if (!rewritten) {\n result.notes.push(`${settingsRel}: no .mjs hook command to migrate`);\n } else if (ctx.dryRun) {\n result.changed.push(settingsRel);\n result.notes.push(`${settingsRel}: would route the hook through \\`noir hook\\``);\n } else {\n try {\n atomicWriteFile(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`);\n result.changed.push(settingsRel);\n result.notes.push(`${settingsRel}: hook now routes through \\`noir hook\\``);\n } catch (err) {\n result.conflicts.push(settingsRel);\n result.notes.push(`${settingsRel}: write failed (${errorMessage(err)}): left untouched`);\n }\n }\n } else {\n result.notes.push(`${settingsRel}: absent: nothing to migrate`);\n }\n\n // 2. Remove the orphaned runner. Its command no longer points at it, so the\n // file is dead weight in a project that had the old hook emitted.\n const runnerRel = '.noir/hooks/noir-session-start.mjs';\n const runnerPath = join(ctx.root, '.noir', 'hooks', 'noir-session-start.mjs');\n if (!existsSync(runnerPath)) {\n return result;\n }\n if (ctx.dryRun) {\n result.changed.push(runnerRel);\n result.notes.push(`${runnerRel}: would remove the orphaned runner`);\n return result;\n }\n try {\n rmSync(runnerPath, { force: true });\n } catch (err) {\n result.conflicts.push(runnerRel);\n result.notes.push(`${runnerRel}: remove failed (${errorMessage(err)})`);\n return result;\n }\n result.changed.push(runnerRel);\n result.notes.push(`${runnerRel}: removed (orphaned runner)`);\n // Drop the now-empty hooks directory so no stale `.noir/hooks/` lingers.\n // Best-effort: a non-empty or already-gone directory is fine either way.\n const hooksDir = join(ctx.root, '.noir', 'hooks');\n try {\n if (readdirSync(hooksDir).length === 0) rmdirSync(hooksDir);\n } catch {\n // best-effort cleanup\n }\n return result;\n },\n};\n\n// --- 1.4.0 → 1.5.0: append the enforcement hook events ----------------------\n\n/** A hook event the migration appends to an existing host hook file, shaped for\n * that host's config (claude `{hooks:[{type,command}]}`, gemini adds a\n * `matcher` for the BeforeTool deny event). */\ntype HookEventPatch = Array<Record<string, unknown>>;\n\n/** Append a set of hook events to a host's hook config file. Idempotent: an\n * event key already present is left alone. `createIfMissing` controls whether\n * an absent file (or a file with no `hooks` object) is created: true for a\n * host whose hook is brand new (gemini), false for a host where an absent file\n * means the user removed the hook and it must not be resurrected (claude). */\nfunction applyHookEvents(\n ctx: MigrationContext,\n rel: string,\n abs: string,\n events: Record<string, HookEventPatch>,\n createIfMissing: boolean,\n): { note: string; changed: boolean; conflict: boolean } {\n let settings: Record<string, unknown>;\n if (!existsSync(abs)) {\n if (!createIfMissing) {\n return { note: `${rel}: absent: nothing to migrate`, changed: false, conflict: false };\n }\n settings = {};\n } else {\n try {\n settings = JSON.parse(readFileSync(abs, 'utf8')) as Record<string, unknown>;\n } catch (err) {\n return {\n note: `${rel}: unreadable (${errorMessage(err)}): left untouched`,\n changed: false,\n conflict: true,\n };\n }\n }\n\n const existing = settings.hooks;\n if (typeof existing !== 'object' || existing === null || Array.isArray(existing)) {\n if (!createIfMissing) {\n return {\n note: `${rel}: no hooks object: nothing to migrate`,\n changed: false,\n conflict: false,\n };\n }\n settings.hooks = {};\n }\n const hooksObj = settings.hooks as Record<string, unknown>;\n\n let added = false;\n for (const [event, patch] of Object.entries(events)) {\n if (hooksObj[event] === undefined) {\n hooksObj[event] = patch;\n added = true;\n }\n }\n if (!added) {\n return { note: `${rel}: enforcement events already present`, changed: false, conflict: false };\n }\n if (ctx.dryRun) {\n return { note: `${rel}: would append enforcement events`, changed: true, conflict: false };\n }\n try {\n atomicWriteFile(abs, `${JSON.stringify(settings, null, 2)}\\n`);\n return { note: `${rel}: appended enforcement events`, changed: true, conflict: false };\n } catch (err) {\n return {\n note: `${rel}: write failed (${errorMessage(err)}): left untouched`,\n changed: false,\n conflict: true,\n };\n }\n}\n\n/** `1.4.0 → 1.5.0`: append the enforcement events to an existing claude or\n * gemini hook. The hook entry is write-once: the manifest never re-emits it\n * after init: so an install created before enforcement shipped has no (or a\n * partial) hook. claude gains PreToolUse + Stop; gemini gains SessionStart +\n * BeforeTool. cursor is a regenerate-mode file, so sync re-emits it with no\n * migration. Idempotent: an event key already present is left alone. */\nconst hookEvents: MigrationScript = {\n from: '1.4.0',\n to: '1.5.0',\n description: 'append the enforcement hook events to the claude or gemini hook',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const host = readProjectConfig(ctx.root)?.host ?? 'claude';\n\n if (host !== 'claude' && host !== 'gemini') {\n result.notes.push(\n `${host}: no hook migration needed (cursor is regenerate-mode; opencode/agents-md have no hook surface)`,\n );\n return result;\n }\n\n const rel = host === 'gemini' ? '.gemini/settings.json' : '.claude/settings.local.json';\n const abs = join(ctx.root, ...rel.split('/'));\n const command =\n host === 'gemini'\n ? `\"${resolveNoirCommand()}\" hook --host gemini`\n : `\"${resolveNoirCommand()}\" hook`;\n const events: Record<string, HookEventPatch> =\n host === 'gemini'\n ? {\n SessionStart: [{ hooks: [{ type: 'command', command }] }],\n BeforeTool: [{ matcher: '.*', hooks: [{ type: 'command', command }] }],\n }\n : {\n PreToolUse: [{ hooks: [{ type: 'command', command }] }],\n Stop: [{ hooks: [{ type: 'command', command }] }],\n };\n\n const out = applyHookEvents(ctx, rel, abs, events, host === 'gemini');\n if (out.conflict) result.conflicts.push(rel);\n else if (out.changed) result.changed.push(rel);\n result.notes.push(out.note);\n return result;\n },\n};\n\n/** The registry. The runner sorts the selected window by `to`, so declaration\n * order is documentation only: oldest step first. */\nexport const MIGRATIONS: readonly MigrationScript[] = [\n synthetic,\n envPointer,\n envTemplates,\n workspaceBridge,\n hookCommand,\n hookEvents,\n];\n/** `Error.message` for anything thrown, without assuming an Error. */\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n// --- conflict-marker helpers (exported for migration authors) ---------------\n\n/** Write git-style inline conflict markers around `theirs`/`ours` so a human\n * or AI agent can resolve later. This is the CI-safe fallback —\n * no interactive prompts, ever. */\nexport function applyInlineConflict(\n ours: string,\n theirs: string,\n oursLabel = 'ours',\n theirsLabel = 'theirs',\n): string {\n return `<<<<<<< ${oursLabel}\\n${ours}=======\\n${theirs}>>>>>>> ${theirsLabel}\\n`;\n}\n\n/** Apply `(ours, theirs)` to a region: if they're equal, return `ours` (no\n * conflict); otherwise emit inline markers. Migration authors should prefer\n * this over {@link applyInlineConflict} when the \"no change needed\" case is\n * common: it keeps re-runs truly idempotent (no spurious markers on a clean\n * tree). */\nexport function applyWithConflict(\n ours: string,\n theirs: string,\n path: string,\n): {\n text: string;\n conflicted: boolean;\n} {\n if (ours === theirs) return { text: ours, conflicted: false };\n return {\n text: applyInlineConflict(ours, theirs, path, path),\n conflicted: true,\n };\n}\n","// noir-hygiene: exempt\n// The template literals below record historical seed-template bytes verbatim;\n// their banner-looking `# ---` lines are load-bearing bytes, not comments, and\n// must stay byte-identical to what users already have on disk.\n/**\n * Template history — the evidence needed to decide whether a shipped seed\n * file may be refreshed during an upgrade.\n *\n * WHY THIS EXISTS\n * `noir init` writes two files whose only job is to be read by a human:\n * `.noir/.env.example` (documents the variable set) and `.noir/rules/RULES.md`\n * (the AI working-contract seed). Both are emitted create-only-if-absent, and\n * that is normally the right contract: once a file exists, the user owns it and\n * Noir must never overwrite their edits.\n *\n * It has one bad consequence. When a later release adds a variable to\n * `.noir/.env.example`, an already-initialized project keeps the old text\n * forever — the upgrade path cannot tell \"the user rewrote this file\" apart\n * from \"the user never touched it, and the shipped text has moved on\". Those\n * two need opposite handling: leave the first alone, refresh the second. A\n * plain-content check cannot distinguish them, and nothing on disk records the\n * bytes that were originally written.\n *\n * This module supplies the missing evidence. It holds the exact bytes of the\n * seed templates as they shipped at each past scaffold version, so a caller can\n * ask: \"are the bytes on disk identical to some version Noir once wrote, and\n * different from what Noir writes today?\" If yes, the file is an unedited\n * leftover and refreshing it loses nothing.\n *\n * THE CONTRACT\n * {@link isStaleSeed} answers exactly that question. It is deliberately\n * conservative in one direction: it returns `true` only on an exact match\n * against a recorded past version, so ANY other byte difference — a user edit,\n * a re-wrapped line, even a CRLF line-ending conversion from a Windows\n * checkout — reads as \"user-owned\" and the file is left untouched. A missed\n * refresh is a documentation gap; a wrong refresh destroys user work. The\n * asymmetry is intentional and should not be \"fixed\" into a fuzzy match.\n *\n * Each entry holds the text of BOTH seeds, so the comparison takes the seed it\n * is about as an argument and scans only that field. Scanning all of them would\n * let a `.env.example` match the recorded working-rules text (or the reverse),\n * and since the caller's response to `true` is an overwrite, a mixed-up pairing\n * would discard an edit the user is entitled to keep. The caller knows which\n * file it just read; the argument makes it say so.\n *\n * RENDERING\n * Seed templates are normally interpolated through `render(template, vars)`\n * before writing. That step is a no-op for the two templates recorded here:\n * neither contains a `{{...}}` token, so `render(text, vars) === text` for\n * every possible `vars`. The registry therefore stores raw template text and\n * {@link isStaleSeed} compares it against raw file bytes — no `vars` argument\n * is needed, and callers do not have to reconstruct the context that wrote the\n * file years ago.\n *\n * That equivalence is load-bearing, so it is asserted rather than merely\n * documented: `template-history.test.ts` fails if any recorded template\n * contains a `{{` token. A future seed template that DOES interpolate needs\n * `render` applied to the history entry (and a `vars` parameter on the\n * comparison) before it is added here — the test exists to make that\n * requirement impossible to overlook.\n *\n * APPENDING TO THE HISTORY\n * Record the template bytes as they shipped, keyed by the scaffold version\n * whose `init` wrote them, and never edit an existing entry — a user's disk\n * still holds those old bytes, and rewriting history would silently reclassify\n * their file as \"current\" (no refresh) or \"unknown\" (no refresh). The same\n * reasoning that keeps npm versions immutable applies here.\n */\n\n/**\n * Which recorded seed a comparison is about. The values mirror the field names\n * of {@link SeedTemplateHistoryEntry}: every entry carries the text of both\n * seeds, and a caller must name the one whose bytes it read (see\n * {@link isStaleSeed} on why the pairing is load-bearing).\n */\nexport type SeedKind = 'envExample' | 'rulesSeed';\n\n/**\n * The seed templates as they shipped at one past scaffold version.\n *\n * Only the doc-only seeds are tracked. Files the user is expected to edit\n * heavily (`.noir/config.yml`, `.noir/project.id`) are not candidates for\n * refresh at all, and co-owned files that carry a managed region already have a\n * three-way merge path of their own.\n */\nexport interface SeedTemplateHistoryEntry {\n /**\n * Bare `x.y.z` scaffold version whose `init` wrote these bytes — the value\n * stamped into `.noir/scaffold-version` at the time, with no `v` prefix and\n * no pre-release suffix.\n *\n * Spelled out literally rather than read from `CURRENT_SCAFFOLD_VERSION`:\n * the version recorded here is a historical fact about these bytes, so it\n * must not advance when the current version does.\n */\n readonly scaffoldVersion: string;\n /** Raw text of the `env.example` template at this version (see the module\n * header on why raw text equals the rendered file for this template). */\n readonly envExample: string;\n /** Raw text of the `rules-seed.md` template at this version. */\n readonly rulesSeed: string;\n}\n\n/**\n * `.noir/.env.example` as shipped by scaffold version 1.1.0 — the first\n * version to emit it. Captured verbatim from the packaged template so the\n * bytes are byte-identical to what landed in users' repositories.\n *\n * `\\${` and `` \\` `` appear below because the text itself contains those\n * characters; the escape sequences decode back to the original bytes, which\n * `template-history.test.ts` verifies against a recorded digest.\n */\nconst ENV_EXAMPLE_1_1_0 = `# .noir/.env.example — committable documentation of the format.\n#\n# This file is a TEMPLATE and is never loaded. Noir reads \\`.noir/.env\\`, which\n# \\`noir init\\` already created for you at mode 0600 — edit that file and\n# uncomment the entries you need. Keep this example in git so the variable set\n# stays discoverable.\n#\n# ---------------------------------------------------------------------------\n# The block below is the same header \\`.noir/.env\\` carries (spec 12.1).\n# ---------------------------------------------------------------------------\n#\n# .noir/.env — project-scoped configuration for this repo.\n#\n# This file is the recommended home for project-scoped configuration and\n# secrets. Precedence:\n# 1. run profile env run.profiles.<n>.env (per-invocation; merges OVER)\n# 2. .noir/.env <- recommended home for project-scoped configuration\n# 3. real environment CI / container / launchd / shell rc\n# 4. built-in default\n#\n# A real environment variable is a FALLBACK: it applies only to keys this file\n# leaves unset. A machine-global export therefore cannot shadow a value here.\n# Run \\`noir env\\` to see which source is winning for each key.\n#\n# Keep this file private: chmod 600 .noir/.env\n# It is gitignored by Noir's managed .gitignore block, and Noir REFUSES to load\n# it if it is tracked by git (a cloned repo could redirect credentials).\n# Never commit \\`.noir/.env\\`. (THIS file is the committable one.)\n\n# --- Integrations -----------------------------------------------------------\n# ClickUp — only if the noir-clickup integration is enabled.\n# CLICKUP_API_TOKEN=pk_00000000000000000000000000000000\n#\n# CLICKUP_TEAM_ID is NOT an env var — Noir never reads it. Workspace binding\n# (team/list/space ids) is config.yml instead:\n# integrations:\n# clickup:\n# teamId: \"ABC123\" # optional — only for custom task IDs (#ABC-123)\n\n# --- Remote embedders (context.embedder.kind: remote) -----------------------\n# The default local embedder (kind: local) needs no key.\n# OPENAI_API_KEY=sk-000000000000000000000000000000000000000000000000\n# VOYAGE_API_KEY=pa-0000000000000000000000000000000000000000000000\n# COHERE_API_KEY=0000000000000000000000000000000000000000\n#\n# Ollama (kind: ollama) reads its base URL from config.yml\n# (context.embedder.baseURL); this variable is the fallback:\n# OLLAMA_BASE_URL=http://localhost:11434\n\n# --- Model provider key -----------------------------------------------------\n# There is NO fixed \"model key\" variable, and setting one is not enough on its\n# own. \\`apiKeyEnv\\` is a NAME, not an interpolation: the config stores the\n# variable NAME and Noir reads process.env[<that name>] at call time. Write the\n# bare name — never \\`\\${...}\\` — in .noir/config.yml:\n#\n# model:\n# providers:\n# anthropic:\n# apiKeyEnv: ANTHROPIC_API_KEY # -> reads $ANTHROPIC_API_KEY\n# tiers:\n# default:\n# provider: anthropic\n#\n# Only \\`run.profiles.<name>.env\\` interpolates \\`\\${VAR}\\`. With no provider\n# configured the model layer degrades to templates — Noir never makes a silent\n# paid call. \\`ANTHROPIC_API_KEY\\` is the conventional example, not a variable\n# Noir looks up on its own:\n# ANTHROPIC_API_KEY=sk-ant-0000000000000000000000000000000000000000000000\n\n# --- Run profile selection --------------------------------------------------\n# Selects a \\`run.profiles.<name>\\` entry for \\`noir run\\`.\n# Precedence: --profile flag > NOIR_PROFILE > run.defaultProfile > host default.\n# NOIR_PROFILE=work\n\n# --- Update kill-switches ---------------------------------------------------\n# NOIR_DISABLE_UPDATE_CHECK=1 # suppress the background startup check only\n# NOIR_DISABLE_UPDATES=1 # make \\`noir update\\` refuse (exit 2)\n\n# See docs/reference/environment.md for every variable Noir reads.\n`;\n\n/**\n * `.noir/.env.example` as shipped by scaffold version 1.2.0 — the env-template\n * redesign (gateway section first, a full purpose/precedence/commit guide, and\n * placeholder values that are clearly placeholders rather than fake keys).\n * Captured verbatim from the packaged template so the bytes are byte-identical\n * to what landed in users' repositories.\n */\nconst ENV_EXAMPLE_1_2_0 = `# .noir/.env.example — the committable reference for .noir/.env.\n#\n# What .noir/.env is for\n# ----------------------\n# .noir/.env is this project's configuration-and-secrets file. \\`noir init\\`\n# creates it at mode 0600 and gitignores it, and Noir loads it on every\n# command. For every key it defines, it WINS over your shell environment, so it\n# is the recommended home for project-scoped values and secrets.\n#\n# Precedence (highest wins):\n# 1. run profile env run.profiles.<n>.env (per-invocation; merges over)\n# 2. .noir/.env the project-scoped file this example documents\n# 3. real environment CI / container / launchd / shell rc\n# 4. built-in default\n#\n# A real environment variable is only a FALLBACK for keys .noir/.env leaves\n# unset. For a value scoped to one invocation, add a \\`run.profiles.<name>.env\\`\n# entry and reference it there as \\`\\${VAR}\\`, so the literal value stays out of\n# config.yml; values that must not be committed belong in \\`.noir/.env\\`\n# (gitignored) or the real environment. \\`noir env\\` reports the winning source\n# for each key.\n#\n# What to commit\n# --------------\n# Keep .noir/.env OUT of git: it holds real secrets, Noir's managed .gitignore\n# block excludes it, and Noir refuses to load a copy that IS tracked (a cloned\n# repo could redirect credentials). chmod 600 .noir/.env to keep it private.\n# Never commit the real file — commit THIS example, so the keys stay\n# discoverable and documented.\n#\n# Every line below is documentation. Copy a key to .noir/.env and set a real\n# value to enable it.\n#\n# Full reference: docs/reference/environment.md\n\n# --- Host gateway (Claude Code via Z.AI / LiteLLM / OpenRouter / Kimi ...) ---\n# Point the host at an Anthropic-shaped endpoint instead of api.anthropic.com.\n#\n# ANTHROPIC_BASE_URL — the gateway's base URL. Host ONLY: give the origin (for\n# example https://gateway.example) and NOT the /v1/messages path — the host\n# appends that itself.\n# ANTHROPIC_AUTH_TOKEN — sent as \\`Authorization: Bearer <token>\\`. It is the\n# HEADER variant of a credential: AUTH_TOKEN becomes Bearer, while\n# ANTHROPIC_API_KEY becomes x-api-key. Do not set BOTH — that is an auth\n# conflict.\n# ANTHROPIC_DEFAULT_HAIKU_MODEL / _SONNET_MODEL / _OPUS_MODEL — remap the\n# haiku / sonnet / opus aliases to the model ids your gateway serves.\n# API_TIMEOUT_MS — per-request timeout in milliseconds (Claude Code default is\n# 600000).\n# CLAUDE_CODE_AUTO_COMPACT_WINDOW — auto-compact window (100000 to 1000000).\n#\n# ANTHROPIC_BASE_URL=https://gateway.example\n# ANTHROPIC_AUTH_TOKEN=replace-with-your-gateway-token\n# ANTHROPIC_DEFAULT_HAIKU_MODEL=replace-with-your-gateway-haiku-model\n# ANTHROPIC_DEFAULT_SONNET_MODEL=replace-with-your-gateway-sonnet-model\n# ANTHROPIC_DEFAULT_OPUS_MODEL=replace-with-your-gateway-opus-model\n# API_TIMEOUT_MS=600000\n# CLAUDE_CODE_AUTO_COMPACT_WINDOW=1000000\n\n# --- Integrations ---\n# CLICKUP_API_TOKEN — the token for the noir-clickup integration; set it only\n# when that integration is enabled.\n# CLICKUP_TEAM_ID is NOT an env var. Noir never reads it — workspace binding\n# (team / list / space ids) belongs in .noir/config.yml under\n# integrations.clickup.\n# CLICKUP_API_TOKEN=replace-with-your-clickup-token\n\n# --- Remote embedders (context.embedder.kind: remote) ---\n# The default local embedder (kind: local) needs no key. The three API-key\n# variables below apply only when config.yml selects a remote embedder.\n# OPENAI_API_KEY=replace-with-your-openai-key\n# VOYAGE_API_KEY=replace-with-your-voyage-key\n# COHERE_API_KEY=replace-with-your-cohere-key\n#\n# Ollama (kind: ollama) reads its base URL from config.yml\n# (context.embedder.baseURL); this variable is the fallback:\n# OLLAMA_BASE_URL=http://localhost:11434\n\n# --- Model provider key (read via apiKeyEnv in .noir/config.yml) ---\n# There is no fixed \"model key\" variable, and setting a key is not enough on\n# its own. \\`apiKeyEnv\\` is a NAME, not an interpolation: config.yml stores the\n# variable NAME, and Noir reads process.env[<that name>] at call time. Write\n# the bare name — never \\`\\${...}\\` — in .noir/config.yml:\n#\n# model:\n# providers:\n# anthropic:\n# apiKeyEnv: ANTHROPIC_API_KEY # -> reads $ANTHROPIC_API_KEY\n#\n# Only \\`run.profiles.<name>.env\\` interpolates \\`\\${VAR}\\`. With no provider\n# configured the model layer degrades to templates — Noir never makes a silent\n# paid call. \\`ANTHROPIC_API_KEY\\` is the conventional example, not a variable\n# Noir looks up on its own.\n# ANTHROPIC_API_KEY=replace-with-your-anthropic-key\n\n# --- Run profile selection (noir run) ---\n# Selects a \\`run.profiles.<name>\\` entry for \\`noir run\\`.\n# Precedence: --profile flag > NOIR_PROFILE > run.defaultProfile > host default.\n# NOIR_PROFILE=work\n\n# --- Update kill-switches ---\n# NOIR_DISABLE_UPDATE_CHECK=1 # suppress the background startup check only\n# NOIR_DISABLE_UPDATES=1 # make \\`noir update\\` refuse (exit 2)\n\n# See docs/reference/environment.md for every variable Noir reads.\n`;\n\n/**\n * `.noir/rules/RULES.md` as shipped by scaffold version 1.1.0.\n *\n * Copy this text verbatim when recording a new version's seed: the whole value\n * of the snapshot is byte equality with what users already have on disk.\n */\nconst RULES_SEED_1_1_0 = `# Noir working rules\n\n> Canonical AI working-contract for this project. The host context file (CLAUDE.md) @imports this.\n> Keep LEAN: every line must be failure-backed, tool-enforceable, decision-encoding, or triggerable — else delete it.\n> Edit freely — Noir re-emits only the @import pointer, never this body.\n\n## Identity & scope\n- This project uses **Noir** (discipline/context/memory layer) inside **Claude Code**.\n- Noir is an orchestration layer — the host CLI is the execution engine; Noir is the spec/context/memory brain.\n- Stay in scope: do only what the current Noir task (\\`.noir/tasks/\\`) requires. Surface scope creep BEFORE acting.\n\n## Anti-assumption contract\n- **Never fabricate** facts, APIs, file contents, or command output. Use only what you have read or verified.\n- **Never assume** — if a path, signature, or convention is unclear, STOP and ask before acting.\n- Cite the file/line or command you relied on for any non-obvious claim.\n- Detect non-conventional setups; do not paper over them with assumed conventions.\n\n## Spec-Driven Development workflow\n- Follow the Noir SDD lifecycle: intake → clarify → spec → plan → execute → verify → document.\n- Gates are observable: every gate decision is recorded (approved / forced / skipped). \\`--force\\` requires a reason.\n- Do not edit/run/execute before facts are gathered AND the human confirms understanding.\n\n## Verification (run before claiming done)\n- \\`pnpm build && pnpm lint && pnpm typecheck && pnpm test\\` — all green, with real output. No hedging.\n- Report failures truthfully with the actual output; never claim green without evidence.\n\n## Coding standards & architecture\n- Follow existing patterns; match surrounding code's style, naming, comment density.\n- Architecture decisions live in \\`.noir/decisions/\\` (ADR-style) — read them before cross-cutting changes.\n- See \\`docs/roadmap.md\\` for direction and \\`docs/specs/\\` for design.\n\n## Conventions gotchas (project-specific — fill in)\n- Commits stay local until explicitly pushed.\n- The full test suite runs offline/free (no network in CI).\n`;\n\n/**\n * The recorded seed-template bytes, oldest first.\n *\n * Read-only by intent: this is an append-only ledger of shipped bytes, not\n * mutable configuration. Entries are never edited or removed (see the module\n * header on why rewriting a past entry would silently misclassify a user's\n * file), so a version that changed a seed template appends rather than\n * updates.\n */\nexport const SEED_TEMPLATE_HISTORY: readonly SeedTemplateHistoryEntry[] = [\n {\n scaffoldVersion: '1.1.0',\n envExample: ENV_EXAMPLE_1_1_0,\n rulesSeed: RULES_SEED_1_1_0,\n },\n {\n scaffoldVersion: '1.2.0',\n envExample: ENV_EXAMPLE_1_2_0,\n rulesSeed: RULES_SEED_1_1_0,\n },\n];\n\n/**\n * Does `fileBytes` look like an unedited `seed` left over from an older scaffold\n * version?\n *\n * `true` iff the bytes are an exact match for that seed's text in a recorded\n * history entry AND differ from `currentRender` (what Noir writes for that seed\n * today). Callers pass `currentRender` rather than this module deriving it,\n * because the current template text is the business of the manifest and the\n * template loader — and because the compile-time constant that names the\n * current scaffold version advances independently of the bytes recorded here.\n *\n * `seed` names the file the bytes came from. It is a safety argument, not\n * bookkeeping: because each entry records both seeds, a comparison that scanned\n * every field would report the recorded working-rules bytes as a stale\n * `.env.example`, and the caller's response to `true` is to overwrite the file.\n * Naming the seed keeps the comparison inside its own column, so a mixed-up\n * pairing cannot silently destroy an edit the user is entitled to keep.\n *\n * The `currentRender` guard is checked FIRST: it is what keeps a no-op from\n * being reported as a refresh. A project initialized at the current version\n * already holds the current bytes; those bytes also match their own history\n * entry, so without the second condition every up-to-date project would be told\n * to rewrite its seeds.\n *\n * Callers own the other half of the decision: a file that does not exist has no\n * bytes to compare and is a plain \"create the seed\", not a refresh.\n */\nexport function isStaleSeed(seed: SeedKind, fileBytes: string, currentRender: string): boolean {\n if (fileBytes === currentRender) return false;\n return SEED_TEMPLATE_HISTORY.some((entry) => entry[seed] === fileBytes);\n}\n","import { MIGRATIONS } from './index.js';\nimport type { MigrationContext, MigrationResult, MigrationScript } from './types.js';\n\n/**\n * Migration runner. Given a `from` version present on disk and a target `to`\n * version (usually {@link CURRENT_SCAFFOLD_VERSION}), walks the\n * {@link MIGRATIONS} registry forward in version order, executing each step.\n *\n * Selection rule: a script runs when its `from` is `>= fromArg` (semver-ish\n * string compare is enough at Noir's scale; we don't pull in a semver dep for\n * this) AND its `to` is `<= toArg`. Scripts strictly outside the window are\n * skipped. Within the window, scripts run sorted by `to` ascending so a\n * multi-step upgrade (1.0.0 → 1.1.0 → 1.2.0) composes in order.\n *\n * The runner NEVER throws on a per-script failure: it captures the error,\n * records a synthetic conflict entry (`<path>__error`), and continues so a\n * single broken migration doesn't block the rest of the chain. The orchestrator\n * decides whether non-empty `conflicts` is a hard failure (init --upgrade) or a\n * warning (doctor).\n *\n * Returns the aggregate of every step's `changed`/`conflicts`/`notes`.\n */\nexport function runMigrations(\n root: string,\n from: string | null,\n to: string,\n opts: { dryRun?: boolean; rulesEnabled?: boolean } = {},\n): MigrationResult & { from: string | null; to: string; ran: string[] } {\n const window = pickWindow(from ?? '0.0.0', to, MIGRATIONS);\n const ctx: MigrationContext = {\n root,\n dryRun: opts.dryRun === true,\n rulesEnabled: opts.rulesEnabled,\n };\n const aggregate: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const ran: string[] = [];\n\n for (const script of window) {\n ran.push(`${script.from}→${script.to}`);\n let res: MigrationResult;\n try {\n res = script.run(ctx);\n } catch (err) {\n // Non-fatal at the runner level: record and continue. The caller turns\n // non-empty conflicts into a CI failure with a real exit code.\n const msg = err instanceof Error ? err.message : String(err);\n aggregate.conflicts.push(`<runner>:${script.from}→${script.to} threw: ${msg}`);\n continue;\n }\n aggregate.changed.push(...res.changed);\n aggregate.conflicts.push(...res.conflicts);\n aggregate.notes.push(...res.notes);\n }\n\n return {\n ...aggregate,\n from,\n to,\n ran,\n };\n}\n\n/** Pick the ordered subset of `scripts` forming the chain `[from, to]`. */\nfunction pickWindow(\n from: string,\n to: string,\n scripts: readonly MigrationScript[],\n): MigrationScript[] {\n return scripts\n .filter((s) => compareVer(s.from) >= compareVer(from) && compareVer(s.to) <= compareVer(to))\n .sort((a, b) => compareVer(a.to) - compareVer(b.to));\n}\n\n/** Tiny numeric tuple compare: `'1.10.3'` → `[1,10,3]`, compared element-wise.\n * Pre-release suffixes (e.g. `-beta.1`) are ignored: Noir ships `x.y.z`\n * scaffold versions and the runner only needs a deterministic order. */\nfunction compareVer(v: string): number {\n const parts = v.split('-')[0]?.split('.') ?? [];\n let n = 0;\n for (let i = 0; i < 3; i++) {\n const p = Number(parts[i] ?? '0');\n n = n * 1000 + (Number.isFinite(p) ? p : 0);\n }\n return n;\n}\n","import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport {\n createProjectId,\n loadProjectInfo,\n type ManagedBlock,\n paths,\n readManagedBlock,\n readProjectConfig,\n resolveNoirCommand,\n sha256Hex12,\n} from '@noir-ai/core';\nimport { readAncestors, writeAncestors } from './ancestors.js';\nimport { detectCommands, detectEntryPoints, renderBaseline } from './baseline.js';\nimport {\n type BuildManifestContext,\n buildManifest,\n type HostTag,\n type ManifestEntry,\n REFRESHABLE_SEED_KIND,\n} from './manifest.js';\nimport { mergeThreeWay } from './merge.js';\nimport { runMigrations } from './migrations/index.js';\nimport {\n CURRENT_SCAFFOLD_VERSION,\n readScaffoldVersion,\n writeScaffoldVersion,\n} from './scaffold-version.js';\nimport { detectStack, type StackInfo } from './stack-detect.js';\nimport { render } from './template.js';\nimport { isStaleSeed, type SeedKind } from './template-history.js';\nimport { loadTemplate } from './template-loader.js';\nimport {\n buildRegion,\n type EnvMode,\n ensureOwnerOnly,\n managedBlock,\n managedBlocks,\n mergeJson,\n predictManagedBlock,\n predictManagedBlocks,\n refreshSeed,\n regenerate,\n skipIfExists,\n type WriteMode,\n} from './writers.js';\n\n/**\n * Scaffold orchestrator. One function the cli calls for `noir init`,\n * `noir create`, and `noir sync`; mode selects the manifest subset + how\n * project identity is resolved.\n *\n * High-level flow:\n * 1. Resolve project id (provided > existing > generated; sync requires existing).\n * 2. Detect stack (READ-ONLY; always runs; never throws).\n * 3. If {@link ScaffoldOptions.upgrade}, run migrations from the on-disk\n * scaffold-version → {@link CURRENT_SCAFFOLD_VERSION}.\n * 4. Build the manifest, filter by host + mode.\n * 5. For each entry: mkdir -p, render template/content, dispatch to the\n * matching writer.\n * 6. On `init`/`create`, stamp `.noir/scaffold-version` LAST so a crash leaves\n * an old/absent stamp rather than a misleading fresh one.\n *\n * The orchestrator owns dir creation (writers refuse to so that a missing dir\n * is one attributable failure, not N silent ones).\n */\n\nexport type ScaffoldMode = 'init' | 'create' | 'sync';\n\nexport interface ScaffoldOptions {\n /** Absolute repo root. For `create`, the new dir (created if absent). */\n root: string;\n mode: ScaffoldMode;\n /** Target host. Defaults to `'claude'` (the only shipped host). */\n host?: HostTag;\n /** MCP transport. Defaults to `'stdio'`. */\n transport?: 'stdio' | 'streamable-http';\n /** Required when transport is `streamable-http`. */\n url?: string;\n /** Explicit project id; bypasses generate/read. Mainly for tests + `create`\n * flows that want deterministic ids. */\n projectId?: string;\n /** `noir init --upgrade`: run migrations, then re-emit every manifest mode\n * EXCEPT `mergeJson` (`.claude/settings.local.json` is user-owned and\n * write-once at init/create). `skipIfExists` keeps its\n * create-only-if-absent semantics, so an upgrade backfills seeds added since\n * initialization without ever touching a file the user owns. Only meaningful\n * with mode `'init'`. */\n upgrade?: boolean;\n /** Re-scaffold even when the target is already initialized (bypasses\n * the already-initialized no-op guard). Does NOT bypass `assertSafeRoot`\n * : root-safety is hard, never bypassable. */\n force?: boolean;\n /** Preview: compute the same written/skipped/migrated lists without touching\n * disk. `noir doctor`/CI use this to report drift. */\n dryRun?: boolean;\n /** Drift check: a dry-run that reports only what would CHANGE (byte comparison,\n * not the full planned-emit list). Used by `noir sync --check`. */\n check?: boolean;\n /** Policy for a `regenerate` file that exists and DIFFERS from the\n * template, when no {@link onConflict} callback is provided. Default\n * `'overwrite'` is byte-backward-compatible. `'preserve'` keeps the user's\n * file (the non-TTY / CI default in the cli). */\n conflictPolicy?: 'overwrite' | 'preserve';\n /** Per-file conflict resolver: the UI seam (the engine stays UI-free;\n * the cli injects a @clack-based resolver). Called when a `regenerate`\n * file exists and differs from the template. The return type widens to\n * accept a rich shape carrying `applyToAll`: the engine then reuses the\n * decision for the rest of the run (per artifact CLASS). */\n onConflict?: (ctx: ConflictContext) => Promise<ConflictResolverReturn> | ConflictResolverReturn;\n /** Three-way merge managed regions (base/ours/theirs) using a persisted\n * ancestor snapshot (`.noir/ancestors.json`) instead of strip-replace, so a\n * hand-edit inside a `<!-- noir:* -->` region survives a template update.\n * DEFAULT TRUE: ancestor capture is unconditional, so the first merge\n * run always has a base. Set to `false` (CLI `--no-merge-regions`) to\n * restore strip-replace. Supports BOTH single-region (NOIR.md, ignores) AND\n * multi-region (CLAUDE.md CONTEXT+RULES) managed files: the multi-region\n * path merges managed regions per block (`managedBlocks` +\n * `mergeManagedRegion`). */\n mergeManagedRegions?: boolean;\n /** Explicit interactivity signal. The engine reads THIS (not\n * `process.env`), so a direct API/embedded caller in a TTY that does NOT\n * inject {@link onConflict} never hits a @clack prompt. The CLI sets it from\n * its global flags (via the `NOIR_NON_INTERACTIVE` bridge bin.ts owns);\n * `process.env` remains only a fallback bridge for the CLI layer. */\n interactive?: boolean;\n}\n\nexport interface ScaffoldResult {\n /** Repo-relative paths actually written. */\n written: string[];\n /** Repo-relative paths skipIfExists'd (already present). */\n skipped: string[];\n /** Repo-relative `regenerate` paths skipped because byte-identical to\n * the template (content-hash dedup: no rewrite). */\n identical: string[];\n /** Repo-relative doc-only seed paths REPLACED on the upgrade path because the\n * copy on disk was still byte-identical to an older Noir's shipped text —\n * i.e. the user never edited it, so a newer seed costs nothing. Reported\n * separately from {@link written} because the distinction is the whole point\n * of the refresh: a caller (and the user reading the summary) can tell\n * \"Noir updated documentation you never touched\" apart from \"Noir created\n * something new\".\n *\n * A seed the user DID edit is never listed here: it goes through the\n * ordinary conflict flow, so it appears in {@link written} only when a\n * resolver explicitly chose to replace it (or `--force` did). Empty on every\n * other run: a fresh `init`/`create` has nothing to compare against and\n * `sync` does not emit seeds. */\n refreshed: string[];\n /** True when the already-initialized guard short-circuited (a bare\n * `noir init`/`create` on an initialized project). Callers (init.ts/create.ts)\n * gate skills emission + the \"initialized\" message on this: a no-op must NOT\n * re-emit skills or claim it initialized. */\n noop: boolean;\n /** Migration steps executed (`<from>→<to>`), when upgrade ran. */\n migrationsRan: string[];\n /** Migration conflicts (repo-relative or `<runner>:…`), when upgrade ran. */\n migrationConflicts: string[];\n /** Repo-relative paths a migration CHANGED (wrote or refreshed), when upgrade\n * ran: reported separately from {@link refreshed} so callers can name\n * \"migrated\" files (e.g. `.noir/config.yml`) apart from doc seeds. */\n migrationChanged: string[];\n stack: StackInfo;\n projectId: string;\n fromVersion: string | null;\n toVersion: string;\n /** The host actually emitted (post-default). */\n host: HostTag;\n /** Structured conflict report: one record per file that existed AND\n * differed from the proposed bytes (regenerate conflict path). Populated in\n * `--json`/`--no-input`/non-TTY runs (no prompt fires) so a CI/JSON caller\n * can see exactly which files diverged + how they were resolved, without\n * grepping stderr. Empty when no conflicts occurred (also on first-run +\n * no-op). */\n conflicts: ConflictRecord[];\n /** Permission planned for each file this run CREATED at a non-default mode\n * (repo-relative path → mode), i.e. the {@link ManifestEntry.fileMode} of a\n * `skipIfExists` entry that had no file on disk. A real run reports the mode\n * it applied; a `dryRun` reports the mode it WOULD apply (the predictor's\n * job: the caller can preview `.noir/.env` at 0600 without writing it).\n * Paths the entry skipped (already present) are absent: an existing file's\n * mode is never changed. Optional so existing external constructors of this\n * type (test stubs) stay valid; `scaffold()` always populates it. */\n fileModes?: Record<string, number>;\n /** What the run did to the permissions of `.noir/.env`: the outcome of\n * {@link ensureOwnerOnly}, which is called on every run that may write, in\n * every mode, because the seeded file's 0600 mode is applied only when it is\n * created.\n * `'healed'` means a group/world-readable file was tightened to 0600, so a\n * caller can report the fix instead of leaving the user to notice it in a\n * later diagnostic; `'unchanged'` and `'unsupported'` mean nothing needed\n * doing (or the platform has no POSIX mode bits): the `'unchanged'` a fresh\n * `create` reports, whose seed was written 0600 in the same run. Absent on a\n * dry run, which must not touch disk. Optional so existing external\n * constructors of this type stay valid. */\n envMode?: EnvMode;\n}\n\n/** One entry in {@link ScaffoldResult.conflicts}. */\nexport interface ConflictRecord {\n /** Repo-relative path of the conflicting file. */\n path: string;\n /** Artifact class: drives apply-to-all memory scope + report grouping. */\n mode: ConflictContext['mode'];\n /** LCS similarity (0-1) between existing and proposed. */\n similarity?: number;\n /** sha256 (hex, first 12 chars) of the on-disk bytes at conflict time. */\n existingSha: string;\n /** sha256 (hex, first 12 chars) of the proposed bytes. */\n proposedSha: string;\n /** Resolution the engine applied (`replace`/`preserve`/`rename`/…). */\n resolution: ConflictResolution;\n}\n\n/** Context passed to {@link ScaffoldOptions.onConflict} when a\n * `regenerate` file exists and differs from the template. */\nexport interface ConflictContext {\n /** Repo-relative path of the conflicting file. */\n relPath: string;\n /** The file's current on-disk content. */\n existing: string;\n /** The content the scaffold would write. */\n proposed: string;\n /** Artifact class: drives apply-to-all memory scope (`regenerate` shares\n * one decision across a run; `managedBlock`/`managedBlocks` stay per-file).\n * Defaults to `'regenerate'` for backward compatibility with older resolvers\n * that pre-date the field (the engine always sets it). */\n mode?:\n | 'regenerate'\n | 'managedBlock'\n | 'managedBlocks'\n | 'skipIfExists'\n | 'skill'\n | 'markdown'\n | 'artifact';\n /** LCS similarity (0-1) between existing and proposed. Cheap signal for\n * the resolver to bias toward `replace` when ~1.0 or `preserve` when ~0. */\n similarity?: number;\n /** When the 3-way merge of a managed region hit an overlap, the merged\n * bytes WITH zdiff3 conflict markers (selecting `'merge'` writes this). */\n mergedWithMarkers?: string;\n}\n\n/** How to resolve a `regenerate` conflict. */\nexport type ConflictResolution =\n | 'replace'\n | 'preserve'\n | 'rename'\n | 'duplicate'\n | 'cancel'\n | 'merge';\n\n/** The resolver may return a bare {@link ConflictResolution} (scope\n * `'one'`) OR a rich shape carrying `applyToAll`. The engine unwraps both; when\n * `applyToAll` is true it stores the choice in its per-run memory keyed by\n * artifact CLASS (`regenerate` shares one decision across a run; managedBlock\n * stays per-file regardless: user edits there need individual review). */\nexport type ConflictResolverReturn =\n | ConflictResolution\n | { resolution: ConflictResolution; applyToAll?: boolean };\n\n/** A partial emit: the two contexts that re-emit a SUBSET of the manifest.\n * A fresh `init`/`create` (and `--force`) is neither: it emits every mode, so\n * {@link EMITTED_IN} is only consulted for these. */\ntype EmitContext = 'sync' | 'upgrade';\n\n/** Which emit contexts a write mode participates in. Per-mode (never\n * special-cased by path), so a new manifest entry inherits the right behavior\n * from its mode alone.\n *\n * - `sync` re-emits the **runtime subset**: the always-safe-to-rewrite\n * entries (`regenerate` + `managedBlock`). `skipIfExists` is excluded\n * because sync is not a scaffold event: it refreshes the host pointers, it\n * does not (re)create a project's seeds.\n * - `upgrade` (`noir init --upgrade`) re-emits the runtime subset PLUS\n * `skipIfExists`, which normally creates only when the file is ABSENT: so\n * an upgrade backfills a seed added to the manifest after a project was\n * initialized while remaining unable to touch a file the user owns. The one\n * entry that goes further is a doc-only seed flagged `refreshIfStale`, whose\n * untouched older copy is replaced (see {@link ManifestEntry}).\n * `mergeJson` stays OUT: `.claude/settings.local.json` is user-owned and\n * write-ONCE by its own contract (init/create only), and the merge re-appends\n * the SessionStart hook whenever the dedup substring is gone: i.e. an\n * upgrade emit would resurrect a hook the user deliberately removed. */\nconst EMITTED_IN: Record<WriteMode, { sync: boolean; upgrade: boolean }> = {\n regenerate: { sync: true, upgrade: true },\n managedBlock: { sync: true, upgrade: true },\n skipIfExists: { sync: false, upgrade: true },\n mergeJson: { sync: false, upgrade: false },\n};\n\n/**\n * Refuse to scaffold when `root` is: or is inside: a `.noir/` directory.\n * Running `noir init`/`create`/`sync` while cwd = `.noir/` would\n * otherwise mint a FRESH project id (because `<root>/.noir/project.id` is\n * absent) and build a NESTED second project (`.noir/.noir/`, `.noir/CLAUDE.md`,\n * `.noir/.claude/skills/`, …): the duplicate-`.noir` bug. The walk ascends the\n * ancestor chain only, so a legitimate project root that merely CONTAINS a\n * `.noir/` store is never flagged. String-based: works whether or not `root`\n * exists on disk yet (so `noir create <new-dir>` is still guarded).\n *\n * Hard against literal `.noir` path segments: NOT bypassable by `--force`\n * (which is reserved for the already-initialized no-op). NOTE: the walk is\n * string-based, so a symlink whose TARGET is inside `.noir/` is not resolved\n * (a local self-foot-gun only: the caller controls `root`: not externally\n * exploitable); in practice `--cwd`/positional roots are literal paths.\n */\nexport function assertSafeRoot(root: string): void {\n let cur = root;\n for (let i = 0; i < 64; i++) {\n if (basename(cur) === '.noir') {\n throw new Error(\n `Refusing to scaffold inside a .noir/ directory (${root}). Run \\`noir init\\` from the project root, not from inside .noir/.`,\n );\n }\n const parent = dirname(cur);\n if (parent === cur) break; // reached the filesystem root\n cur = parent;\n }\n}\n\n/** The outcome of re-asserting the owner-only mode on `.noir/.env` for this\n * run, or `undefined` when the run must not touch disk (`dryRun`).\n *\n * EVERY mode re-asserts, `create` included. The seeded file's 0600 is applied\n * by the writer only when the file is CREATED (`skipIfExists` never opens an\n * existing one), so the mode can only be trusted on a run that checks the file\n * as it finds it: and `create` reaches an existing tree too: `--force` (or a\n * plain re-run) over a project initialized earlier is the same `.noir/.env`\n * that `init` would find, and it can already be sitting at 0644. Gating the\n * heal to `init`/`sync` on the grounds that \"create writes its seed 0600 in the\n * same run\" holds only for a FRESH tree. Including `create` costs nothing on\n * that fresh tree: the file is absent or already 0600, so the heal is a no-op\n * : and closes the gap on a forced one.\n *\n * Called from both exits: the already-initialized no-op and the end of the\n * emit: because a bare `noir init` on an existing project re-emits nothing\n * but must still fix a credential file left at a wider mode by an older\n * version. Only one of the two runs per invocation. */\nfunction envOwnerOnly(opts: ScaffoldOptions): EnvMode | undefined {\n if (opts.dryRun === true) return undefined;\n // Through the layout helper, like every other `.noir/` path: the directory\n // name lives in one place, so a rename cannot leave this file behind.\n return ensureOwnerOnly(join(paths.noirDir(opts.root), '.env'));\n}\n\n/** The configured host from an existing `.noir/config.yml`, or `'claude'`.\n * A bare re-scaffold (`create --force`) must keep the host the project already\n * chose; an absent or unreadable config degrades to the default. Mirrors\n * init.ts's resolveInitHost. */\nfunction readConfiguredHost(root: string): HostTag {\n if (!existsSync(paths.config(root))) return 'claude';\n try {\n return loadProjectInfo(root).config.host;\n } catch {\n return 'claude';\n }\n}\n\n/** The `rules.enabled` switch from an existing `.noir/config.yml`, or `true`.\n * A project with no config yet (a fresh `init`/`create`) has not opted out of\n * anything, and an absent, unreadable or invalid config degrades to the schema\n * default: so the working-rules seed keeps being emitted exactly as it always\n * was.\n *\n * Reads the config directly rather than through {@link loadProjectInfo}, which\n * also demands a canonical project id: the first `init` over a config the user\n * wrote by hand is exactly the state in which the switch has to be honoured,\n * and it has no id yet. */\nfunction readConfiguredRulesEnabled(root: string): boolean {\n return readProjectConfig(root)?.rules.enabled ?? true;\n}\n\nexport async function scaffold(opts: ScaffoldOptions): Promise<ScaffoldResult> {\n // Root-safety: refuse to scaffold at/inside a .noir/ directory BEFORE\n // any write (incl. `create`'s target mkdir). Prevents the nested .noir/.noir/\n // re-init bug. Hard guard; not bypassable.\n assertSafeRoot(opts.root);\n\n // The host a bare re-scaffold emits for. `init` resolves the host itself and\n // always passes it; `create` may omit it, so a `--force`/re-run over an\n // existing project keeps the host that project already configured rather than\n // falling back to the default and emitting artifacts for the wrong host.\n // Mirrors init.ts's resolveInitHost: explicit > configured > 'claude'.\n const host: HostTag = opts.host ?? readConfiguredHost(opts.root);\n const transport: BuildManifestContext['transport'] = opts.transport ?? 'stdio';\n if (transport === 'streamable-http' && !opts.url) {\n throw new Error(\"transport 'streamable-http' requires opts.url\");\n }\n\n // 1. Root for `create` may not exist yet: mirror `init.ts`'s mkdir of .noir/.\n if (opts.mode === 'create' && !opts.dryRun) {\n mkdirSync(opts.root, { recursive: true });\n }\n\n // 2. Resolve project id. Read the on-disk stamp ONCE and reuse the result\n // for the corrupt-file heal below. sync requires a VALID existing id.\n const idFile = readProjectIdFile(opts.root);\n const projectId = resolveProjectId(opts, idFile);\n\n // A `project.id` that EXISTS but is empty/unparseable is CORRUPT, not\n // absent. The manifest writes project.id via `skipIfExists`, which would\n // preserve the empty file while NOIR.md's BRIEF_BLOCK renders the freshly\n // resolved/generated id → silent identity split (NOIR.md states an id the\n // store DB can't open). project.id is Noir-owned canonical; heal a corrupt\n // stamp by removing it so `skipIfExists` writes the resolved id fresh.\n // Absent/valid files and dryRun are left to the manifest writer.\n if (!opts.dryRun && idFile.state === 'corrupt') {\n rmSync(paths.projectId(opts.root), { force: true });\n }\n\n const fromVersion = readScaffoldVersion(opts.root);\n\n // 3. Stack detect (read-only, never throws). Always populated so callers\n // (TUI, doctor) get a single source of truth regardless of mode.\n const stack = detectStack(opts.root);\n // Compile the derivable baseline (stack + canonical commands + entry\n // points) into the markdown the `.noir/NOIR.md` brief block renders.\n const baseline = renderBaseline(stack, detectCommands(opts.root), detectEntryPoints(opts.root));\n\n // Ancestor snapshot is read + captured UNCONDITIONALLY (not just under\n // --merge) so the first merge run: now the DEFAULT: always has a base. The\n // merge APPLICATION still keys off `mergeManagedRegions` below; ancestor\n // CAPTURE is always-on. `writeAncestors` at the end dedups internally.\n const ancestors = readAncestors(opts.root);\n // mergeManagedRegions defaults to TRUE. `--no-merge-regions`\n // (CLI) restores strip-replace by setting opts.mergeManagedRegions = false.\n const mergeManagedRegions = opts.mergeManagedRegions !== false;\n\n // Widened no-op guard (was: scaffold-version present ONLY). A bare\n // `noir init`/`noir create` is a NO-OP when the project carries EITHER a\n // `.noir/scaffold-version` stamp (1.3.0+) OR a `.noir/project.id` (pre-1.3.0\n // legacy). Previously a legacy project (id present, no stamp) re-scaffolded\n // on every bare init; now it no-ops too. `--upgrade` stays the explicit\n // migration entry (a stamp-less legacy project runs the full chain from\n // `0.0.0`); `--force` re-scaffolds without migrating. Both bypass this guard.\n // sync is unaffected (it requires a valid project.id and emits the runtime subset).\n // dryRun returns the no-op shape silently so `noir doctor`/CI previews stay\n // clean.\n const hasIdentity = fromVersion !== null || idFile.state === 'valid';\n if (\n hasIdentity &&\n opts.upgrade !== true &&\n opts.force !== true &&\n (opts.mode === 'init' || opts.mode === 'create')\n ) {\n if (opts.dryRun !== true) {\n const where =\n fromVersion !== null\n ? `at scaffold ${fromVersion}`\n : '(pre-1.3.0 project; no scaffold-version stamp)';\n process.stderr.write(\n `Noir already initialized in ${opts.root} ${where}; run \\`noir init --upgrade\\` to migrate.\\n`,\n );\n }\n return {\n written: [],\n skipped: [],\n identical: [],\n refreshed: [],\n conflicts: [],\n fileModes: {},\n envMode: envOwnerOnly(opts),\n noop: true,\n migrationsRan: [],\n migrationConflicts: [],\n migrationChanged: [],\n stack,\n projectId,\n fromVersion,\n toVersion: CURRENT_SCAFFOLD_VERSION,\n host,\n };\n }\n\n // 4. Migrations (only when explicitly upgrading). A project with NO identity\n // at all: no scaffold-version stamp AND no valid project.id: is a fresh,\n // never-initialized tree: nothing to migrate, so skip entirely and report\n // no steps. A stamped project runs from its recorded version. A legacy\n // project (a valid project.id but no stamp) passes `null` through; the\n // runner treats `null` as `0.0.0`, so the full registered chain runs.\n const migrationsRan: string[] = [];\n const migrationConflicts: string[] = [];\n const migrationChanged: string[] = [];\n if (\n opts.mode === 'init' &&\n opts.upgrade === true &&\n (fromVersion !== null || idFile.state === 'valid')\n ) {\n const m = runMigrations(opts.root, fromVersion, CURRENT_SCAFFOLD_VERSION, {\n dryRun: opts.dryRun === true,\n // The migration runs BEFORE the manifest, so it cannot see the manifest's\n // own rules-gating. Hand the switch through so a migration that would\n // rewrite the working-rules seed (the doc-seed refresh keyed on recorded\n // older bytes) leaves a switch-off project's RULES.md alone.\n rulesEnabled: readConfiguredRulesEnabled(opts.root),\n });\n migrationsRan.push(...m.ran);\n migrationConflicts.push(...m.conflicts);\n migrationChanged.push(...m.changed);\n }\n\n // 5. Build manifest + filter by host + mode.\n // Resolve the MCP `command`: the absolute native shim when a native install\n // is detected (GUI MCP clients don't read shell profiles → `spawn noir\n // ENOENT`), else `'noir'`. Read-only + never throws; defaults to 'noir'.\n const command = resolveNoirCommand();\n const manifest = buildManifest({\n root: opts.root,\n projectId,\n host,\n transport,\n url: opts.url,\n command,\n stack,\n // The project's own working-rules switch: a project that set\n // `rules.enabled: false` gets no `.noir/rules/RULES.md`: not on the first\n // scaffold, and not backfilled by an upgrade. Unchanged (enabled) whenever\n // the config is absent or says nothing about rules.\n rulesEnabled: readConfiguredRulesEnabled(opts.root),\n });\n // Which subset this run emits: `null` means every mode (a fresh\n // `init`/`create`, and `--force`). `sync` re-emits the runtime subset;\n // `init --upgrade` additionally re-emits `skipIfExists`, whose\n // create-only-if-absent semantics are exactly what backfills a seed added to\n // the manifest after a project was initialized: the regression where\n // `.noir/.env.example` was reachable by no command at all. On top of that,\n // an upgrade is the only way to reach the doc-seed refresh: an entry flagged\n // `refreshIfStale` has its existing file READ and compared against the bytes\n // Noir used to ship, so a copy the user never touched gets the newer text.\n // Both branches keep the same guarantee for everything else: an unflagged\n // entry goes through the writer that does not open an existing file at all,\n // and a flagged seed the user edited only ever reaches the conflict flow.\n // `--upgrade` still excludes `mergeJson`: see {@link EMITTED_IN}.\n const emitContext: EmitContext | null =\n opts.mode === 'sync'\n ? 'sync'\n : opts.mode === 'init' && opts.upgrade === true\n ? 'upgrade'\n : null;\n const vars: BuildManifestContext = {\n root: opts.root,\n projectId,\n host,\n transport,\n url: opts.url,\n command,\n baseline,\n };\n\n const written: string[] = [];\n const skipped: string[] = [];\n const identical: string[] = [];\n const refreshed: string[] = [];\n const fileModes: Record<string, number> = {};\n // Per-run conflict memory + structured report. Memory is keyed by\n // artifact CLASS for `regenerate` (one decision shared across the run, so a\n // `noir init --upgrade` over N pointers → 1 prompt) and by per-file path for\n // `managedBlock`/`managedBlocks` (user edits there need individual review).\n const conflictMemory = new Map<string, ConflictResolution>();\n const conflictRecords: ConflictRecord[] = [];\n const recordConflict = (\n relPath: string,\n mode: NonNullable<ConflictContext['mode']>,\n existing: string,\n proposed: string,\n resolution: ConflictResolution,\n similarity?: number,\n ): void => {\n conflictRecords.push({\n path: relPath,\n mode,\n similarity,\n existingSha: sha256Hex12(existing),\n proposedSha: sha256Hex12(proposed),\n resolution,\n });\n };\n\n // GROUP applicable entries by target path (manifest order preserved within\n // each group) so files carrying MULTIPLE managed blocks (CLAUDE.md today =\n // CONTEXT + RULES) get ONE atomic multi-region write. Single-entry\n // paths keep the existing per-entry writer: byte-stable for the NOIR.md\n // brief, the ignore files, and the regenerated `.mcp.json`. dryRun uses the\n // SAME grouping so its reported paths match what a real run would write\n // (CLAUDE.md reported once, not twice).\n const groups = groupApplicableByPath(manifest, host, emitContext);\n for (const [relPath, entries] of groups) {\n const abs = join(opts.root, relPath);\n const managed = entries.filter((e) => e.mode === 'managedBlock');\n\n if (managed.length >= 2) {\n // Multi-managed-block file: ONE atomic write of all regions.\n if (opts.dryRun) {\n if (opts.check === true) {\n // Drift check: compare the would-be merged bytes without writing.\n const onDisk = readOptional(abs);\n const regions = managed.map((e) => {\n const block = e.block;\n if (!block) {\n throw new Error(`manifest entry ${e.path}: managedBlock mode missing 'block'`);\n }\n const theirs = buildRegion(block, renderEntry(e, vars));\n const merged = mergeManagedRegions\n ? mergeManagedRegion(abs, e.path, block, theirs, ancestors)\n : { text: theirs, conflict: false, cleanTheirs: theirs };\n return { block, regionText: merged.text };\n });\n const predicted = predictManagedBlocks(onDisk ?? '', regions);\n if (onDisk !== undefined && predicted === onDisk) identical.push(relPath);\n else written.push(relPath);\n continue;\n }\n written.push(relPath);\n continue;\n }\n mkdirSync(dirname(abs), { recursive: true });\n const onDisk = readOptional(abs);\n let hadMergeConflict = false;\n const regions = managed.map((e) => {\n const block = e.block;\n if (!block) {\n throw new Error(`manifest entry ${e.path}: managedBlock mode missing 'block'`);\n }\n const theirs = buildRegion(block, renderEntry(e, vars));\n const merged = mergeManagedRegions\n ? mergeManagedRegion(abs, e.path, block, theirs, ancestors)\n : { text: theirs, conflict: false, cleanTheirs: theirs };\n if (merged.conflict) hadMergeConflict = true;\n // Capture ancestor unconditionally (pre-write snapshot) so a later\n // merge run has a base even if this run was strip-replace.\n ancestors[`${e.path}::${block.begin}`] = theirs;\n return { block, regionText: merged.text };\n });\n // Managed-region conflict (per-file; never apply-to-all). Hand the\n // resolver the merged-with-markers bytes (the 6th \"merge\" option's\n // payload). Default behavior when no resolver is wired (or non-interactive):\n // write the merged-with-markers bytes + the stderr note (unchanged).\n if (hadMergeConflict) {\n const ctxMode: NonNullable<ConflictContext['mode']> = 'managedBlocks';\n const resolution = await resolveManagedConflict(relPath, ctxMode, opts);\n if (resolution === 'cancel') {\n throw new Error(`scaffold cancelled by user at conflicting file ${relPath}`);\n }\n // 'merge' / undefined → write the marked bytes (current behavior).\n if (resolution !== undefined && resolution !== 'merge') {\n // 'preserve' → skip the write entirely (user's overlap stands).\n // 'replace'/'duplicate'/'rename' → unsupported for managedBlocks\n // (managed regions are inside co-owned files); treat as 'merge'.\n process.stderr.write(\n `noir: managed-region conflict in ${relPath}: wrote inline markers; resolve manually.\\n`,\n );\n } else {\n process.stderr.write(\n `noir: managed-region conflict in ${relPath}: wrote inline markers; resolve manually.\\n`,\n );\n }\n }\n // Content-hash dedup: if the would-be-written bytes equal the on-disk\n // file, skip the write entirely (no mtime/git churn). `noir sync` on an\n // unchanged tree writes NOTHING.\n const predicted = predictManagedBlocks(onDisk ?? '', regions);\n if (onDisk !== undefined && predicted === onDisk) {\n identical.push(relPath);\n } else {\n managedBlocks(abs, regions);\n written.push(relPath);\n }\n continue;\n }\n\n // Per-entry path: single-region managed / regenerate / skipIfExists.\n if (!opts.dryRun) mkdirSync(dirname(abs), { recursive: true });\n for (const entry of entries) {\n if (opts.dryRun) {\n if (opts.check === true && (entry.mode === 'regenerate' || entry.mode === 'managedBlock')) {\n // Drift check: compare the would-be bytes without writing.\n const body = renderEntry(entry, vars);\n const onDisk = readOptional(abs);\n if (entry.mode === 'regenerate') {\n if (onDisk === undefined || onDisk !== body) written.push(entry.path);\n else identical.push(entry.path);\n } else {\n const block = entry.block;\n if (!block) {\n throw new Error(`manifest entry ${entry.path}: managedBlock mode missing 'block'`);\n }\n const theirs = buildRegion(block, body);\n const merged = mergeManagedRegions\n ? mergeManagedRegion(abs, entry.path, block, theirs, ancestors)\n : { text: theirs, conflict: false, cleanTheirs: theirs };\n const predicted = predictManagedBlock(onDisk ?? '', block, merged.text);\n if (onDisk !== undefined && predicted === onDisk) identical.push(entry.path);\n else written.push(entry.path);\n }\n continue;\n }\n if (entry.mode !== 'skipIfExists') {\n written.push(entry.path);\n continue;\n }\n const seedKind = refreshableSeedKind(entry, emitContext);\n if (seedKind === undefined) {\n if (existsSync(abs)) {\n skipped.push(entry.path);\n } else {\n written.push(entry.path);\n // Dry-run predictor: report the mode a real run WOULD apply,\n // without writing (`.noir/.env` → 0600).\n if (entry.fileMode !== undefined) fileModes[entry.path] = entry.fileMode;\n }\n continue;\n }\n // Refreshable seed. The comparison needs the current render, so this is\n // the one dry-run case that renders a body: a preview that reported a\n // different outcome from the run it previews would be worse than none.\n // An edited seed reads as \"already present\": the interactive conflict\n // prompt cannot run under a preview, and leaving the user's bytes alone\n // is what the same run does without a resolver.\n const plan = planSeedWrite(abs, seedKind, renderEntry(entry, vars));\n if (plan === 'create') {\n written.push(entry.path);\n if (entry.fileMode !== undefined) fileModes[entry.path] = entry.fileMode;\n } else if (plan === 'refresh') {\n refreshed.push(entry.path);\n } else {\n skipped.push(entry.path);\n }\n continue;\n }\n const body = renderEntry(entry, vars);\n if (entry.mode === 'regenerate') {\n const out = await writeWithConflict(abs, entry.path, body, opts, {\n memory: conflictMemory,\n record: recordConflict,\n });\n written.push(...out.written);\n skipped.push(...out.skipped);\n identical.push(...out.identical);\n } else if (entry.mode === 'managedBlock') {\n const block = entry.block;\n if (!block) {\n throw new Error(`manifest entry ${entry.path}: managedBlock mode missing 'block'`);\n }\n // A legacy .noir/NOIR.md (written before the artifact table shipped) is\n // a whole-file auto-brief with NO managed markers. The normal path\n // would treat the old brief as user content and append a SECOND managed\n // brief → two \"Project\n // id:\" lines. Self-heal: when the existing file has NO noir managed\n // marker at all, wipe it first so the managed write emits a clean\n // single brief. (That legacy NOIR.md was 100% auto-generated, so\n // there is no user content to preserve in that legacy shape.)\n if (isNoirMdPath(entry.path)) healLegacyNoirMd(abs);\n const theirs = buildRegion(block, body);\n const merged = mergeManagedRegions\n ? mergeManagedRegion(abs, entry.path, block, theirs, ancestors)\n : { text: theirs, conflict: false, cleanTheirs: theirs };\n const regionText = merged.text;\n // Capture ancestor unconditionally (pre-write snapshot).\n ancestors[`${entry.path}::${block.begin}`] = theirs;\n // Managed-region conflict (per-file; never apply-to-all). Hand the\n // resolver the merged-with-markers bytes (the 6th \"merge\" option's\n // payload). Default behavior when no resolver is wired (or\n // non-interactive): write the merged-with-markers bytes + the\n // stderr note (unchanged from v1.2).\n if (merged.conflict) {\n const ctxMode: NonNullable<ConflictContext['mode']> = 'managedBlock';\n const onDiskRegion = readOptional(abs);\n const ctx: ConflictContext = {\n relPath: entry.path,\n existing: onDiskRegion ?? '',\n proposed: theirs,\n mode: ctxMode,\n similarity: similarity(onDiskRegion ?? '', merged.text),\n mergedWithMarkers: merged.text,\n };\n const resolution = await resolveManagedConflictCtx(ctx, opts);\n if (resolution === 'cancel') {\n throw new Error(`scaffold cancelled by user at conflicting file ${entry.path}`);\n }\n if (resolution === 'preserve') {\n identical.push(entry.path); // user's overlap stands; skip the write.\n continue;\n }\n process.stderr.write(\n `noir: managed-region conflict in ${entry.path}: wrote inline markers; resolve manually.\\n`,\n );\n }\n // Content-hash dedup (post-heal: re-read, the heal may have wiped).\n // If the would-be-written bytes equal the on-disk file, skip the write\n // (no mtime/git churn): a no-op `noir sync` writes NOTHING.\n const onDisk = readOptional(abs);\n const predicted = predictManagedBlock(onDisk ?? '', block, regionText);\n if (onDisk !== undefined && predicted === onDisk) {\n identical.push(entry.path);\n } else {\n managedBlock(abs, block, regionText);\n written.push(entry.path);\n }\n } else if (entry.mode === 'mergeJson') {\n // SessionStart hook: merge-aware JSON write (preserves the user's\n // permissions/env/enabledPlugins, appends the hook entry once, deduped\n // by command substring). `body` is the rendered JSON patch; the dedup\n // marker comes from the entry.\n const patch = JSON.parse(body) as Record<string, unknown>;\n const out = mergeJson(abs, patch, entry.dedupSubstring);\n if (out.written) written.push(entry.path);\n else identical.push(entry.path);\n } else {\n // `skipIfExists`: create the seed when it is absent, otherwise leave it\n // untouched. A refreshable doc seed is the one exception, and only on\n // an upgrade: see `refreshableSeedKind`.\n const seedKind = refreshableSeedKind(entry, emitContext);\n if (seedKind === undefined) {\n // `entry.fileMode` rides along so a created `.noir/.env` is 0600 from\n // the moment it exists: see `skipIfExists`.\n const out = skipIfExists(abs, body, entry.fileMode);\n if (out.written) {\n written.push(entry.path);\n if (entry.fileMode !== undefined) fileModes[entry.path] = entry.fileMode;\n } else {\n skipped.push(entry.path);\n }\n continue;\n }\n const plan = planSeedWrite(abs, seedKind, body);\n if (plan === 'create') {\n skipIfExists(abs, body, entry.fileMode);\n written.push(entry.path);\n if (entry.fileMode !== undefined) fileModes[entry.path] = entry.fileMode;\n } else if (plan === 'refresh') {\n // The bytes on disk are an unedited copy of an older seed, so the\n // newer text costs the user nothing. The file's mode is left as it is.\n refreshSeed(abs, body);\n refreshed.push(entry.path);\n } else if (plan === 'keep') {\n skipped.push(entry.path);\n } else {\n // Edited by the user, so it is theirs: run the same conflict flow the\n // other write modes use rather than replacing anything silently. The\n // artifact class is `skipIfExists`, which scopes an \"apply to all\"\n // answer to the seeds alone: a decision about these doc files must\n // not ride along into `.mcp.json`'s pointer-file conflicts.\n const out = await writeWithConflict(abs, entry.path, body, opts, {\n memory: conflictMemory,\n record: recordConflict,\n artifactClass: 'skipIfExists',\n // No resolver answered (no TTY, `--no-input`, CI): keep the user's\n // file. An explicit overwrite policy is an instruction rather than a\n // missing answer, so `--force` still replaces.\n defaultResolution: opts.conflictPolicy === 'overwrite' ? 'replace' : 'preserve',\n });\n written.push(...out.written);\n skipped.push(...out.skipped);\n identical.push(...out.identical);\n }\n }\n }\n }\n\n // 6. Stamp scaffold-version on init/create (NOT sync). Written last so a\n // crash leaves the previous stamp. Upgrade rewrites it to current: but\n // ONLY when every migration actually applied. A non-empty\n // `migrationConflicts` means a transformation did NOT land, so stamping\n // \"current\" would make that failure permanent and invisible: the migration\n // would never retry (the window closes) and `noir doctor` would stop\n // reporting drift. Leaving the OLD stamp keeps both honest: the retry and\n // the drift report. The conflicts themselves are already reported to the\n // caller via `ScaffoldResult.migrationConflicts` (`--json` included).\n if (\n (opts.mode === 'init' || opts.mode === 'create') &&\n !opts.dryRun &&\n migrationConflicts.length === 0\n ) {\n writeScaffoldVersion(opts.root, CURRENT_SCAFFOLD_VERSION);\n }\n\n // Persist the ancestor snapshot UNCONDITIONALLY (every init/create/sync\n // seeds it) so the first merge run: now the default: has a base.\n // `writeAncestors` dedups internally: when the serialized bytes equal the\n // on-disk file, the rewrite is skipped, so a no-op sync leaves ancestors.json\n // untouched too (true zero-write idempotency).\n if (!opts.dryRun) {\n writeAncestors(opts.root, ancestors);\n }\n\n return {\n written,\n skipped,\n identical,\n refreshed,\n conflicts: conflictRecords,\n fileModes,\n // Re-asserted here, after the emit, so the mode on disk is the one this run\n // leaves behind: the seed writer only applies 0600 at creation (see\n // `envOwnerOnly`).\n envMode: envOwnerOnly(opts),\n noop: false,\n migrationsRan,\n migrationConflicts,\n migrationChanged,\n stack,\n projectId,\n fromVersion,\n toVersion: CURRENT_SCAFFOLD_VERSION,\n host,\n };\n}\n\n/** LCS-based similarity ratio in [0,1]. 1.0 = byte-identical lines, 0.0 =\n * wholly disjoint. Cheap signal the resolver can use to bias toward `replace`\n * when ~1 (cosmetic drift) vs `preserve` when ~0 (substantive edit). Reuses\n * the SAME LCS as {@link mergeThreeWay} so there is one line-diff algorithm. */\nfunction similarity(a: string, b: string): number {\n if (a === b) return 1;\n const A = a.split('\\n');\n const B = b.split('\\n');\n if (A.length === 0 && B.length === 0) return 1;\n // `lcsMatch` returns the paired anchor indices; |anchors| / max(len) is the\n // similarity. Re-derive here (without importing merge.ts) so the engine stays\n // decoupled from the merge module's diff3 specifics.\n const n = A.length;\n const m = B.length;\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0));\n for (let i = n - 1; i >= 0; i--) {\n const dpi = dp[i] ?? [];\n const dpi1 = dp[i + 1] ?? [];\n const ai = A[i] ?? '';\n for (let j = m - 1; j >= 0; j--) {\n const bj = B[j] ?? '';\n dpi[j] = ai === bj ? (dpi1[j + 1] ?? 0) + 1 : Math.max(dpi1[j] ?? 0, dpi[j + 1] ?? 0);\n }\n }\n const lcs = dp[0]?.[0] ?? 0;\n return n + m === 0 ? 1 : (2 * lcs) / (n + m);\n}\n\n// --- helpers -----------------------------------------------------------------\n\n/** Three-way merge a managed region against the persisted ancestor\n * (`base`). `theirs` is the freshly-rendered template region (with markers);\n * `ours` is the region currently on disk. With no ancestor / no existing\n * region this is a no-op (returns `theirs`). On conflict, returns the merged\n * bytes WITH zdiff3 markers as `text` AND `conflict: true`, so the per-file\n * scaffold site can hand the resolver a 6th \"merge (with conflict markers)\"\n * option. `cleanTheirs` is the un-merged template: what the\n * `'replace'` resolution would write. */\nfunction mergeManagedRegion(\n abs: string,\n relPath: string,\n block: ManagedBlock,\n theirs: string,\n ancestors: Record<string, string>,\n): { text: string; conflict: boolean; cleanTheirs: string } {\n const base = ancestors[`${relPath}::${block.begin}`];\n if (base === undefined) return { text: theirs, conflict: false, cleanTheirs: theirs };\n const ours = readManagedBlock(abs, block);\n if (ours === null) return { text: theirs, conflict: false, cleanTheirs: theirs };\n // `readManagedBlock` returns the matched region WITHOUT the trailing `\\n`\n // after `end`, while `buildRegion`/`theirs` INCLUDE it. Feeding the raw `ours`\n // into mergeThreeWay makes every merge return a region missing that newline,\n // so the writer would drop it on each sync (byte drift) AND the content-hash\n // dedup could never fire (predicted !== onDisk by one `\\n`). Normalize `ours`\n // to `theirs`'s shape (always end with `\\n`) so an unchanged region stays\n // byte-identical and `noir sync` on an unchanged tree is a true no-op.\n const oursNormalized = ours.endsWith('\\n') ? ours : `${ours}\\n`;\n // zdiff3 markers so the resolver's 6th option shows the base section.\n const res = mergeThreeWay(base, oursNormalized, theirs, 'zdiff3');\n return { text: res.merged, conflict: res.conflict, cleanTheirs: theirs };\n}\n\n/** Consult {@link ScaffoldOptions.onConflict} for a managed-region merge\n * conflict (single-block path). Per-file (never apply-to-all: user edits\n * inside a `<!-- noir:* -->` region need individual review). When no resolver\n * is wired OR the engine is non-interactive, returns `undefined` so the caller\n * falls through to the earlier behavior (write the merged-with-markers bytes +\n * the stderr note). Returns the resolver's bare resolution choice. */\nasync function resolveManagedConflictCtx(\n ctx: ConflictContext,\n opts: ScaffoldOptions,\n): Promise<ConflictResolution | undefined> {\n if (opts.onConflict === undefined) return undefined;\n const ret = await opts.onConflict(ctx);\n const resolution = typeof ret === 'string' ? ret : ret.resolution;\n if (resolution === 'rename' || resolution === 'duplicate') {\n // Not meaningful for a region INSIDE a co-owned file. Treat as 'merge'\n // (write the marked bytes: the user keeps both sides, manually resolves).\n return 'merge';\n }\n if (resolution === 'replace') {\n // 'replace' on a managed region → write the clean template (clobber the\n // user's overlap inside this region). The caller writes `cleanTheirs`\n // (the un-merged template region) instead of the marked merge.\n return 'replace';\n }\n // 'merge' / 'preserve' / 'cancel': pass through.\n return resolution;\n}\n\n/** Consult {@link ScaffoldOptions.onConflict} for a managed-region merge\n * conflict (multi-block path). Simpler than the single-block path: the\n * multi-block file is written atomically as ONE unit, so 'rename'/'duplicate'\n * /'replace' have no clean meaning (the regions live alongside user content in\n * a co-owned file). Only 'merge' (write the marked bytes), 'preserve' (skip),\n * and 'cancel' (abort) are honored; others fall through to 'merge'. */\nasync function resolveManagedConflict(\n relPath: string,\n mode: NonNullable<ConflictContext['mode']>,\n opts: ScaffoldOptions,\n): Promise<ConflictResolution | undefined> {\n if (opts.onConflict === undefined) return undefined;\n const ret = await opts.onConflict({ relPath, existing: '', proposed: '', mode });\n const resolution = typeof ret === 'string' ? ret : ret.resolution;\n return resolution;\n}\n\n/** Read the `.noir/project.id` stamp ONCE and classify it for BOTH id\n * resolution and the corrupt-file heal. `absent` (ENOENT) and `valid`\n * (non-empty) are the normal cases; `corrupt` (file exists but trims to empty)\n * is healed by the orchestrator before the manifest loop so `skipIfExists`\n * writes the resolved id fresh instead of preserving the empty file. */\nfunction readProjectIdFile(\n root: string,\n): { state: 'absent'; id: null } | { state: 'valid'; id: string } | { state: 'corrupt'; id: null } {\n let raw: string;\n try {\n raw = readFileSync(paths.projectId(root), 'utf8');\n } catch {\n return { state: 'absent', id: null };\n }\n const trimmed = raw.trim();\n return trimmed.length > 0 ? { state: 'valid', id: trimmed } : { state: 'corrupt', id: null };\n}\n\nfunction resolveProjectId(\n opts: ScaffoldOptions,\n idFile: ReturnType<typeof readProjectIdFile>,\n): string {\n if (opts.projectId !== undefined) return opts.projectId;\n if (idFile.state === 'valid') return idFile.id;\n // absent OR corrupt → resolve a fresh id. sync still requires a valid\n // pre-existing id (a corrupt stamp can't be trusted to name the store DB).\n if (opts.mode === 'sync') {\n throw new Error(`Noir is not initialized in ${opts.root}. Run \\`noir init\\` first.`);\n }\n return createProjectId();\n}\n\n/** What a refreshable seed's write should do, decided from the bytes on disk.\n * Split out so the dry-run predictor and the real write path agree by\n * construction: they are the same decision, evaluated twice. */\ntype SeedPlan = 'create' | 'keep' | 'refresh' | 'conflict';\n\n/**\n * Decide what to do with a refreshable seed, from the file currently on disk:\n * - absent → `create`: the ordinary seed backfill.\n * - an exact copy of a RECORDED → `refresh`: Noir wrote these bytes and\n * older version the user never changed them.\n * - an exact copy of what Noir → `keep`: already current, nothing to do.\n * writes today\n * - present but unreadable → `keep`: the bytes cannot be compared,\n * so they cannot be shown to be Noir's.\n * - anything else → `conflict`: these are the user's bytes,\n * and only the user may replace them.\n *\n * The order matters only in that `keep` is checked after the staleness test,\n * which already excludes the current render; every non-matching byte pattern\n * falls through to `conflict`, the conservative branch.\n *\n * A read that fails for any reason other than \"no file there\" resolves to\n * `keep` rather than propagating. Deciding whether to replace a file requires\n * reading it, and writing one whose bytes could not be read is the one outcome\n * this whole path exists to prevent: so an unreadable seed (permissions, a\n * symlink loop, a directory in the way) is left exactly as it is and reported\n * as skipped, the same as any other file the run declines to touch. Letting the\n * error escape would instead abort the emit loop part-way through, leaving the\n * project half-upgraded and its version stamp unwritten.\n *\n * `conflict` is NOT the fallback for this case even though the conflict flow\n * defaults to preserving: that path reads the file itself and treats a failed\n * read as \"no file\", which would write the new seed over the unreadable one.\n */\nfunction planSeedWrite(abs: string, seed: SeedKind, currentRender: string): SeedPlan {\n let existing: string | undefined;\n try {\n existing = readOptional(abs);\n } catch {\n return 'keep';\n }\n if (existing === undefined) return 'create';\n if (isStaleSeed(seed, existing, currentRender)) return 'refresh';\n if (existing === currentRender) return 'keep';\n return 'conflict';\n}\n\n/** Which recorded seed this entry's bytes may be compared against, or\n * `undefined` when the entry is not refreshable in this run.\n *\n * Refresh is an upgrade-only behavior, and that condition lives here rather\n * than in the manifest so one place defines \"which run may replace a seed\": a\n * fresh `init`/`create` has no already-written seed to compare against (its\n * entry either creates the file or leaves it alone), and `sync` does not emit\n * seeds at all.\n *\n * Throws when a flagged entry declares no seed kind. Such an entry could only\n * ever be compared against the wrong seed's bytes, and the consequence of that\n * comparison is an overwrite: so it fails loudly instead of guessing. */\nfunction refreshableSeedKind(\n entry: ManifestEntry,\n emitContext: EmitContext | null,\n): SeedKind | undefined {\n if (emitContext !== 'upgrade' || entry.refreshIfStale !== true) return undefined;\n const kind = REFRESHABLE_SEED_KIND[entry.path];\n if (kind === undefined) {\n throw new Error(\n `manifest entry ${entry.path}: 'refreshIfStale' is set but no recorded seed is declared for that path`,\n );\n }\n return kind;\n}\n\n/**\n * Write a file whose bytes DIFFER from what is on disk, honoring conflict\n * resolution when the target already exists. Identical bytes (or a missing\n * file) write straight through (content-hash dedup is not applied at this\n * level: identical still \"writes\" today to keep sync/--upgrade byte-stable).\n * Resolution:\n * - `replace` : overwrite.\n * - `preserve`/`cancel`: keep the user's file; report skipped.\n * - `rename` : move the user's file to `<path>.local`, write the new\n * bytes in place.\n * - `duplicate` : write the new bytes to `<path>.noir`, keep the user's.\n * Returns the repo-relative paths to record as written / skipped.\n *\n * Both callers are user-owned targets that must not be clobbered on a whim: a\n * `regenerate` pointer file and a refreshable doc seed whose bytes no longer\n * match Noir's record. The two differ only in their class key and in what \"no\n * answer\" means, which is why those are arguments rather than a second copy of\n * this switch.\n */\nasync function writeWithConflict(\n abs: string,\n relPath: string,\n proposed: string,\n opts: ScaffoldOptions,\n internals: {\n /** Per-run memory keyed by artifact CLASS: populated when a resolver\n * returns `applyToAll`. */\n memory: Map<string, ConflictResolution>;\n /** Append a structured record for `ScaffoldResult.conflicts`. */\n record: (\n relPath: string,\n mode: NonNullable<ConflictContext['mode']>,\n existing: string,\n proposed: string,\n resolution: ConflictResolution,\n similarity?: number,\n ) => void;\n /** Artifact class this write belongs to. Decides the scope of an\n * \"apply to all\" answer and the label on the conflict record; defaults to\n * `'regenerate'` (a pointer file, the historical caller). */\n artifactClass?: NonNullable<ConflictContext['mode']>;\n /** Resolution to apply when there is no resolver AND the policy does not\n * already say `preserve`. Defaults to `'replace'`: a pointer file is\n * Noir-owned and exists to be re-emitted. A caller writing a file the USER\n * owns passes `'preserve'`, so an unattended run (no TTY, `--no-input`,\n * CI) leaves their bytes alone. */\n defaultResolution?: ConflictResolution;\n },\n): Promise<{ written: string[]; skipped: string[]; identical: string[] }> {\n let existing: string | undefined;\n try {\n existing = readFileSync(abs, 'utf8');\n } catch {\n existing = undefined;\n }\n if (existing === undefined) {\n regenerate(abs, proposed);\n return { written: [relPath], skipped: [], identical: [] };\n }\n if (existing === proposed) {\n // content-hash dedup: byte-identical → skip the rewrite entirely (no disk IO).\n return { written: [], skipped: [], identical: [relPath] };\n }\n // Apply-to-all memory: files of one class share a single decision across the\n // run (so a `noir init --upgrade` over N pointers → 1 prompt).\n const MODE: NonNullable<ConflictContext['mode']> = internals.artifactClass ?? 'regenerate';\n const sim = similarity(existing, proposed);\n let resolution: ConflictResolution;\n const remembered = internals.memory.get(MODE);\n if (remembered !== undefined) {\n resolution = remembered;\n } else if (opts.onConflict !== undefined) {\n const ret = await opts.onConflict({ relPath, existing, proposed, mode: MODE, similarity: sim });\n const unwrapped: ConflictResolution = typeof ret === 'string' ? ret : ret.resolution;\n if (typeof ret !== 'string' && ret.applyToAll === true) {\n internals.memory.set(MODE, unwrapped);\n }\n resolution = unwrapped;\n } else {\n resolution =\n opts.conflictPolicy === 'preserve' ? 'preserve' : (internals.defaultResolution ?? 'replace');\n }\n // Record the conflict ALWAYS (interactive or not) so `--json`/CI can see\n // exactly which files diverged + how the engine resolved them. The prompt\n // never fires under non-interactive (buildConflictOpts returns preserve w/o\n // onConflict); the record still lands.\n internals.record(relPath, MODE, existing, proposed, resolution, sim);\n switch (resolution) {\n case 'replace':\n regenerate(abs, proposed);\n return { written: [relPath], skipped: [], identical: [] };\n case 'merge': {\n // `merge` is only meaningful when ctx.mergedWithMarkers was populated\n // (managed-region path). For a bare regenerate conflict without markers\n // the resolver should not pick `merge`; defensively fall back to replace\n // (better than dropping the user's bytes).\n regenerate(abs, proposed);\n return { written: [relPath], skipped: [], identical: [] };\n }\n case 'rename': {\n // Preserve the user's file aside at a UNIQUE path. Review fix: a bare\n // `renameSync(abs, abs.local)` would silently clobber a pre-existing\n // `.local` (POSIX rename replaces) or throw EEXIST mid-scaffold (win32) —\n // the very data-loss the conflict flow exists to prevent. uniqueAside\n // picks a fresh\n // `.local` (then `.local.1`, …) so the move is always safe.\n const aside = uniqueAside(abs, relPath, '.local');\n renameSync(abs, aside.abs);\n regenerate(abs, proposed);\n return { written: [relPath], skipped: [aside.rel], identical: [] };\n }\n case 'duplicate': {\n // Write the template ALONGSIDE at a unique path; keep the user's file\n // untouched. (Same unique-suffix safeguard as `rename`.)\n const aside = uniqueAside(abs, relPath, '.noir');\n regenerate(aside.abs, proposed);\n return { written: [aside.rel], skipped: [relPath], identical: [] };\n }\n case 'preserve':\n return { written: [], skipped: [relPath], identical: [] };\n case 'cancel':\n // Review fix: Cancel ABORTS the whole scaffold. It used to fall through\n // to \"skip this file\" and keep writing the remaining entries: a contract\n // violation (Cancel/Escape must stop the run). Throwing propagates out of\n // scaffold(); the cli reports it. Entries written before this conflict\n // remain on disk, as with any cancelled operation.\n throw new Error(`scaffold cancelled by user at conflicting file ${relPath}`);\n default:\n return { written: [], skipped: [relPath], identical: [] };\n }\n}\n\n/** Pick a fresh `<abs><suffix>` aside path (plus its repo-relative form) that\n * does NOT exist: tries `<suffix>`, then `<suffix>.1`, `<suffix>.2`, … so the\n * `rename`/`duplicate` resolutions never silently overwrite a prior backup\n * (data-loss) and never hit win32 EEXIST. */\nfunction uniqueAside(abs: string, relPath: string, suffix: string): { abs: string; rel: string } {\n const make = (s: string): { abs: string; rel: string } => ({\n abs: `${abs}${s}`,\n rel: `${relPath}${s}`,\n });\n let candidate = make(suffix);\n for (let n = 1; existsSync(candidate.abs); n++) candidate = make(`${suffix}.${n}`);\n return candidate;\n}\n\n/** Group applicable manifest entries by target path, preserving manifest order\n * within each group. JS `Map` preserves insertion order, so iterating groups\n * visits paths in the same sequence the manifest declares them (CONTEXT before\n * RULES inside the CLAUDE.md group). */\nfunction groupApplicableByPath(\n manifest: readonly ManifestEntry[],\n host: HostTag,\n /** The partial emit this run performs, or `null` for \"every mode\". Modes not\n * listed in {@link EMITTED_IN} for the context are dropped here: the single\n * place the manifest subset is decided. */\n emitContext: EmitContext | null,\n): Map<string, ManifestEntry[]> {\n const groups = new Map<string, ManifestEntry[]>();\n for (const entry of manifest) {\n if (entry.host !== undefined && entry.host !== host) continue; // host filter\n if (emitContext !== null && !EMITTED_IN[entry.mode][emitContext]) continue;\n const list = groups.get(entry.path);\n if (list) list.push(entry);\n else groups.set(entry.path, [entry]);\n }\n return groups;\n}\n\n/** True for the canonical NOIR.md path the manifest emits (the BRIEF_BLOCK\n * target). Scoped so the legacy-heal only fires for that one file: we must\n * not wipe arbitrary co-owned managed files. */\nfunction isNoirMdPath(relPath: string): boolean {\n return relPath === '.noir/NOIR.md';\n}\n\n/** Self-heal: wipe a legacy whole-file NOIR.md before the managed write.\n * Legacy shape = file exists but contains NO `<!-- noir:<name> begin -->`\n * managed marker (the whole file was the auto-brief). Files that already have\n * markers, or are absent, are left untouched (normal managed-block path or\n * fresh write respectively). */\nfunction healLegacyNoirMd(absPath: string): void {\n let content: string;\n try {\n content = readFileSync(absPath, 'utf8');\n } catch {\n return; // absent: fresh write, nothing to heal\n }\n if (/<!-- noir:[a-z]+ begin -->/.test(content)) return; // already managed-shape\n rmSync(absPath, { force: true });\n}\n\nfunction renderEntry(entry: ManifestEntry, vars: BuildManifestContext): string {\n if (entry.template !== undefined) {\n return render(loadTemplate(entry.template), vars);\n }\n if (entry.content !== undefined) return entry.content;\n throw new Error(`manifest entry ${entry.path}: must define 'content' or 'template'`);\n}\n\n/** Read a file as UTF-8, returning `undefined` when it is absent (ENOENT).\n * Used by the content-hash dedup to read the on-disk bytes ONCE per managed\n * target and feed them to the predictor + the byte-equality check. Any other\n * read error re-throws (a real IO failure should not be silenced into a\n * mistaken \"identical → skip write\"). */\nfunction readOptional(absPath: string): string | undefined {\n try {\n return readFileSync(absPath, 'utf8');\n } catch (err) {\n if (err instanceof Error && (err as NodeJS.ErrnoException).code === 'ENOENT') {\n return undefined;\n }\n throw err;\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { StackInfo } from './stack-detect.js';\n\n/**\n * Project-baseline compilation: the derivable facts (`detectStack`'s stack\n * summary plus canonical commands and entry points) rendered into the short\n * markdown block the host's `@import`ed `.noir/NOIR.md` carries. Read-only and\n * total: a foreign or empty dir still renders a minimal \"unknown\" block, never\n * throws.\n */\n\n/** package.json script keys we surface as canonical commands. */\nconst COMMAND_KEYS: ReadonlyArray<[name: string, key: string]> = [\n ['build', 'build'],\n ['test', 'test'],\n ['lint', 'lint'],\n ['typecheck', 'typecheck'],\n ['format', 'format'],\n ['start', 'start'],\n ['dev', 'dev'],\n];\n\n/** Well-known entry-point files probed at the project root only. */\nconst ENTRY_POINTS: ReadonlyArray<string> = [\n 'src/index.ts',\n 'src/index.tsx',\n 'src/index.js',\n 'src/index.jsx',\n 'src/main.ts',\n 'src/main.js',\n 'src/main.py',\n 'main.ts',\n 'main.js',\n 'main.py',\n 'app/main.py',\n 'src/app.py',\n 'cmd/main.go',\n 'src/lib.rs',\n 'src/main.rs',\n];\n\n/** Canonical commands found in package.json scripts (plus Makefile targets). */\nexport function detectCommands(root: string): Record<string, string> {\n const out: Record<string, string> = {};\n const pjPath = join(root, 'package.json');\n if (existsSync(pjPath)) {\n let pj: { scripts?: Record<string, string> } | undefined;\n try {\n pj = JSON.parse(readFileSync(pjPath, 'utf8')) as { scripts?: Record<string, string> };\n } catch {\n pj = undefined;\n }\n for (const [name, key] of COMMAND_KEYS) {\n const value = pj?.scripts?.[key];\n if (typeof value === 'string' && value !== '') out[name] = value;\n }\n }\n const makefile = join(root, 'Makefile');\n if (existsSync(makefile)) {\n try {\n const raw = readFileSync(makefile, 'utf8');\n for (const name of ['build', 'test', 'lint']) {\n if (!(name in out) && new RegExp(`^${name}:`, 'm').test(raw)) out[name] = `make ${name}`;\n }\n } catch {\n // unreadable Makefile: skip\n }\n }\n return out;\n}\n\n/** Entry-point files found at the project root. */\nexport function detectEntryPoints(root: string): string[] {\n return ENTRY_POINTS.filter((p) => existsSync(join(root, p)));\n}\n\n/** Render the short baseline block, always at least one line. */\nexport function renderBaseline(\n stack: StackInfo,\n commands: Record<string, string>,\n entryPoints: string[],\n): string {\n const lang = stack.languages.length > 0 ? stack.languages.join(', ') : 'unknown';\n const framework = stack.frameworks.length > 0 ? ` (${stack.frameworks.join(', ')})` : '';\n const lines = [\n `- **Stack:** ${lang}${framework}${stack.monorepo ? ' (monorepo)' : ''}`,\n `- **Package manager:** ${stack.packageManager ?? 'none'}`,\n ];\n if (stack.ci) lines.push(`- **CI:** ${stack.ci}`);\n const entries = Object.entries(commands);\n if (entries.length > 0) {\n lines.push('- **Commands:**');\n for (const [name, command] of entries) lines.push(` - \\`${name}\\`: \\`${command}\\``);\n }\n if (entryPoints.length > 0) lines.push(`- **Entry points:** ${entryPoints.join(', ')}`);\n return lines.join('\\n');\n}\n","import { mkdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { NOIR_DIR } from '@noir-ai/core';\nimport { regenerate } from './writers.js';\n\n/**\n * Scaffold version stamp: Noir's equivalent of Copier's `last-applied`.\n * Stored at `.noir/scaffold-version` as a single `noir-scaffold=<semver>` line\n * so `noir init --upgrade` / `noir doctor` can diff against\n * {@link CURRENT_SCAFFOLD_VERSION} and decide which migrations to run.\n *\n * Format is line-oriented (not YAML) on purpose: it must be readable before\n * `config.yml` is parsed (doctor runs even with a broken config), and the\n * `key=value` shape is trivial to grep from a shell.\n */\n\n/** The scaffold version this build of @noir-ai/create ships. Bumped atomically\n * whenever a manifest entry, template, or migration changes shape. 1.3.0 adds\n * the migration that points a joined repo's MCP entry back at the workspace\n * bridge; 1.4.0 adds the migration that moves the SessionStart hook command off\n * the `.mjs` runner and onto `noir hook`; 1.5.0 adds the migration that appends\n * the enforcement events to an existing claude hook and backfills the gemini\n * hook (see migrations/index.ts). */\nexport const CURRENT_SCAFFOLD_VERSION = '1.5.0';\n\nconst PREFIX = 'noir-scaffold=';\n\n/** Path to the stamp file under `root`. Exposed for tests + doctor. */\nexport function scaffoldVersionPath(root: string): string {\n return join(root, NOIR_DIR, 'scaffold-version');\n}\n\n/** Read the applied scaffold version, or `null` if the stamp is absent/unparseable.\n * Never throws: doctor must keep reporting even on a malformed stamp. */\nexport function readScaffoldVersion(root: string): string | null {\n let raw: string;\n try {\n raw = readFileSync(scaffoldVersionPath(root), 'utf8');\n } catch {\n return null;\n }\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed.startsWith(PREFIX)) {\n const v = trimmed.slice(PREFIX.length).trim();\n if (v.length > 0) return v;\n }\n }\n return null;\n}\n\n/** Write the stamp, creating `.noir/` if needed. The orchestrator writes it\n * LAST so a crash mid-scaffold leaves an old/absent stamp rather than a\n * misleading fresh one.\n *\n * Routed through the package's atomic `regenerate()` writer (tmp+rename in\n * the same dir) for consistency with the rest of the engine's durable writes —\n * a half-written stamp would mislead `noir doctor`/`init --upgrade`, so the\n * stamp deserves the same crash-atomicity as `.mcp.json` and the NOIR.md brief. */\nexport function writeScaffoldVersion(root: string, version: string): void {\n const file = scaffoldVersionPath(root);\n mkdirSync(dirname(file), { recursive: true });\n regenerate(file, `${PREFIX}${version}\\n`);\n}\n","import { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * READ-ONLY stack detection. Probes for well-known marker files under `root`\n * and reports ONLY what is present: never assumes. The result feeds path\n * adaptation (where to drop `.claude/` vs `.cursor/` etc., future), ignore-file\n * selection, and the onboarding TUI's confirm step.\n *\n * Design rules:\n * - Never throws. A foreign/empty dir returns `{ languages: [], monorepo:\n * false, frameworks: [], packageManager: null }`.\n * - Never opens network, never parses code beyond a `package.json`/`pyproject`\n * dependency list. Marker files + top-level manifests only.\n * - Frameworks are reported only when both the marker file is present AND the\n * framework's dependency is listed (avoids false positives from a stale\n * `package.json`).\n */\n\nexport interface StackInfo {\n /** Lower-cased language ids found via marker files:\n * `typescript` | `javascript` | `python` | `go` | `rust`. */\n languages: string[];\n /** True when a workspace manifest is present (pnpm-workspace, npm/yarn\n * workspaces in package.json, turbo.json, nx.json). */\n monorepo: boolean;\n /** Lower-cased framework ids (e.g. `next`, `vite`, `express`, `fastapi`,\n * `actix`). Empty when none detected. */\n frameworks: string[];\n /** `pnpm` | `npm` | `yarn` when a lockfile is present, else null. */\n packageManager: string | null;\n /**\n * Source of the resolved PM. `packageManager-field`\n * (package.json#packageManager), `lockfile`, `user-agent` (invoke-time\n * npm_config_user_agent), or `unknown`.\n */\n pmSource: 'packageManager-field' | 'lockfile' | 'user-agent' | 'unknown';\n /**\n * Detected CI. `github`/`gitlab`/`circleci`/\n * `jenkins`/`none`.\n */\n ci: string | null;\n /**\n * Existing AI instruction files found under root.\n * Never clobbered: the scaffold confirms the write strategy (skip/add-section/\n * standalone).\n */\n existingAiFiles: { path: string; kind: string }[];\n /** True when the invoke-time PM conflicts with the detected project-state PM. */\n pmConflict: boolean;\n}\n\n/** Frameworks looked up against `package.json#dependencies`+`devDependencies`.\n * Keyed by the dependency name as published on npm. */\nconst NODE_FRAMEWORKS: ReadonlyArray<[dep: string, id: string]> = [\n ['next', 'next'],\n ['vite', 'vite'],\n ['express', 'express'],\n ['fastify', 'fastify'],\n ['nuxt', 'nuxt'],\n ['remix', 'remix'],\n ['@sveltejs/kit', 'sveltekit'],\n ['@angular/core', 'angular'],\n ['react', 'react'],\n ['vue', 'vue'],\n];\n\n/** Frameworks looked up against `[project] dependencies` /\n * `[project.optional-dependencies]` / `[tool.poetry.dependencies]` in\n * `pyproject.toml`. Keyed by the PyPI package name. */\nconst PYTHON_FRAMEWORKS: ReadonlyArray<[dep: string, id: string]> = [\n ['fastapi', 'fastapi'],\n ['flask', 'flask'],\n ['django', 'django'],\n ['sanic', 'sanic'],\n ['starlette', 'starlette'],\n ['tornado', 'tornado'],\n ['aiohttp', 'aiohttp'],\n ['bottle', 'bottle'],\n ['pyramid', 'pyramid'],\n ['falcon', 'falcon'],\n];\n\n/** Read+parse JSON without throwing; returns undefined on any error. JSON5/ESM\n * `package.json` with comments would land here too: `package.json` is plain\n * JSON in practice so a failed `JSON.parse` genuinely means \"not a node\n * project\" or \"broken file\", both of which we report as \"absent\". */\nfunction readJson<T = unknown>(file: string): T | undefined {\n try {\n return JSON.parse(readFileSync(file, 'utf8')) as T;\n } catch {\n return undefined;\n }\n}\n\ninterface PackageJson {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n workspaces?: string[] | { packages?: string[] };\n packageManager?: string;\n}\n\nexport function detectStack(root: string): StackInfo {\n const languages = new Set<string>();\n const frameworks = new Set<string>();\n let monorepo = false;\n let packageManager: string | null = null;\n\n // Node / JS / TS: gated on package.json presence.\n const pjPath = join(root, 'package.json');\n if (existsSync(pjPath)) {\n const pj = readJson<PackageJson>(pjPath);\n if (pj) {\n const hasTs =\n Boolean(pj.devDependencies?.typescript) || existsSync(join(root, 'tsconfig.json'));\n languages.add(hasTs ? 'typescript' : 'javascript');\n\n const deps = new Set([\n ...Object.keys(pj.dependencies ?? {}),\n ...Object.keys(pj.devDependencies ?? {}),\n ]);\n for (const [dep, id] of NODE_FRAMEWORKS) {\n if (deps.has(dep)) frameworks.add(id);\n }\n\n const ws = pj.workspaces;\n if (\n Array.isArray(ws) ||\n (typeof ws === 'object' && ws !== null && Array.isArray(ws.packages))\n ) {\n monorepo = true;\n }\n if (typeof pj.packageManager === 'string' && pj.packageManager.length > 0) {\n // `packageManager: pnpm@10.12.4` → `pnpm`. Yarn/npm similarly.\n const m = pj.packageManager.split('@')[0];\n if (m) packageManager = m;\n }\n }\n }\n\n // Workspace manifests (these override/confirm monorepo independent of pj).\n if (existsSync(join(root, 'pnpm-workspace.yaml'))) {\n monorepo = true;\n packageManager = packageManager ?? 'pnpm';\n }\n if (existsSync(join(root, 'turbo.json')) || existsSync(join(root, 'nx.json'))) {\n monorepo = true;\n }\n\n // Lockfiles pin packageManager when `package.json#packageManager` didn't.\n if (!packageManager) {\n if (existsSync(join(root, 'pnpm-lock.yaml'))) packageManager = 'pnpm';\n else if (existsSync(join(root, 'yarn.lock'))) packageManager = 'yarn';\n else if (existsSync(join(root, 'package-lock.json'))) packageManager = 'npm';\n }\n\n // Python: pyproject.toml / requirements.txt / Pipfile / setup.py.\n if (\n existsSync(join(root, 'pyproject.toml')) ||\n existsSync(join(root, 'requirements.txt')) ||\n existsSync(join(root, 'Pipfile')) ||\n existsSync(join(root, 'setup.py'))\n ) {\n languages.add('python');\n // Framework detection scans pyproject.toml for PEP 508 dependency names\n // under `[project] dependencies` / `[project.optional-dependencies]` /\n // `[tool.poetry.dependencies]`. A full section-aware TOML parse is overkill\n // at v1 (and would need a dep we don't ship); the boundary-aware text scan\n // in `pyprojectHasDep` is robust to the three formats users actually write\n // and never throws on malformed TOML (degrades to \"no match\").\n if (existsSync(join(root, 'pyproject.toml'))) {\n const raw = safeRead(join(root, 'pyproject.toml'));\n if (raw) {\n for (const [dep, id] of PYTHON_FRAMEWORKS) {\n if (pyprojectHasDep(raw, dep)) frameworks.add(id);\n }\n }\n }\n }\n\n // Go: go.mod.\n if (existsSync(join(root, 'go.mod'))) {\n languages.add('go');\n packageManager = packageManager ?? 'go-modules';\n }\n\n // Rust: Cargo.toml.\n if (existsSync(join(root, 'Cargo.toml'))) {\n languages.add('rust');\n const raw = safeRead(join(root, 'Cargo.toml'));\n if (raw) {\n // Cargo.toml deps are always `name = \"ver\"` or `name = { … }`, so\n // require the `=` after the crate name. The old `/^\\s*actix\\b/m` matched\n // `actix-web` (word boundary between `x` and `-`) and falsely reported\n // `actix`. Treat the hyphenated runtime (`actix-web`) as its OWN id and\n // gate bare `actix` on the equals form. Same equals-form tightening is\n // applied to `axum`/`rocket` so `axum-extra`-style crates don't trip the\n // same bug. The `/m` flag makes `^` match any line (the previous\n // `/^actix\\s*=/` had no `/m` and only matched a string starting with\n // `actix`, i.e. effectively never: dead code).\n if (/^\\s*actix-web\\b/m.test(raw)) frameworks.add('actix-web');\n if (/^\\s*actix\\s*=/m.test(raw)) frameworks.add('actix');\n if (/^\\s*axum\\s*=/m.test(raw)) frameworks.add('axum');\n if (/^\\s*rocket\\s*=/m.test(raw)) frameworks.add('rocket');\n }\n packageManager = packageManager ?? 'cargo';\n }\n\n // Invoke-time PM via npm_config_user_agent (fallback\n // after lockfile + packageManager field). `pmConflict` flags when invoke-time\n // differs from project-state (the \"no surprise\" principle).\n let pmSource: StackInfo['pmSource'] = 'unknown';\n let pmConflict = false;\n if (packageManager) {\n if (pjPath && existsSync(pjPath)) {\n const pj = readJson<PackageJson>(pjPath);\n if (pj?.packageManager?.split('@')[0] === packageManager) {\n pmSource = 'packageManager-field';\n } else {\n pmSource = 'lockfile';\n }\n } else {\n pmSource = 'lockfile';\n }\n }\n // Invoke-time fallback: only when the dir has recognizable languages\n // (user-agent alone doesn't define a project: an empty dir gets no PM).\n if (!packageManager && languages.size > 0) {\n const ua = process.env.npm_config_user_agent;\n if (ua) {\n if (ua.startsWith('pnpm')) packageManager = 'pnpm';\n else if (ua.startsWith('yarn')) packageManager = 'yarn';\n else if (ua.startsWith('npm')) packageManager = 'npm';\n else if (ua.startsWith('bun')) packageManager = 'bun';\n pmSource = 'user-agent';\n }\n }\n // Conflict detection: invoke-time PM differs from project-state PM.\n {\n const ua = process.env.npm_config_user_agent;\n if (ua && packageManager) {\n const invokePm = ua.startsWith('pnpm')\n ? 'pnpm'\n : ua.startsWith('yarn')\n ? 'yarn'\n : ua.startsWith('npm')\n ? 'npm'\n : ua.startsWith('bun')\n ? 'bun'\n : null;\n if (invokePm && invokePm !== packageManager) pmConflict = true;\n }\n }\n\n // CI detection.\n let ci: string | null = null;\n if (existsSync(join(root, '.github', 'workflows'))) ci = 'github';\n else if (existsSync(join(root, '.gitlab-ci.yml'))) ci = 'gitlab';\n else if (existsSync(join(root, '.circleci', 'config.yml'))) ci = 'circleci';\n else if (existsSync(join(root, 'Jenkinsfile'))) ci = 'jenkins';\n\n // Existing AI instruction files (never clobbered).\n const aiFiles: [string, string][] = [\n ['AGENTS.md', 'agents-md'],\n ['CLAUDE.md', 'claude'],\n ['.cursorrules', 'cursor'],\n ['GEMINI.md', 'gemini'],\n ['.github/copilot-instructions.md', 'copilot'],\n ];\n const existingAiFiles: StackInfo['existingAiFiles'] = [];\n for (const [p, kind] of aiFiles) {\n const full = join(root, p);\n if (existsSync(full)) existingAiFiles.push({ path: p, kind });\n }\n // .cursor/rules/**/*.md: probe the dir (shallow).\n const cursorRulesDir = join(root, '.cursor', 'rules');\n try {\n if (existsSync(cursorRulesDir)) {\n const entries = readdirSync(cursorRulesDir);\n for (const e of entries) {\n if (e.endsWith('.md'))\n existingAiFiles.push({ path: `.cursor/rules/${e}`, kind: 'cursor-rules' });\n }\n }\n } catch {\n // ignore\n }\n\n return {\n languages: [...languages].sort(),\n monorepo,\n frameworks: [...frameworks].sort(),\n packageManager,\n pmSource,\n pmConflict,\n ci,\n existingAiFiles,\n };\n}\n\nfunction safeRead(file: string): string | undefined {\n try {\n return readFileSync(file, 'utf8');\n } catch {\n return undefined;\n }\n}\n\n/** True iff `dep` appears as a PEP 508 dependency name in `pyproject.toml`\n * text: handles PEP 621 `dependencies`/`optional-dependencies` list entries\n * (`\"fastapi\"`, `\"fastapi[all]>=0.100\"`) AND Poetry `[tool.poetry.dependencies]`\n * bare keys (`fastapi = \"^0.100\"`).\n *\n * The `before` boundary (line-start, quote, or whitespace) plus the `after`\n * PEP 508 boundary (whitespace, quote, version specifier `<>=!~`, extras `[`,\n * marker `;`, or end-of-string) prevent substring mismatches: `flask` will\n * NOT match `flask-restful`, and `fastapi` will NOT match `x-fastapi`.\n *\n * Never throws: `dep` is escaped, the regex is constructed from a controlled\n * template, and `String.prototype.test` is total on string input. Malformed\n * TOML simply yields no matches. */\nfunction pyprojectHasDep(raw: string, dep: string): boolean {\n const esc = dep.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const before = `(?:^|[\"'\\\\s])`;\n const after = `(?=\\\\s|[\"'<>=!~;]|\\\\[|$)`;\n return new RegExp(`${before}${esc}${after}`, 'm').test(raw);\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readFileSync","dirname","join","existsSync","readFileSync","dirname","join","join","mkdirSync","dirname","existsSync","readFileSync","out","existsSync","join","resolveAdapter","managedBlock","readFileSync","dirname","join","managedBlock","resolveAdapter","existsSync","join","i","j","existsSync","readFileSync","rmSync","writeFileSync","join","relative","atomicWriteFile","readWorkspaceMarker","resolveNoirCommand","join","existsSync","readFileSync","writeFileSync","relative","readWorkspaceMarker","resolveNoirCommand","atomicWriteFile","rmSync","existsSync","mkdirSync","readFileSync","renameSync","rmSync","basename","dirname","join","loadProjectInfo","paths","readProjectConfig","resolveNoirCommand","existsSync","readFileSync","join","mkdirSync","readFileSync","dirname","join","NOIR_DIR","join","NOIR_DIR","readFileSync","mkdirSync","dirname","existsSync","readdirSync","readFileSync","join","basename","dirname","join","paths","existsSync","loadProjectInfo","readProjectConfig","mkdirSync","rmSync","resolveNoirCommand","similarity","onDisk","regions","predicted","body","readFileSync","renameSync"]}
|