@noir-ai/create 1.3.0-beta.2 → 1.3.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +34 -30
- package/dist/index.js +37 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,36 @@ declare function readAncestors(root: string): Record<string, string>;
|
|
|
11
11
|
* plain write (no tmp) is fine. */
|
|
12
12
|
declare function writeAncestors(root: string, map: Record<string, string>): void;
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* READ-ONLY stack detection. Probes for well-known marker files under `root`
|
|
16
|
+
* and reports ONLY what is present — never assumes. The result feeds path
|
|
17
|
+
* adaptation (where to drop `.claude/` vs `.cursor/` etc., future), ignore-file
|
|
18
|
+
* selection, and the onboarding TUI's confirm step.
|
|
19
|
+
*
|
|
20
|
+
* Design rules (spec §4.5):
|
|
21
|
+
* - Never throws. A foreign/empty dir returns `{ languages: [], monorepo:
|
|
22
|
+
* false, frameworks: [], packageManager: null }`.
|
|
23
|
+
* - Never opens network, never parses code beyond a `package.json`/`pyproject`
|
|
24
|
+
* dependency list. Marker files + top-level manifests only.
|
|
25
|
+
* - Frameworks are reported only when both the marker file is present AND the
|
|
26
|
+
* framework's dependency is listed (avoids false positives from a stale
|
|
27
|
+
* `package.json`).
|
|
28
|
+
*/
|
|
29
|
+
interface StackInfo {
|
|
30
|
+
/** Lower-cased language ids found via marker files:
|
|
31
|
+
* `typescript` | `javascript` | `python` | `go` | `rust`. */
|
|
32
|
+
languages: string[];
|
|
33
|
+
/** True when a workspace manifest is present (pnpm-workspace, npm/yarn
|
|
34
|
+
* workspaces in package.json, turbo.json, nx.json). */
|
|
35
|
+
monorepo: boolean;
|
|
36
|
+
/** Lower-cased framework ids (e.g. `next`, `vite`, `express`, `fastapi`,
|
|
37
|
+
* `actix`). Empty when none detected. */
|
|
38
|
+
frameworks: string[];
|
|
39
|
+
/** `pnpm` | `npm` | `yarn` when a lockfile is present, else null. */
|
|
40
|
+
packageManager: string | null;
|
|
41
|
+
}
|
|
42
|
+
declare function detectStack(root: string): StackInfo;
|
|
43
|
+
|
|
14
44
|
/**
|
|
15
45
|
* The three-mode writer — generalizes keystone-K's `writeManagedRegion` into
|
|
16
46
|
* the declarative dispatch the scaffold manifest drives. Each mode maps 1:1 to
|
|
@@ -132,6 +162,10 @@ type BuildManifestContext = {
|
|
|
132
162
|
transport: 'stdio' | 'streamable-http';
|
|
133
163
|
/** Required when transport is `streamable-http`. */
|
|
134
164
|
url?: string;
|
|
165
|
+
/** Detected stack — drives stack-aware ignore emission (.npmignore /
|
|
166
|
+
* .prettierignore only for JS; .dockerignore only when a Dockerfile is
|
|
167
|
+
* present; an unknown/empty stack ⇒ all four, for backward compat). */
|
|
168
|
+
stack?: StackInfo;
|
|
135
169
|
};
|
|
136
170
|
/** Co-owned NOIR.md auto-brief region. Defined locally (not exported from
|
|
137
171
|
* core) because core's keystone-K named instances cover only the three
|
|
@@ -323,36 +357,6 @@ declare function applyWithConflict(ours: string, theirs: string, path: string):
|
|
|
323
357
|
conflicted: boolean;
|
|
324
358
|
};
|
|
325
359
|
|
|
326
|
-
/**
|
|
327
|
-
* READ-ONLY stack detection. Probes for well-known marker files under `root`
|
|
328
|
-
* and reports ONLY what is present — never assumes. The result feeds path
|
|
329
|
-
* adaptation (where to drop `.claude/` vs `.cursor/` etc., future), ignore-file
|
|
330
|
-
* selection, and the onboarding TUI's confirm step.
|
|
331
|
-
*
|
|
332
|
-
* Design rules (spec §4.5):
|
|
333
|
-
* - Never throws. A foreign/empty dir returns `{ languages: [], monorepo:
|
|
334
|
-
* false, frameworks: [], packageManager: null }`.
|
|
335
|
-
* - Never opens network, never parses code beyond a `package.json`/`pyproject`
|
|
336
|
-
* dependency list. Marker files + top-level manifests only.
|
|
337
|
-
* - Frameworks are reported only when both the marker file is present AND the
|
|
338
|
-
* framework's dependency is listed (avoids false positives from a stale
|
|
339
|
-
* `package.json`).
|
|
340
|
-
*/
|
|
341
|
-
interface StackInfo {
|
|
342
|
-
/** Lower-cased language ids found via marker files:
|
|
343
|
-
* `typescript` | `javascript` | `python` | `go` | `rust`. */
|
|
344
|
-
languages: string[];
|
|
345
|
-
/** True when a workspace manifest is present (pnpm-workspace, npm/yarn
|
|
346
|
-
* workspaces in package.json, turbo.json, nx.json). */
|
|
347
|
-
monorepo: boolean;
|
|
348
|
-
/** Lower-cased framework ids (e.g. `next`, `vite`, `express`, `fastapi`,
|
|
349
|
-
* `actix`). Empty when none detected. */
|
|
350
|
-
frameworks: string[];
|
|
351
|
-
/** `pnpm` | `npm` | `yarn` when a lockfile is present, else null. */
|
|
352
|
-
packageManager: string | null;
|
|
353
|
-
}
|
|
354
|
-
declare function detectStack(root: string): StackInfo;
|
|
355
|
-
|
|
356
360
|
/**
|
|
357
361
|
* Scaffold orchestrator. One function the cli (S-T2) calls for `noir init`,
|
|
358
362
|
* `noir create`, and `noir sync`; mode selects the manifest subset + how
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ function writeAncestors(root, map) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// src/manifest.ts
|
|
28
|
+
import { existsSync as existsSync2 } from "fs";
|
|
28
29
|
import { join as join2, relative } from "path";
|
|
29
30
|
import {
|
|
30
31
|
AGENTS_MD_FILENAME,
|
|
@@ -56,7 +57,7 @@ function buildManifest(ctx) {
|
|
|
56
57
|
return [...hostAgnosticEntries(ctx), ...buildHostArtifacts(resolveAdapter(ctx.host), ctx)];
|
|
57
58
|
}
|
|
58
59
|
function hostAgnosticEntries(ctx) {
|
|
59
|
-
|
|
60
|
+
const entries = [
|
|
60
61
|
{
|
|
61
62
|
path: P.projectId,
|
|
62
63
|
mode: "skipIfExists",
|
|
@@ -113,6 +114,14 @@ function hostAgnosticEntries(ctx) {
|
|
|
113
114
|
description: ".prettierignore noir managed block"
|
|
114
115
|
}
|
|
115
116
|
];
|
|
117
|
+
const isEmpty = (ctx.stack?.languages?.length ?? 0) === 0 && !ctx.stack?.packageManager;
|
|
118
|
+
const isJs = isEmpty || (ctx.stack?.languages?.some((l) => l === "typescript" || l === "javascript") ?? false) || ["npm", "pnpm", "yarn"].includes(ctx.stack?.packageManager ?? "");
|
|
119
|
+
const hasDocker = isEmpty || existsSync2(join2(ctx.root, "Dockerfile")) || existsSync2(join2(ctx.root, "docker-compose.yml")) || existsSync2(join2(ctx.root, "compose.yaml"));
|
|
120
|
+
return entries.filter((e) => {
|
|
121
|
+
if (e.path === ".npmignore" || e.path === ".prettierignore") return isJs;
|
|
122
|
+
if (e.path === ".dockerignore") return hasDocker;
|
|
123
|
+
return true;
|
|
124
|
+
});
|
|
116
125
|
}
|
|
117
126
|
function buildHostArtifacts(adapter, ctx) {
|
|
118
127
|
const ectx = { root: ctx.root };
|
|
@@ -286,7 +295,7 @@ function mergeThreeWay(base, ours, theirs) {
|
|
|
286
295
|
}
|
|
287
296
|
|
|
288
297
|
// src/migrations/index.ts
|
|
289
|
-
import { existsSync as
|
|
298
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
290
299
|
import { join as join3 } from "path";
|
|
291
300
|
|
|
292
301
|
// src/migrations/runner.ts
|
|
@@ -338,7 +347,7 @@ var synthetic = {
|
|
|
338
347
|
const result = { changed: [], conflicts: [], notes: [] };
|
|
339
348
|
if (process.env.NOIR_TEST_FORCE_CONFLICT === "1" && !ctx.dryRun) {
|
|
340
349
|
const file = join3(ctx.root, ".noir", "scaffold-version");
|
|
341
|
-
if (
|
|
350
|
+
if (existsSync3(file)) {
|
|
342
351
|
const prev = readFileSync2(file, "utf8");
|
|
343
352
|
const merged = applyInlineConflict(prev, "noir-scaffold=1.0.0\n", "ours", "theirs");
|
|
344
353
|
writeFileSync2(file, merged, "utf8");
|
|
@@ -365,7 +374,7 @@ function applyWithConflict(ours, theirs, path) {
|
|
|
365
374
|
}
|
|
366
375
|
|
|
367
376
|
// src/scaffold.ts
|
|
368
|
-
import { existsSync as
|
|
377
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync7, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
|
|
369
378
|
import { basename as basename2, dirname as dirname5, join as join8 } from "path";
|
|
370
379
|
import { createProjectId, paths as paths2, readManagedBlock } from "@noir-ai/core";
|
|
371
380
|
|
|
@@ -377,7 +386,7 @@ import { NOIR_DIR as NOIR_DIR2 } from "@noir-ai/core";
|
|
|
377
386
|
// src/writers.ts
|
|
378
387
|
import {
|
|
379
388
|
closeSync,
|
|
380
|
-
existsSync as
|
|
389
|
+
existsSync as existsSync4,
|
|
381
390
|
openSync,
|
|
382
391
|
readFileSync as readFileSync3,
|
|
383
392
|
renameSync,
|
|
@@ -450,7 +459,7 @@ ${block.end}
|
|
|
450
459
|
`;
|
|
451
460
|
}
|
|
452
461
|
function skipIfExists(absPath, content) {
|
|
453
|
-
if (
|
|
462
|
+
if (existsSync4(absPath)) {
|
|
454
463
|
return { path: absPath, mode: "skipIfExists", written: false };
|
|
455
464
|
}
|
|
456
465
|
writeFileSync3(absPath, content, "utf8");
|
|
@@ -487,7 +496,7 @@ function writeScaffoldVersion(root, version) {
|
|
|
487
496
|
}
|
|
488
497
|
|
|
489
498
|
// src/stack-detect.ts
|
|
490
|
-
import { existsSync as
|
|
499
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
491
500
|
import { join as join6 } from "path";
|
|
492
501
|
var NODE_FRAMEWORKS = [
|
|
493
502
|
["next", "next"],
|
|
@@ -526,10 +535,10 @@ function detectStack(root) {
|
|
|
526
535
|
let monorepo = false;
|
|
527
536
|
let packageManager = null;
|
|
528
537
|
const pjPath = join6(root, "package.json");
|
|
529
|
-
if (
|
|
538
|
+
if (existsSync5(pjPath)) {
|
|
530
539
|
const pj = readJson(pjPath);
|
|
531
540
|
if (pj) {
|
|
532
|
-
const hasTs = Boolean(pj.devDependencies?.typescript) ||
|
|
541
|
+
const hasTs = Boolean(pj.devDependencies?.typescript) || existsSync5(join6(root, "tsconfig.json"));
|
|
533
542
|
languages.add(hasTs ? "typescript" : "javascript");
|
|
534
543
|
const deps = /* @__PURE__ */ new Set([
|
|
535
544
|
...Object.keys(pj.dependencies ?? {}),
|
|
@@ -548,21 +557,21 @@ function detectStack(root) {
|
|
|
548
557
|
}
|
|
549
558
|
}
|
|
550
559
|
}
|
|
551
|
-
if (
|
|
560
|
+
if (existsSync5(join6(root, "pnpm-workspace.yaml"))) {
|
|
552
561
|
monorepo = true;
|
|
553
562
|
packageManager = packageManager ?? "pnpm";
|
|
554
563
|
}
|
|
555
|
-
if (
|
|
564
|
+
if (existsSync5(join6(root, "turbo.json")) || existsSync5(join6(root, "nx.json"))) {
|
|
556
565
|
monorepo = true;
|
|
557
566
|
}
|
|
558
567
|
if (!packageManager) {
|
|
559
|
-
if (
|
|
560
|
-
else if (
|
|
561
|
-
else if (
|
|
568
|
+
if (existsSync5(join6(root, "pnpm-lock.yaml"))) packageManager = "pnpm";
|
|
569
|
+
else if (existsSync5(join6(root, "yarn.lock"))) packageManager = "yarn";
|
|
570
|
+
else if (existsSync5(join6(root, "package-lock.json"))) packageManager = "npm";
|
|
562
571
|
}
|
|
563
|
-
if (
|
|
572
|
+
if (existsSync5(join6(root, "pyproject.toml")) || existsSync5(join6(root, "requirements.txt")) || existsSync5(join6(root, "Pipfile")) || existsSync5(join6(root, "setup.py"))) {
|
|
564
573
|
languages.add("python");
|
|
565
|
-
if (
|
|
574
|
+
if (existsSync5(join6(root, "pyproject.toml"))) {
|
|
566
575
|
const raw = safeRead(join6(root, "pyproject.toml"));
|
|
567
576
|
if (raw) {
|
|
568
577
|
for (const [dep, id] of PYTHON_FRAMEWORKS) {
|
|
@@ -571,11 +580,11 @@ function detectStack(root) {
|
|
|
571
580
|
}
|
|
572
581
|
}
|
|
573
582
|
}
|
|
574
|
-
if (
|
|
583
|
+
if (existsSync5(join6(root, "go.mod"))) {
|
|
575
584
|
languages.add("go");
|
|
576
585
|
packageManager = packageManager ?? "go-modules";
|
|
577
586
|
}
|
|
578
|
-
if (
|
|
587
|
+
if (existsSync5(join6(root, "Cargo.toml"))) {
|
|
579
588
|
languages.add("rust");
|
|
580
589
|
const raw = safeRead(join6(root, "Cargo.toml"));
|
|
581
590
|
if (raw) {
|
|
@@ -706,7 +715,14 @@ async function scaffold(opts) {
|
|
|
706
715
|
migrationsRan.push(...m.ran);
|
|
707
716
|
migrationConflicts.push(...m.conflicts);
|
|
708
717
|
}
|
|
709
|
-
const manifest = buildManifest({
|
|
718
|
+
const manifest = buildManifest({
|
|
719
|
+
root: opts.root,
|
|
720
|
+
projectId,
|
|
721
|
+
host,
|
|
722
|
+
transport,
|
|
723
|
+
url: opts.url,
|
|
724
|
+
stack
|
|
725
|
+
});
|
|
710
726
|
const emitRuntimeOnly = opts.mode === "sync" || opts.mode === "init" && opts.upgrade === true;
|
|
711
727
|
const vars = {
|
|
712
728
|
root: opts.root,
|
|
@@ -745,7 +761,7 @@ async function scaffold(opts) {
|
|
|
745
761
|
if (!opts.dryRun) mkdirSync3(dirname5(abs), { recursive: true });
|
|
746
762
|
for (const entry of entries) {
|
|
747
763
|
if (opts.dryRun) {
|
|
748
|
-
if (entry.mode === "skipIfExists" &&
|
|
764
|
+
if (entry.mode === "skipIfExists" && existsSync6(abs)) skipped.push(entry.path);
|
|
749
765
|
else written.push(entry.path);
|
|
750
766
|
continue;
|
|
751
767
|
}
|
|
@@ -869,7 +885,7 @@ function uniqueAside(abs, relPath, suffix) {
|
|
|
869
885
|
rel: `${relPath}${s}`
|
|
870
886
|
});
|
|
871
887
|
let candidate = make(suffix);
|
|
872
|
-
for (let n = 1;
|
|
888
|
+
for (let n = 1; existsSync6(candidate.abs); n++) candidate = make(`${suffix}.${n}`);
|
|
873
889
|
return candidate;
|
|
874
890
|
}
|
|
875
891
|
function groupApplicableByPath(manifest, host, emitRuntimeOnly) {
|
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":["// SP-D follow-up — ancestor store for three-way managed-region merge.\n//\n// Persists the last-emitted managed-region text per (file, block) so a later\n// `noir init`/`sync --merge` can three-way merge (base/ours/theirs) instead of\n// strip-replacing. Stored at `.noir/ancestors.json` as a flat map. Only written\n// when `ScaffoldOptions.mergeManagedRegions` is set (opt-in), so a default\n// scaffold run never creates it.\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. */\nexport function writeAncestors(root: string, map: Record<string, string>): void {\n mkdirSync(dirname(ancestorsPath(root)), { recursive: true });\n writeFileSync(ancestorsPath(root), `${JSON.stringify(map, null, 2)}\\n`, 'utf8');\n}\n","import { 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 { 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 — fix-wave I1 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}\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};\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 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). Fix-wave I1: {@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 return [\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.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}\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}\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 C3 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 per C3; 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 ...(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 return entries;\n}\n\n/** Convert an absolute path under `root` to a repo-relative POSIX string (the\n * manifest's path shape). Throws if `abs` is NOT under `root` so a future\n * adapter that returns a stray path fails loudly instead of producing a\n * malformed manifest entry. */\nfunction hostRel(abs: string, root: string): string {\n const rel = relative(root, abs);\n if (rel.length === 0 || rel.startsWith('..') || rel.startsWith('/')) {\n throw new Error(`buildHostArtifacts: path '${abs}' is not under root '${root}'`);\n }\n // Normalize any platform separators to POSIX (manifest paths are POSIX).\n return rel.replace(/\\\\/g, '/');\n}\n","// 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. */\nfunction 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\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 (`<<<<<<< ours` / `=======` / `>>>>>>> theirs`). Pure +\n * deterministic (no IO) so it's unit-testable.\n */\nexport function mergeThreeWay(base: string, ours: string, theirs: string): 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 out.push('<<<<<<< ours', ...oSeg, '=======', ...tSeg, '>>>>>>> theirs');\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 { createProjectId, type ManagedBlock, paths, readManagedBlock } 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 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. */\n onConflict?: (ctx: ConflictContext) => Promise<ConflictResolution> | ConflictResolution;\n /** SP-D follow-up: three-way merge managed regions (base/ours/theirs) using a\n * persisted ancestor snapshot (`.noir/ancestors.json`) instead of\n * strip-replace, so a hand-edit inside a `<!-- noir:* -->` region survives a\n * template update. Opt-in (default false ⇒ current strip-replace, no\n * ancestor file). Single-region managed files only (NOIR.md, ignores);\n * multi-region (CLAUDE.md) is a follow-up. */\n mergeManagedRegions?: 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}\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}\n\n/** SP-C — how to resolve a `regenerate` conflict. */\nexport type ConflictResolution = 'replace' | 'preserve' | 'rename' | 'duplicate' | 'cancel';\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 regenerate: 'runtime',\n managedBlock: 'runtime',\n skipIfExists: '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 (C1). sync requires a VALID existing id.\n const idFile = readProjectIdFile(opts.root);\n const projectId = resolveProjectId(opts, idFile);\n\n // C1: 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 // SP-D: ancestor map for three-way managed-region merge (opt-in). Read once;\n // written back at the end only when mergeManagedRegions is set.\n const ancestors = opts.mergeManagedRegions ? readAncestors(opts.root) : {};\n\n // SP-A — already-initialized guard: a bare `noir init`/`noir create` on a\n // project that already carries a .noir/scaffold-version stamp is a NO-OP,\n // not a silent re-emit (re-running init looked like it re-scaffolded, which\n // is what made the nested-`.noir` bug feel like \"init duplicates things\").\n // `--upgrade` is the explicit migrate+re-emit path; `--force` re-scaffolds\n // without migrating. Both bypass this guard. sync is unaffected (it requires\n // a valid project.id and emits the runtime subset only). dryRun returns the\n // no-op shape silently (no stderr) so `noir doctor`/CI previews stay clean.\n if (\n fromVersion !== null &&\n opts.upgrade !== true &&\n opts.force !== true &&\n (opts.mode === 'init' || opts.mode === 'create')\n ) {\n if (opts.dryRun !== true) {\n process.stderr.write(\n `Noir is already initialized in ${opts.root} (scaffold ${fromVersion}). No-op. Use \\`noir init --upgrade\\` to migrate, or \\`--force\\` to re-scaffold.\\n`,\n );\n }\n return {\n written: [],\n skipped: [],\n identical: [],\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 const manifest = buildManifest({ root: opts.root, projectId, host, transport, url: opts.url });\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 };\n\n const written: string[] = [];\n const skipped: string[] = [];\n const identical: string[] = [];\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 (I1). 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 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 regionText = opts.mergeManagedRegions\n ? mergeManagedRegion(abs, e.path, block, theirs, ancestors)\n : theirs;\n if (opts.mergeManagedRegions) ancestors[`${e.path}::${block.begin}`] = theirs;\n return { block, regionText };\n });\n managedBlocks(abs, regions);\n written.push(relPath);\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 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 // I2: 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 regionText = opts.mergeManagedRegions\n ? mergeManagedRegion(abs, entry.path, block, theirs, ancestors)\n : theirs;\n managedBlock(abs, block, regionText);\n if (opts.mergeManagedRegions) ancestors[`${entry.path}::${block.begin}`] = theirs;\n written.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 // SP-D: persist the ancestor map (only when three-way merge is opted in).\n if (opts.mergeManagedRegions && !opts.dryRun) {\n writeAncestors(opts.root, ancestors);\n }\n\n return {\n written,\n skipped,\n identical,\n noop: false,\n migrationsRan,\n migrationConflicts,\n stack,\n projectId,\n fromVersion,\n toVersion: CURRENT_SCAFFOLD_VERSION,\n host,\n };\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, inline markers are\n * written + a stderr note (never silently drops either side). */\nfunction mergeManagedRegion(\n abs: string,\n relPath: string,\n block: ManagedBlock,\n theirs: string,\n ancestors: Record<string, string>,\n): string {\n const base = ancestors[`${relPath}::${block.begin}`];\n if (base === undefined) return theirs; // no ancestor yet → strip-replace (first merge run)\n const ours = readManagedBlock(abs, block);\n if (ours === null) return theirs; // no existing region → fresh\n const res = mergeThreeWay(base, ours, theirs);\n if (res.conflict) {\n process.stderr.write(\n `noir: managed-region conflict in ${relPath} — wrote inline markers; resolve manually.\\n`,\n );\n }\n return res.merged;\n}\n\n/** Read the `.noir/project.id` stamp ONCE and classify it for BOTH id\n * resolution and the C1 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): 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 const resolution: ConflictResolution =\n opts.onConflict !== undefined\n ? await opts.onConflict({ relPath, existing, proposed })\n : opts.conflictPolicy === 'preserve'\n ? 'preserve'\n : 'replace';\n switch (resolution) {\n case 'replace':\n regenerate(abs, proposed);\n return { written: [relPath], skipped: [], identical: [] };\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 I2 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/** I2 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","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';\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 (I1): 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/** 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","import { existsSync, 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\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 return {\n languages: [...languages].sort(),\n monorepo,\n frameworks: [...frameworks].sort(),\n packageManager,\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":["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","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":["// SP-D follow-up — ancestor store for three-way managed-region merge.\n//\n// Persists the last-emitted managed-region text per (file, block) so a later\n// `noir init`/`sync --merge` can three-way merge (base/ours/theirs) instead of\n// strip-replacing. Stored at `.noir/ancestors.json` as a flat map. Only written\n// when `ScaffoldOptions.mergeManagedRegions` is set (opt-in), so a default\n// scaffold run never creates it.\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. */\nexport function writeAncestors(root: string, map: Record<string, string>): void {\n mkdirSync(dirname(ancestorsPath(root)), { recursive: true });\n writeFileSync(ancestorsPath(root), `${JSON.stringify(map, null, 2)}\\n`, '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 — fix-wave I1 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}\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 /** 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 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). Fix-wave I1: {@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.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}\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 C3 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 per C3; 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 ...(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 return entries;\n}\n\n/** Convert an absolute path under `root` to a repo-relative POSIX string (the\n * manifest's path shape). Throws if `abs` is NOT under `root` so a future\n * adapter that returns a stray path fails loudly instead of producing a\n * malformed manifest entry. */\nfunction hostRel(abs: string, root: string): string {\n const rel = relative(root, abs);\n if (rel.length === 0 || rel.startsWith('..') || rel.startsWith('/')) {\n throw new Error(`buildHostArtifacts: path '${abs}' is not under root '${root}'`);\n }\n // Normalize any platform separators to POSIX (manifest paths are POSIX).\n return rel.replace(/\\\\/g, '/');\n}\n","// 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. */\nfunction 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\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 (`<<<<<<< ours` / `=======` / `>>>>>>> theirs`). Pure +\n * deterministic (no IO) so it's unit-testable.\n */\nexport function mergeThreeWay(base: string, ours: string, theirs: string): 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 out.push('<<<<<<< ours', ...oSeg, '=======', ...tSeg, '>>>>>>> theirs');\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 { createProjectId, type ManagedBlock, paths, readManagedBlock } 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 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. */\n onConflict?: (ctx: ConflictContext) => Promise<ConflictResolution> | ConflictResolution;\n /** SP-D follow-up: three-way merge managed regions (base/ours/theirs) using a\n * persisted ancestor snapshot (`.noir/ancestors.json`) instead of\n * strip-replace, so a hand-edit inside a `<!-- noir:* -->` region survives a\n * template update. Opt-in (default false ⇒ current strip-replace, no\n * ancestor file). Single-region managed files only (NOIR.md, ignores);\n * multi-region (CLAUDE.md) is a follow-up. */\n mergeManagedRegions?: 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}\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}\n\n/** SP-C — how to resolve a `regenerate` conflict. */\nexport type ConflictResolution = 'replace' | 'preserve' | 'rename' | 'duplicate' | 'cancel';\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 regenerate: 'runtime',\n managedBlock: 'runtime',\n skipIfExists: '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 (C1). sync requires a VALID existing id.\n const idFile = readProjectIdFile(opts.root);\n const projectId = resolveProjectId(opts, idFile);\n\n // C1: 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 // SP-D: ancestor map for three-way managed-region merge (opt-in). Read once;\n // written back at the end only when mergeManagedRegions is set.\n const ancestors = opts.mergeManagedRegions ? readAncestors(opts.root) : {};\n\n // SP-A — already-initialized guard: a bare `noir init`/`noir create` on a\n // project that already carries a .noir/scaffold-version stamp is a NO-OP,\n // not a silent re-emit (re-running init looked like it re-scaffolded, which\n // is what made the nested-`.noir` bug feel like \"init duplicates things\").\n // `--upgrade` is the explicit migrate+re-emit path; `--force` re-scaffolds\n // without migrating. Both bypass this guard. sync is unaffected (it requires\n // a valid project.id and emits the runtime subset only). dryRun returns the\n // no-op shape silently (no stderr) so `noir doctor`/CI previews stay clean.\n if (\n fromVersion !== null &&\n opts.upgrade !== true &&\n opts.force !== true &&\n (opts.mode === 'init' || opts.mode === 'create')\n ) {\n if (opts.dryRun !== true) {\n process.stderr.write(\n `Noir is already initialized in ${opts.root} (scaffold ${fromVersion}). No-op. Use \\`noir init --upgrade\\` to migrate, or \\`--force\\` to re-scaffold.\\n`,\n );\n }\n return {\n written: [],\n skipped: [],\n identical: [],\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 const manifest = buildManifest({\n root: opts.root,\n projectId,\n host,\n transport,\n url: opts.url,\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 };\n\n const written: string[] = [];\n const skipped: string[] = [];\n const identical: string[] = [];\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 (I1). 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 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 regionText = opts.mergeManagedRegions\n ? mergeManagedRegion(abs, e.path, block, theirs, ancestors)\n : theirs;\n if (opts.mergeManagedRegions) ancestors[`${e.path}::${block.begin}`] = theirs;\n return { block, regionText };\n });\n managedBlocks(abs, regions);\n written.push(relPath);\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 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 // I2: 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 regionText = opts.mergeManagedRegions\n ? mergeManagedRegion(abs, entry.path, block, theirs, ancestors)\n : theirs;\n managedBlock(abs, block, regionText);\n if (opts.mergeManagedRegions) ancestors[`${entry.path}::${block.begin}`] = theirs;\n written.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 // SP-D: persist the ancestor map (only when three-way merge is opted in).\n if (opts.mergeManagedRegions && !opts.dryRun) {\n writeAncestors(opts.root, ancestors);\n }\n\n return {\n written,\n skipped,\n identical,\n noop: false,\n migrationsRan,\n migrationConflicts,\n stack,\n projectId,\n fromVersion,\n toVersion: CURRENT_SCAFFOLD_VERSION,\n host,\n };\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, inline markers are\n * written + a stderr note (never silently drops either side). */\nfunction mergeManagedRegion(\n abs: string,\n relPath: string,\n block: ManagedBlock,\n theirs: string,\n ancestors: Record<string, string>,\n): string {\n const base = ancestors[`${relPath}::${block.begin}`];\n if (base === undefined) return theirs; // no ancestor yet → strip-replace (first merge run)\n const ours = readManagedBlock(abs, block);\n if (ours === null) return theirs; // no existing region → fresh\n const res = mergeThreeWay(base, ours, theirs);\n if (res.conflict) {\n process.stderr.write(\n `noir: managed-region conflict in ${relPath} — wrote inline markers; resolve manually.\\n`,\n );\n }\n return res.merged;\n}\n\n/** Read the `.noir/project.id` stamp ONCE and classify it for BOTH id\n * resolution and the C1 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): 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 const resolution: ConflictResolution =\n opts.onConflict !== undefined\n ? await opts.onConflict({ relPath, existing, proposed })\n : opts.conflictPolicy === 'preserve'\n ? 'preserve'\n : 'replace';\n switch (resolution) {\n case 'replace':\n regenerate(abs, proposed);\n return { written: [relPath], skipped: [], identical: [] };\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 I2 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/** I2 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","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';\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 (I1): 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/** 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","import { existsSync, 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\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 return {\n languages: [...languages].sort(),\n monorepo,\n frameworks: [...frameworks].sort(),\n packageManager,\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","join","existsSync","managedBlock","readFileSync","renameSync"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noir-ai/create",
|
|
3
|
-
"version": "1.3.0-beta.
|
|
3
|
+
"version": "1.3.0-beta.4",
|
|
4
4
|
"description": "Noir scaffold engine — the declarative three-mode writer, manifest, templates, stack-detect, and migrations that power `noir init`/`create`/`sync`.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "agaaaptr",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"templates"
|
|
47
47
|
],
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@noir-ai/adapters": "1.3.0-beta.
|
|
50
|
-
"@noir-ai/core": "1.3.0-beta.
|
|
49
|
+
"@noir-ai/adapters": "1.3.0-beta.4",
|
|
50
|
+
"@noir-ai/core": "1.3.0-beta.4"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "^26.1.1"
|