@noir-ai/create 1.13.0 → 1.14.0

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.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ancestors.ts","../src/manifest.ts","../src/merge.ts","../src/migrations/index.ts","../src/migrations/runner.ts","../src/scaffold.ts","../src/scaffold-version.ts","../src/writers.ts","../src/stack-detect.ts","../src/template.ts","../src/template-loader.ts"],"sourcesContent":["// Ancestor store for three-way managed-region merge (SP-D, widened).\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 } 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 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} from '@noir-ai/core';\nimport type { StackInfo } from './stack-detect.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 (S-T1 → S-T2 → S10): this table is a strict superset of\n * the artifacts `packages/cli/src/{init,sync}.ts` wrote pre-Slice-S. The cli\n * refactor (S-T2) replaced those ad-hoc writers with a call into `scaffold()`;\n * the byte-for-byte output MUST stay equivalent for first-run init. S10 makes\n * the manifest HOST-PARAMETRIC: {@link buildManifest} now returns host-agnostic\n * entries + a {@link buildHostArtifacts} call that materializes 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/** S10: `HostTag` is now the SAME `HostId` enum the adapter registry uses\n * (re-exported so existing imports keep working). Pre-S10 this was the\n * literal `'claude'`; widening to `HostId` lets one manifest serve every host\n * 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 /** 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 /** 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. Added in S10 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};\n\n// --- named managed blocks ----------------------------------------------------\n\n/** Co-owned NOIR.md auto-brief region. Defined locally (not exported from\n * core) because core's keystone-K named 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// --- 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 noirMd: `${NOIR_DIR}/NOIR.md`,\n rulesMd: `${NOIR_DIR}/rules/RULES.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 * 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 * S10 structure: the manifest is now `[...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 (see S-T1 report for the full table):\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 * - 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 path: P.envExample,\n mode: 'skipIfExists',\n template: 'env.example.tmpl',\n description: '.noir/.env placeholder (gitignored; copy + fill real tokens)',\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 path: P.rulesMd,\n mode: 'skipIfExists',\n template: 'rules-seed.md.tmpl',\n description: 'AI working-rules seed',\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 (SP-D validation fix). 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 return entries.filter((e) => {\n if (e.path === '.npmignore' || e.path === '.prettierignore') return isJs;\n if (e.path === '.dockerignore') return hasDocker;\n return true;\n });\n}\n\n// ---------------------------------------------------------------------------\n// S10 — host-specific artifact generation. One entry point: `buildHostArtifacts`.\n// ---------------------------------------------------------------------------\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}\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 * 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`); other hosts use the\n * adapter directly.\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 ectx: EmitContext = { root: ctx.root };\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 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 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 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 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 (byte-identical parity\n // gate); other hosts use adapter.emitMcpConfig directly.\n const mcpAbs = adapter.mcpConfigPath?.(ectx) ?? join(ctx.root, '.mcp.json');\n const mcpRel = hostRel(mcpAbs, ctx.root);\n if (host === 'claude') {\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(ectx, {\n transport: ctx.transport,\n command: ctx.command,\n ...(ctx.url !== undefined ? { url: ctx.url } : {}),\n })}\\n`;\n entries.push({\n path: mcpRel,\n mode: 'regenerate',\n host,\n content: mcpContent,\n description: `${host} MCP server pointer`,\n });\n }\n\n // 4. C3 SessionStart hook bootstrap (claude only). Three artifacts, three\n // ownerships (research-validated \"both + split\" design):\n // a. `.claude/settings.local.json` SessionStart entry — user-owned,\n // written ONCE via mergeJson (init/create only, deduped by command\n // substring). NEVER re-written by sync; preserves permissions/env.\n // b. `.noir/hooks/noir-session-start.mjs` — Noir-owned runner,\n // regenerate (init + sync), emits additionalContext from router.md.\n // c. `.noir/router.md` — co-owned mutable router contract, managedBlock\n // (init + sync), user edits outside markers survive.\n if (host === 'claude') {\n const HOOK_DEDUP = 'noir-session-start';\n const hookEntry = {\n hooks: {\n SessionStart: [\n {\n hooks: [\n {\n type: 'command',\n command: `\"${ctx.root}/.noir/hooks/noir-session-start.mjs\"`,\n },\n ],\n },\n ],\n },\n };\n entries.push({\n path: '.claude/settings.local.json',\n mode: 'mergeJson',\n host,\n content: JSON.stringify(hookEntry, null, 2),\n dedupSubstring: HOOK_DEDUP,\n description: 'SessionStart hook entry (user-owned, written once)',\n });\n entries.push({\n path: '.noir/hooks/noir-session-start.mjs',\n mode: 'regenerate',\n host,\n content: SESSION_START_HOOK_SCRIPT,\n description: 'SessionStart hook runner (Noir-owned, re-emitted)',\n });\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 }\n\n return entries;\n}\n\n/** The C3 SessionStart hook runner. Reads `.noir/router.md` (the co-owned\n * router contract) and emits it as `hookSpecificOutput.additionalContext` so\n * Claude Code wraps it in a system reminder at the start of every session —\n * deterministic, NOT in the skill-listing 1% budget. Kept small (<10k chars)\n * so Claude never file-izes it. */\nconst SESSION_START_HOOK_SCRIPT = `#!/usr/bin/env node\n// Noir C3 SessionStart hook — injects the skill router contract at session start.\n// The mutable contract lives in .noir/router.md (a Noir managed block, so user\n// edits outside the markers survive noir sync). This script is a pure runner.\nimport { readFileSync, existsSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst ROOT = join(HERE, '..', '..');\nconst ROUTER = join(ROOT, '.noir', 'router.md');\n\nfunction main() {\n if (!existsSync(ROUTER)) {\n // No router contract (project not initialized / router removed) — silent.\n process.stdout.write(JSON.stringify({ hookSpecificOutput: {} }));\n process.exit(0);\n }\n const contract = readFileSync(ROUTER, 'utf8').trim();\n process.stdout.write(\n JSON.stringify({ hookSpecificOutput: { additionalContext: contract } }),\n );\n}\n\nmain();\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","// SP-D follow-up — 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, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { 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 * At v1.0.0 there are no real migrations (the package ships fresh, so every\n * install starts at `CURRENT_SCAFFOLD_VERSION`). The registry is the\n * deliverable: it proves the runner end-to-end and gives the next contributor\n * a copy-pasteable template. Add the first real entry here when a template or\n * manifest change merits a `1.0.0 → 1.1.0` step.\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 */\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`. Safe to remove once a real migration lands. */\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\nexport const MIGRATIONS: readonly MigrationScript[] = [synthetic];\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 the spec locks\n * in (S-OQ2) — 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","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 } = {},\n): MigrationResult & { from: string | null; to: string; ran: string[] } {\n const window = pickWindow(from ?? '0.0.0', to, MIGRATIONS);\n const ctx: MigrationContext = { root, dryRun: opts.dryRun === true };\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 type ManagedBlock,\n paths,\n readManagedBlock,\n resolveNoirCommand,\n sha256Hex12,\n} from '@noir-ai/core';\nimport { readAncestors, writeAncestors } from './ancestors.js';\nimport {\n type BuildManifestContext,\n buildManifest,\n type HostTag,\n type ManifestEntry,\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 { loadTemplate } from './template-loader.js';\nimport {\n buildRegion,\n managedBlock,\n managedBlocks,\n mergeJson,\n predictManagedBlock,\n predictManagedBlocks,\n regenerate,\n skipIfExists,\n type WriteMode,\n} from './writers.js';\n\n/**\n * Scaffold orchestrator. One function the cli (S-T2) 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 before re-emitting, and emit only\n * regenerate + managedBlock (skipIfExists left alone). Only meaningful\n * with mode `'init'`. */\n upgrade?: boolean;\n /** SP-A: 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 /** SP-C: 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 /** SP-C: 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 shipped with SP-H (`managedBlocks` + `mergeManagedRegion` per block). */\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 /** SP-D: repo-relative `regenerate` paths skipped because byte-identical to\n * the template (content-hash dedup — no rewrite). */\n identical: string[];\n /** SP-A: 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 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}\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/** SP-C — 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 SP-C 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/** SP-C — 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\nconst WRITER_BY_MODE: Record<WriteMode, 'all' | 'runtime'> = {\n // 'runtime' subset = regenerate + managedBlock (the always-safe-to-rewrite\n // entries). sync + init --upgrade emit only this subset; skipIfExists is\n // reserved for first-run init/create so user edits survive.\n // 'mergeJson' (C3 SessionStart hook) is 'all' — the settings entry is\n // written once at init/create; sync re-emits only the script + router\n // contract (regenerate + managedBlock), never the user-owned entry.\n regenerate: 'runtime',\n managedBlock: 'runtime',\n skipIfExists: 'all',\n mergeJson: 'all',\n};\n\n/**\n * Refuse to scaffold when `root` is — or is inside — a `.noir/` directory.\n * (SP-A) 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\nexport async function scaffold(opts: ScaffoldOptions): Promise<ScaffoldResult> {\n // Root-safety (SP-A): 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 const host: HostTag = opts.host ?? 'claude';\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\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 (M4 skips synthetic migrations when fromVersion === null);\n // `--force` re-scaffolds without migrating. Both bypass this guard. sync is\n // 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 conflicts: [],\n noop: true,\n migrationsRan: [],\n migrationConflicts: [],\n stack,\n projectId,\n fromVersion,\n toVersion: CURRENT_SCAFFOLD_VERSION,\n host,\n };\n }\n\n // 4. Migrations (only when explicitly upgrading). M4: a fresh project\n // (`fromVersion === null`) has NO prior stamp → nothing to migrate. Skip\n // entirely so `noir init --upgrade` on a never-initialized tree doesn't\n // report a synthetic no-op `1.0.0→1.0.0` step.\n const migrationsRan: string[] = [];\n const migrationConflicts: string[] = [];\n if (opts.mode === 'init' && opts.upgrade === true && fromVersion !== null) {\n const m = runMigrations(opts.root, fromVersion, CURRENT_SCAFFOLD_VERSION, {\n dryRun: opts.dryRun === true,\n });\n migrationsRan.push(...m.ran);\n migrationConflicts.push(...m.conflicts);\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 });\n const emitRuntimeOnly = opts.mode === 'sync' || (opts.mode === 'init' && opts.upgrade === true);\n const vars: BuildManifestContext = {\n root: opts.root,\n projectId,\n host,\n transport,\n url: opts.url,\n command,\n };\n\n const written: string[] = [];\n const skipped: string[] = [];\n const identical: string[] = [];\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, emitRuntimeOnly);\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 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 SP-D 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 (entry.mode === 'skipIfExists' && existsSync(abs)) skipped.push(entry.path);\n else written.push(entry.path);\n continue;\n }\n const body = renderEntry(entry, vars);\n if (entry.mode === 'regenerate') {\n const out = await writeRegenerateWithConflict(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 (pre-Slice-S) .noir/NOIR.md is a whole-file auto-brief\n // with NO managed markers. The normal path would treat the old brief\n // as user content and append a SECOND managed 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. (Pre-Slice-S 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 SP-D\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 // C3 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 const out = skipIfExists(abs, body);\n if (out.written) written.push(entry.path);\n else skipped.push(entry.path);\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.\n if ((opts.mode === 'init' || opts.mode === 'create') && !opts.dryRun) {\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 conflicts: conflictRecords,\n noop: false,\n migrationsRan,\n migrationConflicts,\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/** SP-D — 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 v1.2 behavior (write the merged-with-markers bytes +\n * the SP-D 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/**\n * SP-C — write a `regenerate` file, honoring conflict resolution when the\n * target already exists and DIFFERS from the proposed bytes. Identical bytes\n * (or a missing file) write straight through (content-hash dedup is a deferred\n * slice — identical still \"writes\" today to keep sync/--upgrade byte-stable).\n * Resolution:\n * - `replace` — overwrite (the historical default).\n * - `preserve`/`cancel` — keep the user's file; report skipped.\n * - `rename` — move the user's file to `<path>.local`, write template.\n * - `duplicate` — write the template to `<path>.noir`, keep the user's.\n * Returns the repo-relative paths to record as written / skipped.\n */\nasync function writeRegenerateWithConflict(\n abs: string,\n relPath: string,\n proposed: string,\n opts: ScaffoldOptions,\n internals: {\n /** Per-run memory keyed by artifact CLASS (`'regenerate'`) — populated when\n * a resolver 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 },\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: a regenerate file's class shares one decision\n // across the run (so a `noir init --upgrade` over N pointers → 1 prompt).\n const MODE: NonNullable<ConflictContext['mode']> = '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 = opts.conflictPolicy === 'preserve' ? 'preserve' : '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 SP-C exists to prevent. uniqueAside 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 emitRuntimeOnly: boolean,\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 (emitRuntimeOnly && WRITER_BY_MODE[entry.mode] !== 'runtime') 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 (pre-Slice-S) 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 { 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. */\nexport const CURRENT_SCAFFOLD_VERSION = '1.0.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 * N2: 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 {\n closeSync,\n existsSync,\n openSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n writeSync,\n} from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport type { ManagedBlock } from '@noir-ai/core';\nimport { stripManagedBlock, writeManagedRegion } from '@noir-ai/core';\n\n/**\n * The three-mode writer — generalizes keystone-K's `writeManagedRegion` into\n * the declarative dispatch the scaffold manifest drives. Each mode maps 1:1 to\n * an artifact class in the spec §4.5 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 * keystone-K'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 (S-T2) 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. */\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 // M1: 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 keystone-K'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 *\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 keystone-K `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 writeFileSync(absPath, next, 'utf8');\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 * S-T2 relies on for a diff-free refactor. */\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 NOT created (orchestrator's job). */\nexport function skipIfExists(absPath: string, content: string): WriteOutcome {\n if (existsSync(absPath)) {\n return { path: absPath, mode: 'skipIfExists', written: false };\n }\n writeFileSync(absPath, content, 'utf8');\n return { path: absPath, mode: 'skipIfExists', written: true };\n}\n\n/**\n * Merge-aware JSON write (C3 SessionStart hook). `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): 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);\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/** 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. */\nfunction deepMergePreservingHooks(\n existing: Record<string, unknown>,\n patch: Record<string, unknown>,\n dedupSubstring?: string,\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 // 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).\n const alreadyRegistered =\n dedupSubstring !== undefined &&\n existingEntries.some(\n (x) => (x as { command?: string })?.command?.includes(dedupSubstring) ?? false,\n );\n const toAdd = alreadyRegistered ? [] : patchEntries;\n mergedHooks[event] = [...existingEntries, ...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, 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 (spec §4.5):\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 * c4-project-discovery S1: 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 * c4-project-discovery S3: detected CI. `github`/`gitlab`/`circleci`/\n * `jenkins`/`none`.\n */\n ci: string | null;\n /**\n * c4-project-discovery S2: 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 // M3: 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 // c4-project-discovery S1: 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 // c4-project-discovery S3: 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 // c4-project-discovery S2: existing AI instruction files (never clobbered).\n // c4-project-discovery S2: 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","/**\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 file at `packages/create/dist/template-loader.js` →\n * `../templates/<name>` (same relative offset — `dist/` and `src/` are both\n * direct children of the package root, so `../templates` lands 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"],"mappings":"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","names":["existsSync","join","i","j","existsSync","readFileSync","writeFileSync","join","join","existsSync","readFileSync","writeFileSync","existsSync","mkdirSync","readFileSync","renameSync","rmSync","basename","dirname","join","paths","mkdirSync","readFileSync","dirname","join","NOIR_DIR","existsSync","readFileSync","writeFileSync","dirname","join","managedBlock","join","NOIR_DIR","readFileSync","mkdirSync","dirname","existsSync","readFileSync","join","readFileSync","dirname","join","basename","dirname","mkdirSync","rmSync","paths","similarity","join","existsSync","managedBlock","readFileSync","renameSync"]}
1
+ {"version":3,"sources":["../src/ancestors.ts","../src/manifest.ts","../src/merge.ts","../src/migrations/index.ts","../src/migrations/runner.ts","../src/scaffold.ts","../src/scaffold-version.ts","../src/writers.ts","../src/stack-detect.ts","../src/template.ts","../src/template-loader.ts"],"sourcesContent":["// Ancestor store for three-way managed-region merge (SP-D, widened).\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 } 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 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} from '@noir-ai/core';\nimport type { StackInfo } from './stack-detect.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 (S-T1 → S-T2 → S10): this table is a strict superset of\n * the artifacts `packages/cli/src/{init,sync}.ts` wrote pre-Slice-S. The cli\n * refactor (S-T2) replaced those ad-hoc writers with a call into `scaffold()`;\n * the byte-for-byte output MUST stay equivalent for first-run init. S10 makes\n * the manifest HOST-PARAMETRIC: {@link buildManifest} now returns host-agnostic\n * entries + a {@link buildHostArtifacts} call that materializes 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/** S10: `HostTag` is now the SAME `HostId` enum the adapter registry uses\n * (re-exported so existing imports keep working). Pre-S10 this was the\n * literal `'claude'`; widening to `HostId` lets one manifest serve every host\n * 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 /** 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; an existing file's mode is never changed — the\n * `skipIfExists` contract means an existing file is not even opened. The\n * orchestrator forwards this to the writer and reports it on the result as\n * {@link ScaffoldResult.fileModes}. Ignored by every other write mode, whose\n * targets are regenerated rather 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. Added in S10 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};\n\n// --- named managed blocks ----------------------------------------------------\n\n/** Co-owned NOIR.md auto-brief region. Defined locally (not exported from\n * core) because core's keystone-K named 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 (slice E, spec §9). Defined locally\n * for the same reason as {@link BRIEF_BLOCK}: core's keystone-K named\n * instances 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// --- 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 * 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 * S10 structure: the manifest is now `[...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 (see S-T1 report for the full table):\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 path: P.envExample,\n mode: 'skipIfExists',\n template: 'env.example.tmpl',\n description: '.noir/.env.example committable documentation (never loaded)',\n },\n {\n // §8.1: 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 (set on creation only — skipIfExists never\n // touches an existing file, so a user's own chmod stands).\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 path: P.rulesMd,\n mode: 'skipIfExists',\n template: 'rules-seed.md.tmpl',\n description: 'AI working-rules seed',\n },\n {\n // Slice E (spec §9): 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 path: P.readme,\n mode: 'managedBlock',\n block: README_BLOCK,\n template: 'noir-readme.md.tmpl',\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 (SP-D validation fix). 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 return entries.filter((e) => {\n if (e.path === '.npmignore' || e.path === '.prettierignore') return isJs;\n if (e.path === '.dockerignore') return hasDocker;\n return true;\n });\n}\n\n// ---------------------------------------------------------------------------\n// S10 — host-specific artifact generation. One entry point: `buildHostArtifacts`.\n// ---------------------------------------------------------------------------\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}\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 * 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`); other hosts use the\n * adapter directly.\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 ectx: EmitContext = { root: ctx.root };\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 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 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 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 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 (byte-identical parity\n // gate); other hosts use adapter.emitMcpConfig directly.\n const mcpAbs = adapter.mcpConfigPath?.(ectx) ?? join(ctx.root, '.mcp.json');\n const mcpRel = hostRel(mcpAbs, ctx.root);\n if (host === 'claude') {\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(ectx, {\n transport: ctx.transport,\n command: ctx.command,\n ...(ctx.url !== undefined ? { url: ctx.url } : {}),\n })}\\n`;\n entries.push({\n path: mcpRel,\n mode: 'regenerate',\n host,\n content: mcpContent,\n description: `${host} MCP server pointer`,\n });\n }\n\n // 4. C3 SessionStart hook bootstrap (claude only). Three artifacts, three\n // ownerships (research-validated \"both + split\" design):\n // a. `.claude/settings.local.json` SessionStart entry — user-owned,\n // written ONCE via mergeJson (init/create only, deduped by command\n // substring). NEVER re-written by sync or `init --upgrade` (a\n // re-emit would resurrect a hook the user removed); preserves\n // permissions/env.\n // b. `.noir/hooks/noir-session-start.mjs` — Noir-owned runner,\n // regenerate (init + sync), emits additionalContext from router.md.\n // c. `.noir/router.md` — co-owned mutable router contract, managedBlock\n // (init + sync), user edits outside markers survive.\n if (host === 'claude') {\n const HOOK_DEDUP = 'noir-session-start';\n const hookEntry = {\n hooks: {\n SessionStart: [\n {\n hooks: [\n {\n type: 'command',\n command: `\"${ctx.root}/.noir/hooks/noir-session-start.mjs\"`,\n },\n ],\n },\n ],\n },\n };\n entries.push({\n path: '.claude/settings.local.json',\n mode: 'mergeJson',\n host,\n content: JSON.stringify(hookEntry, null, 2),\n dedupSubstring: HOOK_DEDUP,\n description: 'SessionStart hook entry (user-owned, written once)',\n });\n entries.push({\n path: '.noir/hooks/noir-session-start.mjs',\n mode: 'regenerate',\n host,\n content: SESSION_START_HOOK_SCRIPT,\n description: 'SessionStart hook runner (Noir-owned, re-emitted)',\n });\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 }\n\n return entries;\n}\n\n/** The C3 SessionStart hook runner. Reads `.noir/router.md` (the co-owned\n * router contract) and emits it as `hookSpecificOutput.additionalContext` so\n * Claude Code wraps it in a system reminder at the start of every session —\n * deterministic, NOT in the skill-listing 1% budget. Kept small (<10k chars)\n * so Claude never file-izes it. */\nconst SESSION_START_HOOK_SCRIPT = `#!/usr/bin/env node\n// Noir C3 SessionStart hook — injects the skill router contract at session start.\n// The mutable contract lives in .noir/router.md (a Noir managed block, so user\n// edits outside the markers survive noir sync). This script is a pure runner.\nimport { readFileSync, existsSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst ROOT = join(HERE, '..', '..');\nconst ROUTER = join(ROOT, '.noir', 'router.md');\n\nfunction main() {\n if (!existsSync(ROUTER)) {\n // No router contract (project not initialized / router removed) — silent.\n process.stdout.write(JSON.stringify({ hookSpecificOutput: {} }));\n process.exit(0);\n }\n const contract = readFileSync(ROUTER, 'utf8').trim();\n process.stdout.write(\n JSON.stringify({ hookSpecificOutput: { additionalContext: contract } }),\n );\n}\n\nmain();\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","// SP-D follow-up — 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, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { 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.1.0`; a project stamped at an older version\n * migrates through every entry whose window covers it. Two entries ship today:\n * the synthetic `1.0.0 → 1.0.0` runner-proof, and the first REAL migration,\n * `1.0.0 → 1.1.0`, which performs the transformation `skipIfExists` cannot.\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 (spec §11.2) --------------------\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 slice would\n * therefore never learn where secrets belong. Everything else this slice adds\n * is *creation* (§11.1's `skipIfExists` backfill) or *managed blocks*\n * (`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/** 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[] = [synthetic, envPointer];\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 the spec locks\n * in (S-OQ2) — 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","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 } = {},\n): MigrationResult & { from: string | null; to: string; ran: string[] } {\n const window = pickWindow(from ?? '0.0.0', to, MIGRATIONS);\n const ctx: MigrationContext = { root, dryRun: opts.dryRun === true };\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 type ManagedBlock,\n paths,\n readManagedBlock,\n resolveNoirCommand,\n sha256Hex12,\n} from '@noir-ai/core';\nimport { readAncestors, writeAncestors } from './ancestors.js';\nimport {\n type BuildManifestContext,\n buildManifest,\n type HostTag,\n type ManifestEntry,\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 { loadTemplate } from './template-loader.js';\nimport {\n buildRegion,\n managedBlock,\n managedBlocks,\n mergeJson,\n predictManagedBlock,\n predictManagedBlocks,\n regenerate,\n skipIfExists,\n type WriteMode,\n} from './writers.js';\n\n/**\n * Scaffold orchestrator. One function the cli (S-T2) 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 /** SP-A: 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 /** SP-C: 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 /** SP-C: 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 shipped with SP-H (`managedBlocks` + `mergeManagedRegion` per block). */\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 /** SP-D: repo-relative `regenerate` paths skipped because byte-identical to\n * the template (content-hash dedup — no rewrite). */\n identical: string[];\n /** SP-A: 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 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}\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/** SP-C — 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 SP-C 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/** SP-C — 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 creates only when the file is ABSENT — so an upgrade\n * backfills a seed added to the manifest after a project was initialized\n * while remaining unable to touch a file the user owns (spec §11.1).\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 * (SP-A) 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\nexport async function scaffold(opts: ScaffoldOptions): Promise<ScaffoldResult> {\n // Root-safety (SP-A): 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 const host: HostTag = opts.host ?? 'claude';\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\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 (M4 skips synthetic migrations when fromVersion === null);\n // `--force` re-scaffolds without migrating. Both bypass this guard. sync is\n // 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 conflicts: [],\n fileModes: {},\n noop: true,\n migrationsRan: [],\n migrationConflicts: [],\n stack,\n projectId,\n fromVersion,\n toVersion: CURRENT_SCAFFOLD_VERSION,\n host,\n };\n }\n\n // 4. Migrations (only when explicitly upgrading). M4: a fresh project\n // (`fromVersion === null`) has NO prior stamp → nothing to migrate. Skip\n // entirely so `noir init --upgrade` on a never-initialized tree reports\n // no migration steps at all.\n const migrationsRan: string[] = [];\n const migrationConflicts: string[] = [];\n if (opts.mode === 'init' && opts.upgrade === true && fromVersion !== null) {\n const m = runMigrations(opts.root, fromVersion, CURRENT_SCAFFOLD_VERSION, {\n dryRun: opts.dryRun === true,\n });\n migrationsRan.push(...m.ran);\n migrationConflicts.push(...m.conflicts);\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 });\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 §1.6 regression, where\n // `.noir/.env.example` was reachable by no command at all (spec §11.1). The\n // writer never opens an existing file, so a user's own seed, and its mode,\n // cannot be touched by this. `--upgrade` still excludes `mergeJson` — see\n // {@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 };\n\n const written: string[] = [];\n const skipped: string[] = [];\n const identical: 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 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 SP-D 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 (entry.mode === 'skipIfExists') {\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 } else {\n written.push(entry.path);\n }\n continue;\n }\n const body = renderEntry(entry, vars);\n if (entry.mode === 'regenerate') {\n const out = await writeRegenerateWithConflict(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 (pre-Slice-S) .noir/NOIR.md is a whole-file auto-brief\n // with NO managed markers. The normal path would treat the old brief\n // as user content and append a SECOND managed 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. (Pre-Slice-S 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 SP-D\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 // C3 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 // `entry.fileMode` (slice E) rides along so a created `.noir/.env` is\n // 0600 from 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 }\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 conflicts: conflictRecords,\n fileModes,\n noop: false,\n migrationsRan,\n migrationConflicts,\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/** SP-D — 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 v1.2 behavior (write the merged-with-markers bytes +\n * the SP-D 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/**\n * SP-C — write a `regenerate` file, honoring conflict resolution when the\n * target already exists and DIFFERS from the proposed bytes. Identical bytes\n * (or a missing file) write straight through (content-hash dedup is a deferred\n * slice — identical still \"writes\" today to keep sync/--upgrade byte-stable).\n * Resolution:\n * - `replace` — overwrite (the historical default).\n * - `preserve`/`cancel` — keep the user's file; report skipped.\n * - `rename` — move the user's file to `<path>.local`, write template.\n * - `duplicate` — write the template to `<path>.noir`, keep the user's.\n * Returns the repo-relative paths to record as written / skipped.\n */\nasync function writeRegenerateWithConflict(\n abs: string,\n relPath: string,\n proposed: string,\n opts: ScaffoldOptions,\n internals: {\n /** Per-run memory keyed by artifact CLASS (`'regenerate'`) — populated when\n * a resolver 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 },\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: a regenerate file's class shares one decision\n // across the run (so a `noir init --upgrade` over N pointers → 1 prompt).\n const MODE: NonNullable<ConflictContext['mode']> = '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 = opts.conflictPolicy === 'preserve' ? 'preserve' : '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 SP-C exists to prevent. uniqueAside 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 (pre-Slice-S) 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 { 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. */\nexport const CURRENT_SCAFFOLD_VERSION = '1.1.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 * N2: 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 {\n closeSync,\n existsSync,\n openSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n writeSync,\n} from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport type { ManagedBlock } from '@noir-ai/core';\nimport { atomicWriteFile, stripManagedBlock, writeManagedRegion } from '@noir-ai/core';\n\n/**\n * The three-mode writer — generalizes keystone-K's `writeManagedRegion` into\n * the declarative dispatch the scaffold manifest drives. Each mode maps 1:1 to\n * an artifact class in the spec §4.5 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 * keystone-K'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 (S-T2) 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. */\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 // M1: 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 keystone-K'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 *\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 keystone-K `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 writeFileSync(absPath, next, 'utf8');\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 * S-T2 relies on for a diff-free refactor. */\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` (slice E, §8.1) 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). */\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/**\n * Merge-aware JSON write (C3 SessionStart hook). `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): 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);\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/** 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. */\nfunction deepMergePreservingHooks(\n existing: Record<string, unknown>,\n patch: Record<string, unknown>,\n dedupSubstring?: string,\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 // 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).\n const alreadyRegistered =\n dedupSubstring !== undefined &&\n existingEntries.some(\n (x) => (x as { command?: string })?.command?.includes(dedupSubstring) ?? false,\n );\n const toAdd = alreadyRegistered ? [] : patchEntries;\n mergedHooks[event] = [...existingEntries, ...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, 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 (spec §4.5):\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 * c4-project-discovery S1: 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 * c4-project-discovery S3: detected CI. `github`/`gitlab`/`circleci`/\n * `jenkins`/`none`.\n */\n ci: string | null;\n /**\n * c4-project-discovery S2: 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 // M3: 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 // c4-project-discovery S1: 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 // c4-project-discovery S3: 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 // c4-project-discovery S2: existing AI instruction files (never clobbered).\n // c4-project-discovery S2: 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","/**\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 file at `packages/create/dist/template-loader.js` →\n * `../templates/<name>` (same relative offset — `dist/` and `src/` are both\n * direct children of the package root, so `../templates` lands 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"],"mappings":"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","names":["existsSync","join","i","j","existsSync","readFileSync","writeFileSync","join","join","existsSync","readFileSync","writeFileSync","existsSync","mkdirSync","readFileSync","renameSync","rmSync","basename","dirname","join","paths","mkdirSync","readFileSync","dirname","join","NOIR_DIR","existsSync","readFileSync","writeFileSync","dirname","join","managedBlock","join","NOIR_DIR","readFileSync","mkdirSync","dirname","existsSync","readFileSync","join","readFileSync","dirname","join","basename","dirname","mkdirSync","rmSync","paths","similarity","join","existsSync","managedBlock","readFileSync","renameSync"]}