@noir-ai/create 1.19.0-beta.1 → 1.19.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -30,19 +30,215 @@ function writeAncestors(root, map) {
30
30
  writeFileSync(path, next, "utf8");
31
31
  }
32
32
 
33
+ // src/hooks.ts
34
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
35
+ import { dirname as dirname3, join as join3 } from "path";
36
+ import { resolveAdapter } from "@noir-ai/adapters";
37
+ import { resolveNoirCommand } from "@noir-ai/core";
38
+
39
+ // src/writers.ts
40
+ import {
41
+ closeSync,
42
+ existsSync as existsSync2,
43
+ openSync,
44
+ readFileSync as readFileSync2,
45
+ renameSync,
46
+ rmSync,
47
+ writeSync
48
+ } from "fs";
49
+ import { basename, dirname as dirname2, join as join2 } from "path";
50
+ import { atomicWriteFile, stripManagedBlock, writeManagedRegion } from "@noir-ai/core";
51
+ import { ensureOwnerOnly } from "@noir-ai/core";
52
+ function regenerate(absPath, content) {
53
+ const dir = dirname2(absPath);
54
+ const tmp = join2(
55
+ dir,
56
+ `.${basename(absPath)}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`
57
+ );
58
+ let fd;
59
+ try {
60
+ fd = openSync(tmp, "w");
61
+ writeSync(fd, content, 0, "utf8");
62
+ closeSync(fd);
63
+ fd = void 0;
64
+ renameSync(tmp, absPath);
65
+ } finally {
66
+ if (fd !== void 0) {
67
+ try {
68
+ closeSync(fd);
69
+ } catch {
70
+ }
71
+ }
72
+ try {
73
+ rmSync(tmp, { force: true });
74
+ } catch {
75
+ }
76
+ }
77
+ return { path: absPath, mode: "regenerate", written: true };
78
+ }
79
+ function managedBlock(absPath, block, regionText) {
80
+ writeManagedRegion(absPath, block, regionText);
81
+ return { path: absPath, mode: "managedBlock", written: true };
82
+ }
83
+ function managedBlocks(absPath, regions) {
84
+ if (regions.length === 0) {
85
+ throw new Error("managedBlocks requires at least one region");
86
+ }
87
+ if (regions.length === 1) {
88
+ const only = regions[0];
89
+ if (!only) throw new Error("managedBlocks: undefined region");
90
+ return managedBlock(absPath, only.block, only.regionText);
91
+ }
92
+ let content = "";
93
+ try {
94
+ content = readFileSync2(absPath, "utf8");
95
+ } catch {
96
+ }
97
+ let stripped = content;
98
+ for (const r of regions) {
99
+ stripped = stripManagedBlock(stripped, r.block);
100
+ }
101
+ const regionsJoined = regions.map((r) => r.regionText).join("\n");
102
+ const next = stripped.trim().length > 0 ? `${stripped.trimEnd()}
103
+
104
+ ${regionsJoined}` : regionsJoined;
105
+ atomicWriteFile(absPath, next);
106
+ return { path: absPath, mode: "managedBlock", written: true };
107
+ }
108
+ function buildRegion(block, body) {
109
+ return `${block.begin}
110
+ ${body.trimEnd()}
111
+ ${block.end}
112
+ `;
113
+ }
114
+ function predictManagedBlock(currentContent, block, regionText) {
115
+ const stripped = stripManagedBlock(currentContent, block);
116
+ return stripped ? `${stripped.trimEnd()}
117
+
118
+ ${regionText}` : regionText;
119
+ }
120
+ function predictManagedBlocks(currentContent, regions) {
121
+ if (regions.length === 0) {
122
+ throw new Error("predictManagedBlocks requires at least one region");
123
+ }
124
+ if (regions.length === 1) {
125
+ const only = regions[0];
126
+ if (!only) throw new Error("predictManagedBlocks: undefined region");
127
+ return predictManagedBlock(currentContent, only.block, only.regionText);
128
+ }
129
+ let stripped = currentContent;
130
+ for (const r of regions) {
131
+ stripped = stripManagedBlock(stripped, r.block);
132
+ }
133
+ const regionsJoined = regions.map((r) => r.regionText).join("\n");
134
+ return stripped.trim().length > 0 ? `${stripped.trimEnd()}
135
+
136
+ ${regionsJoined}` : regionsJoined;
137
+ }
138
+ function skipIfExists(absPath, content, fileMode) {
139
+ if (existsSync2(absPath)) {
140
+ return { path: absPath, mode: "skipIfExists", written: false };
141
+ }
142
+ atomicWriteFile(absPath, content, fileMode !== void 0 ? { mode: fileMode } : {});
143
+ return { path: absPath, mode: "skipIfExists", written: true };
144
+ }
145
+ function refreshSeed(absPath, content) {
146
+ atomicWriteFile(absPath, content);
147
+ return { path: absPath, mode: "skipIfExists", written: true };
148
+ }
149
+ function mergeJson(absPath, patch, dedupSubstring, force) {
150
+ let existing = {};
151
+ if (existsSync2(absPath)) {
152
+ try {
153
+ existing = JSON.parse(readFileSync2(absPath, "utf8"));
154
+ } catch {
155
+ existing = {};
156
+ }
157
+ }
158
+ const merged = deepMergePreservingHooks(existing, patch, dedupSubstring, force);
159
+ const content = `${JSON.stringify(merged, null, 2)}
160
+ `;
161
+ if (existsSync2(absPath)) {
162
+ try {
163
+ if (readFileSync2(absPath, "utf8") === content) {
164
+ return { path: absPath, mode: "mergeJson", written: false };
165
+ }
166
+ } catch {
167
+ }
168
+ }
169
+ regenerate(absPath, content);
170
+ return { path: absPath, mode: "mergeJson", written: true };
171
+ }
172
+ function entryHasCommand(entry, marker) {
173
+ if (typeof entry !== "object" || entry === null) return false;
174
+ const obj = entry;
175
+ if (typeof obj.command === "string" && obj.command.includes(marker)) return true;
176
+ if (!Array.isArray(obj.hooks)) return false;
177
+ return obj.hooks.some((h) => {
178
+ if (typeof h !== "object" || h === null) return false;
179
+ const command = h.command;
180
+ return typeof command === "string" && command.includes(marker);
181
+ });
182
+ }
183
+ function deepMergePreservingHooks(existing, patch, dedupSubstring, force) {
184
+ const out = { ...existing };
185
+ for (const [key, value] of Object.entries(patch)) {
186
+ if (key === "hooks" && typeof value === "object" && value !== null) {
187
+ const existingHooks = out.hooks ?? {};
188
+ const mergedHooks = { ...existingHooks };
189
+ for (const [event, entries] of Object.entries(value)) {
190
+ const existingEntries = Array.isArray(mergedHooks[event]) ? mergedHooks[event] : [];
191
+ const patchEntries = Array.isArray(entries) ? entries : [];
192
+ const hasMarker = (x) => dedupSubstring !== void 0 && entryHasCommand(x, dedupSubstring);
193
+ const keptEntries = force === true ? existingEntries.filter((x) => !hasMarker(x)) : existingEntries;
194
+ const alreadyRegistered = force !== true && existingEntries.some(hasMarker);
195
+ const toAdd = alreadyRegistered ? [] : patchEntries;
196
+ mergedHooks[event] = [...keptEntries, ...toAdd];
197
+ }
198
+ out.hooks = mergedHooks;
199
+ } else {
200
+ out[key] = value;
201
+ }
202
+ }
203
+ return out;
204
+ }
205
+
206
+ // src/hooks.ts
207
+ function emitHooksToFile(root, host, opts) {
208
+ const adapter = resolveAdapter(host);
209
+ const emissions = adapter.emitHooks?.({ command: resolveNoirCommand() }) ?? null;
210
+ if (emissions === null) return { host, outcomes: [] };
211
+ const outcomes = emissions.map((e) => {
212
+ const abs = join3(root, e.path);
213
+ mkdirSync2(dirname3(abs), { recursive: true });
214
+ if (e.mode === "regenerate") {
215
+ const content = `${JSON.stringify(e.content, null, 2)}
216
+ `;
217
+ if (existsSync3(abs) && readFileSync3(abs, "utf8") === content) {
218
+ return { path: e.path, mode: "regenerate", written: false };
219
+ }
220
+ const out2 = regenerate(abs, content);
221
+ return { path: e.path, mode: "regenerate", written: out2.written };
222
+ }
223
+ const out = mergeJson(abs, e.content, e.dedupSubstring, opts?.force === true);
224
+ return { path: e.path, mode: "mergeJson", written: out.written };
225
+ });
226
+ return { host, outcomes };
227
+ }
228
+
33
229
  // src/manifest.ts
34
- import { existsSync as existsSync2 } from "fs";
35
- import { join as join3, relative } from "path";
230
+ import { existsSync as existsSync4 } from "fs";
231
+ import { join as join5, relative } from "path";
36
232
  import {
37
233
  AGENTS_MD_FILENAME,
38
234
  emitAgentsMd,
39
235
  mcpConfigPathForAdapter,
40
- resolveAdapter
236
+ resolveAdapter as resolveAdapter2
41
237
  } from "@noir-ai/adapters";
42
238
  import {
43
239
  CONTEXT_BLOCK,
44
240
  IGNORE_BLOCK,
45
- managedBlock,
241
+ managedBlock as managedBlock2,
46
242
  NOIR_DIR,
47
243
  paths,
48
244
  RULES_BLOCK,
@@ -61,26 +257,26 @@ function render(template, vars) {
61
257
  }
62
258
 
63
259
  // src/template-loader.ts
64
- import { readFileSync as readFileSync2 } from "fs";
65
- import { dirname as dirname2, join as join2, resolve } from "path";
260
+ import { readFileSync as readFileSync4 } from "fs";
261
+ import { dirname as dirname4, join as join4, resolve } from "path";
66
262
  import { fileURLToPath } from "url";
67
263
  var DEFAULT_TEMPLATES_DIR = resolveTemplatesDir();
68
264
  function resolveTemplatesDir() {
69
265
  const override = process.env.NOIR_TEMPLATES_DIR;
70
266
  if (override && override.length > 0) return resolve(override);
71
- const here = dirname2(fileURLToPath(import.meta.url));
72
- return join2(here, "..", "templates");
267
+ const here = dirname4(fileURLToPath(import.meta.url));
268
+ return join4(here, "..", "templates");
73
269
  }
74
270
  function loadTemplate(name) {
75
- return readFileSync2(join2(DEFAULT_TEMPLATES_DIR, name), "utf8");
271
+ return readFileSync4(join4(DEFAULT_TEMPLATES_DIR, name), "utf8");
76
272
  }
77
273
  function templatesDir() {
78
274
  return DEFAULT_TEMPLATES_DIR;
79
275
  }
80
276
 
81
277
  // src/manifest.ts
82
- var BRIEF_BLOCK = managedBlock("brief", "html");
83
- var README_BLOCK = managedBlock("readme", "html");
278
+ var BRIEF_BLOCK = managedBlock2("brief", "html");
279
+ var README_BLOCK = managedBlock2("readme", "html");
84
280
  var RULES_MAP_ROW_PREFIX = "| `rules/RULES.md` |";
85
281
  function readmeMapContent(ctx) {
86
282
  const rendered = render(loadTemplate("noir-readme.md.tmpl"), ctx);
@@ -107,7 +303,7 @@ var REFRESHABLE_SEED_KIND = {
107
303
  [P.rulesMd]: "rulesSeed"
108
304
  };
109
305
  function buildManifest(ctx) {
110
- return [...hostAgnosticEntries(ctx), ...buildHostArtifacts(resolveAdapter(ctx.host), ctx)];
306
+ return [...hostAgnosticEntries(ctx), ...buildHostArtifacts(resolveAdapter2(ctx.host), ctx)];
111
307
  }
112
308
  function hostAgnosticEntries(ctx) {
113
309
  const entries = [
@@ -212,7 +408,7 @@ function hostAgnosticEntries(ctx) {
212
408
  ];
213
409
  const isEmpty = (ctx.stack?.languages?.length ?? 0) === 0 && !ctx.stack?.packageManager;
214
410
  const isJs = isEmpty || (ctx.stack?.languages?.some((l) => l === "typescript" || l === "javascript") ?? false) || ["npm", "pnpm", "yarn"].includes(ctx.stack?.packageManager ?? "");
215
- const hasDocker = isEmpty || existsSync2(join3(ctx.root, "Dockerfile")) || existsSync2(join3(ctx.root, "docker-compose.yml")) || existsSync2(join3(ctx.root, "compose.yaml"));
411
+ const hasDocker = isEmpty || existsSync4(join5(ctx.root, "Dockerfile")) || existsSync4(join5(ctx.root, "docker-compose.yml")) || existsSync4(join5(ctx.root, "compose.yaml"));
216
412
  const rulesEnabled = ctx.rulesEnabled !== false;
217
413
  return entries.filter((e) => {
218
414
  if (e.path === P.rulesMd) return rulesEnabled;
@@ -229,7 +425,7 @@ function buildHostArtifacts(adapter, ctx) {
229
425
  const emitsAgentsMd = host === "agents-md" || host === "cursor" || host === "opencode";
230
426
  if (emitsAgentsMd) {
231
427
  entries.push({
232
- path: hostRel(adapter.agentsMdPath?.(ectx) ?? join3(ctx.root, AGENTS_MD_FILENAME), ctx.root),
428
+ path: hostRel(adapter.agentsMdPath?.(ectx) ?? join5(ctx.root, AGENTS_MD_FILENAME), ctx.root),
233
429
  mode: "regenerate",
234
430
  host,
235
431
  content: emitAgentsMd(ectx),
@@ -505,15 +701,15 @@ function mergeThreeWay(base, ours, theirs, style = "minimal") {
505
701
  }
506
702
 
507
703
  // src/migrations/index.ts
508
- import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4, rmdirSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
509
- import { join as join5, relative as relative2, sep } from "path";
704
+ import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync5, rmdirSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
705
+ import { join as join6, relative as relative2, sep } from "path";
510
706
  import { mcpConfigPathFor, noirStdioArgs, TRANSPORT_KEYS } from "@noir-ai/adapters";
511
707
  import {
512
708
  atomicWriteFile as atomicWriteFile2,
513
709
  loadProjectInfo,
514
710
  readProjectConfig,
515
711
  readWorkspaceMarker as readWorkspaceMarker2,
516
- resolveNoirCommand
712
+ resolveNoirCommand as resolveNoirCommand2
517
713
  } from "@noir-ai/core";
518
714
 
519
715
  // src/template-history.ts
@@ -755,171 +951,6 @@ function isStaleSeed(seed, fileBytes, currentRender) {
755
951
  return SEED_TEMPLATE_HISTORY.some((entry) => entry[seed] === fileBytes);
756
952
  }
757
953
 
758
- // src/writers.ts
759
- import {
760
- closeSync,
761
- existsSync as existsSync3,
762
- openSync,
763
- readFileSync as readFileSync3,
764
- renameSync,
765
- rmSync,
766
- writeSync
767
- } from "fs";
768
- import { basename, dirname as dirname3, join as join4 } from "path";
769
- import { atomicWriteFile, stripManagedBlock, writeManagedRegion } from "@noir-ai/core";
770
- import { ensureOwnerOnly } from "@noir-ai/core";
771
- function regenerate(absPath, content) {
772
- const dir = dirname3(absPath);
773
- const tmp = join4(
774
- dir,
775
- `.${basename(absPath)}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`
776
- );
777
- let fd;
778
- try {
779
- fd = openSync(tmp, "w");
780
- writeSync(fd, content, 0, "utf8");
781
- closeSync(fd);
782
- fd = void 0;
783
- renameSync(tmp, absPath);
784
- } finally {
785
- if (fd !== void 0) {
786
- try {
787
- closeSync(fd);
788
- } catch {
789
- }
790
- }
791
- try {
792
- rmSync(tmp, { force: true });
793
- } catch {
794
- }
795
- }
796
- return { path: absPath, mode: "regenerate", written: true };
797
- }
798
- function managedBlock2(absPath, block, regionText) {
799
- writeManagedRegion(absPath, block, regionText);
800
- return { path: absPath, mode: "managedBlock", written: true };
801
- }
802
- function managedBlocks(absPath, regions) {
803
- if (regions.length === 0) {
804
- throw new Error("managedBlocks requires at least one region");
805
- }
806
- if (regions.length === 1) {
807
- const only = regions[0];
808
- if (!only) throw new Error("managedBlocks: undefined region");
809
- return managedBlock2(absPath, only.block, only.regionText);
810
- }
811
- let content = "";
812
- try {
813
- content = readFileSync3(absPath, "utf8");
814
- } catch {
815
- }
816
- let stripped = content;
817
- for (const r of regions) {
818
- stripped = stripManagedBlock(stripped, r.block);
819
- }
820
- const regionsJoined = regions.map((r) => r.regionText).join("\n");
821
- const next = stripped.trim().length > 0 ? `${stripped.trimEnd()}
822
-
823
- ${regionsJoined}` : regionsJoined;
824
- atomicWriteFile(absPath, next);
825
- return { path: absPath, mode: "managedBlock", written: true };
826
- }
827
- function buildRegion(block, body) {
828
- return `${block.begin}
829
- ${body.trimEnd()}
830
- ${block.end}
831
- `;
832
- }
833
- function predictManagedBlock(currentContent, block, regionText) {
834
- const stripped = stripManagedBlock(currentContent, block);
835
- return stripped ? `${stripped.trimEnd()}
836
-
837
- ${regionText}` : regionText;
838
- }
839
- function predictManagedBlocks(currentContent, regions) {
840
- if (regions.length === 0) {
841
- throw new Error("predictManagedBlocks requires at least one region");
842
- }
843
- if (regions.length === 1) {
844
- const only = regions[0];
845
- if (!only) throw new Error("predictManagedBlocks: undefined region");
846
- return predictManagedBlock(currentContent, only.block, only.regionText);
847
- }
848
- let stripped = currentContent;
849
- for (const r of regions) {
850
- stripped = stripManagedBlock(stripped, r.block);
851
- }
852
- const regionsJoined = regions.map((r) => r.regionText).join("\n");
853
- return stripped.trim().length > 0 ? `${stripped.trimEnd()}
854
-
855
- ${regionsJoined}` : regionsJoined;
856
- }
857
- function skipIfExists(absPath, content, fileMode) {
858
- if (existsSync3(absPath)) {
859
- return { path: absPath, mode: "skipIfExists", written: false };
860
- }
861
- atomicWriteFile(absPath, content, fileMode !== void 0 ? { mode: fileMode } : {});
862
- return { path: absPath, mode: "skipIfExists", written: true };
863
- }
864
- function refreshSeed(absPath, content) {
865
- atomicWriteFile(absPath, content);
866
- return { path: absPath, mode: "skipIfExists", written: true };
867
- }
868
- function mergeJson(absPath, patch, dedupSubstring) {
869
- let existing = {};
870
- if (existsSync3(absPath)) {
871
- try {
872
- existing = JSON.parse(readFileSync3(absPath, "utf8"));
873
- } catch {
874
- existing = {};
875
- }
876
- }
877
- const merged = deepMergePreservingHooks(existing, patch, dedupSubstring);
878
- const content = `${JSON.stringify(merged, null, 2)}
879
- `;
880
- if (existsSync3(absPath)) {
881
- try {
882
- if (readFileSync3(absPath, "utf8") === content) {
883
- return { path: absPath, mode: "mergeJson", written: false };
884
- }
885
- } catch {
886
- }
887
- }
888
- regenerate(absPath, content);
889
- return { path: absPath, mode: "mergeJson", written: true };
890
- }
891
- function entryHasCommand(entry, marker) {
892
- if (typeof entry !== "object" || entry === null) return false;
893
- const obj = entry;
894
- if (typeof obj.command === "string" && obj.command.includes(marker)) return true;
895
- if (!Array.isArray(obj.hooks)) return false;
896
- return obj.hooks.some((h) => {
897
- if (typeof h !== "object" || h === null) return false;
898
- const command = h.command;
899
- return typeof command === "string" && command.includes(marker);
900
- });
901
- }
902
- function deepMergePreservingHooks(existing, patch, dedupSubstring) {
903
- const out = { ...existing };
904
- for (const [key, value] of Object.entries(patch)) {
905
- if (key === "hooks" && typeof value === "object" && value !== null) {
906
- const existingHooks = out.hooks ?? {};
907
- const mergedHooks = { ...existingHooks };
908
- for (const [event, entries] of Object.entries(value)) {
909
- const existingEntries = Array.isArray(mergedHooks[event]) ? mergedHooks[event] : [];
910
- const patchEntries = Array.isArray(entries) ? entries : [];
911
- const alreadyRegistered = dedupSubstring !== void 0 && existingEntries.some((x) => entryHasCommand(x, dedupSubstring));
912
- const toAdd = alreadyRegistered ? [] : patchEntries;
913
- mergedHooks[event] = [...existingEntries, ...toAdd];
914
- }
915
- out.hooks = mergedHooks;
916
- } else {
917
- out[key] = value;
918
- }
919
- }
920
- return out;
921
- }
922
-
923
954
  // src/migrations/runner.ts
924
955
  function runMigrations(root, from, to, opts = {}) {
925
956
  const window = pickWindow(from ?? "0.0.0", to, MIGRATIONS);
@@ -972,9 +1003,9 @@ var synthetic = {
972
1003
  run: (ctx) => {
973
1004
  const result = { changed: [], conflicts: [], notes: [] };
974
1005
  if (process.env.NOIR_TEST_FORCE_CONFLICT === "1" && !ctx.dryRun) {
975
- const file = join5(ctx.root, ".noir", "scaffold-version");
976
- if (existsSync4(file)) {
977
- const prev = readFileSync4(file, "utf8");
1006
+ const file = join6(ctx.root, ".noir", "scaffold-version");
1007
+ if (existsSync5(file)) {
1008
+ const prev = readFileSync5(file, "utf8");
978
1009
  const merged = applyInlineConflict(prev, "noir-scaffold=1.0.0\n", "ours", "theirs");
979
1010
  writeFileSync2(file, merged, "utf8");
980
1011
  result.conflicts.push(".noir/scaffold-version");
@@ -1008,14 +1039,14 @@ var envPointer = {
1008
1039
  run: (ctx) => {
1009
1040
  const result = { changed: [], conflicts: [], notes: [] };
1010
1041
  const rel = ".noir/config.yml";
1011
- const abs = join5(ctx.root, ".noir", "config.yml");
1012
- if (!existsSync4(abs)) {
1042
+ const abs = join6(ctx.root, ".noir", "config.yml");
1043
+ if (!existsSync5(abs)) {
1013
1044
  result.notes.push(`${rel}: absent \u2014 the template seed carries the pointer`);
1014
1045
  return result;
1015
1046
  }
1016
1047
  let prev;
1017
1048
  try {
1018
- prev = readFileSync4(abs, "utf8");
1049
+ prev = readFileSync5(abs, "utf8");
1019
1050
  } catch (err) {
1020
1051
  result.conflicts.push(rel);
1021
1052
  result.notes.push(`${rel}: unreadable (${errorMessage(err)}) \u2014 left untouched`);
@@ -1057,14 +1088,14 @@ var envTemplates = {
1057
1088
  result.notes.push(`${seed.rel}: skipped \u2014 the working-rules switch is off`);
1058
1089
  continue;
1059
1090
  }
1060
- const abs = join5(ctx.root, seed.rel);
1061
- if (!existsSync4(abs)) {
1091
+ const abs = join6(ctx.root, seed.rel);
1092
+ if (!existsSync5(abs)) {
1062
1093
  result.notes.push(`${seed.rel}: absent \u2014 nothing to refresh`);
1063
1094
  continue;
1064
1095
  }
1065
1096
  let prev;
1066
1097
  try {
1067
- prev = readFileSync4(abs, "utf8");
1098
+ prev = readFileSync5(abs, "utf8");
1068
1099
  } catch (err) {
1069
1100
  result.conflicts.push(seed.rel);
1070
1101
  result.notes.push(`${seed.rel}: unreadable (${errorMessage(err)}) \u2014 left untouched`);
@@ -1100,7 +1131,7 @@ function mcpConfigPath(root) {
1100
1131
  } catch {
1101
1132
  }
1102
1133
  const abs = mcpConfigPathFor(host, root);
1103
- if (!existsSync4(abs)) return null;
1134
+ if (!existsSync5(abs)) return null;
1104
1135
  return { rel: relative2(root, abs).split(sep).join("/"), abs };
1105
1136
  }
1106
1137
  var workspaceBridge = {
@@ -1121,7 +1152,7 @@ var workspaceBridge = {
1121
1152
  }
1122
1153
  let config;
1123
1154
  try {
1124
- config = JSON.parse(readFileSync4(target.abs, "utf8"));
1155
+ config = JSON.parse(readFileSync5(target.abs, "utf8"));
1125
1156
  } catch (err) {
1126
1157
  result.conflicts.push(target.rel);
1127
1158
  result.notes.push(`${target.rel}: unreadable (${errorMessage(err)}) \u2014 left untouched`);
@@ -1159,7 +1190,7 @@ var workspaceBridge = {
1159
1190
  mcpServers: {
1160
1191
  ...servers,
1161
1192
  noir: {
1162
- command: resolveNoirCommand(),
1193
+ command: resolveNoirCommand2(),
1163
1194
  // The argv comes from the adapters' own helper, so this rewrite lands
1164
1195
  // on exactly the entry a fresh join writes — the name is the whole
1165
1196
  // address, and the bridge resolves the daemon (and reads its token)
@@ -1190,11 +1221,11 @@ var hookCommand = {
1190
1221
  run: (ctx) => {
1191
1222
  const result = { changed: [], conflicts: [], notes: [] };
1192
1223
  const settingsRel = ".claude/settings.local.json";
1193
- const settingsPath = join5(ctx.root, ".claude", "settings.local.json");
1194
- if (existsSync4(settingsPath)) {
1224
+ const settingsPath = join6(ctx.root, ".claude", "settings.local.json");
1225
+ if (existsSync5(settingsPath)) {
1195
1226
  let settings;
1196
1227
  try {
1197
- settings = JSON.parse(readFileSync4(settingsPath, "utf8"));
1228
+ settings = JSON.parse(readFileSync5(settingsPath, "utf8"));
1198
1229
  } catch (err) {
1199
1230
  result.conflicts.push(settingsRel);
1200
1231
  result.notes.push(
@@ -1215,7 +1246,7 @@ var hookCommand = {
1215
1246
  if (typeof entry !== "object" || entry === null) continue;
1216
1247
  const hookObj = entry;
1217
1248
  if (typeof hookObj.command === "string" && hookObj.command.includes(OLD_HOOK_MARKER)) {
1218
- hookObj.command = `"${resolveNoirCommand()}" hook`;
1249
+ hookObj.command = `"${resolveNoirCommand2()}" hook`;
1219
1250
  rewritten = true;
1220
1251
  }
1221
1252
  }
@@ -1242,8 +1273,8 @@ var hookCommand = {
1242
1273
  result.notes.push(`${settingsRel}: absent \u2014 nothing to migrate`);
1243
1274
  }
1244
1275
  const runnerRel = ".noir/hooks/noir-session-start.mjs";
1245
- const runnerPath = join5(ctx.root, ".noir", "hooks", "noir-session-start.mjs");
1246
- if (!existsSync4(runnerPath)) {
1276
+ const runnerPath = join6(ctx.root, ".noir", "hooks", "noir-session-start.mjs");
1277
+ if (!existsSync5(runnerPath)) {
1247
1278
  return result;
1248
1279
  }
1249
1280
  if (ctx.dryRun) {
@@ -1260,7 +1291,7 @@ var hookCommand = {
1260
1291
  }
1261
1292
  result.changed.push(runnerRel);
1262
1293
  result.notes.push(`${runnerRel}: removed (orphaned runner)`);
1263
- const hooksDir = join5(ctx.root, ".noir", "hooks");
1294
+ const hooksDir = join6(ctx.root, ".noir", "hooks");
1264
1295
  try {
1265
1296
  if (readdirSync(hooksDir).length === 0) rmdirSync(hooksDir);
1266
1297
  } catch {
@@ -1270,14 +1301,14 @@ var hookCommand = {
1270
1301
  };
1271
1302
  function applyHookEvents(ctx, rel, abs, events, createIfMissing) {
1272
1303
  let settings;
1273
- if (!existsSync4(abs)) {
1304
+ if (!existsSync5(abs)) {
1274
1305
  if (!createIfMissing) {
1275
1306
  return { note: `${rel}: absent \u2014 nothing to migrate`, changed: false, conflict: false };
1276
1307
  }
1277
1308
  settings = {};
1278
1309
  } else {
1279
1310
  try {
1280
- settings = JSON.parse(readFileSync4(abs, "utf8"));
1311
+ settings = JSON.parse(readFileSync5(abs, "utf8"));
1281
1312
  } catch (err) {
1282
1313
  return {
1283
1314
  note: `${rel}: unreadable (${errorMessage(err)}) \u2014 left untouched`,
@@ -1337,8 +1368,8 @@ var hookEvents = {
1337
1368
  return result;
1338
1369
  }
1339
1370
  const rel = host === "gemini" ? ".gemini/settings.json" : ".claude/settings.local.json";
1340
- const abs = join5(ctx.root, ...rel.split("/"));
1341
- const command = host === "gemini" ? `"${resolveNoirCommand()}" hook --host gemini` : `"${resolveNoirCommand()}" hook`;
1371
+ const abs = join6(ctx.root, ...rel.split("/"));
1372
+ const command = host === "gemini" ? `"${resolveNoirCommand2()}" hook --host gemini` : `"${resolveNoirCommand2()}" hook`;
1342
1373
  const events = host === "gemini" ? {
1343
1374
  SessionStart: [{ hooks: [{ type: "command", command }] }],
1344
1375
  BeforeTool: [{ matcher: ".*", hooks: [{ type: "command", command }] }]
@@ -1379,21 +1410,21 @@ function applyWithConflict(ours, theirs, path) {
1379
1410
  }
1380
1411
 
1381
1412
  // src/scaffold.ts
1382
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync8, renameSync as renameSync2, rmSync as rmSync3 } from "fs";
1383
- import { basename as basename2, dirname as dirname5, join as join9 } from "path";
1413
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync3 } from "fs";
1414
+ import { basename as basename2, dirname as dirname6, join as join10 } from "path";
1384
1415
  import {
1385
1416
  createProjectId,
1386
1417
  loadProjectInfo as loadProjectInfo2,
1387
1418
  paths as paths2,
1388
1419
  readManagedBlock,
1389
1420
  readProjectConfig as readProjectConfig2,
1390
- resolveNoirCommand as resolveNoirCommand2,
1421
+ resolveNoirCommand as resolveNoirCommand3,
1391
1422
  sha256Hex12
1392
1423
  } from "@noir-ai/core";
1393
1424
 
1394
1425
  // src/baseline.ts
1395
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1396
- import { join as join6 } from "path";
1426
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
1427
+ import { join as join7 } from "path";
1397
1428
  var COMMAND_KEYS = [
1398
1429
  ["build", "build"],
1399
1430
  ["test", "test"],
@@ -1422,11 +1453,11 @@ var ENTRY_POINTS = [
1422
1453
  ];
1423
1454
  function detectCommands(root) {
1424
1455
  const out = {};
1425
- const pjPath = join6(root, "package.json");
1426
- if (existsSync5(pjPath)) {
1456
+ const pjPath = join7(root, "package.json");
1457
+ if (existsSync6(pjPath)) {
1427
1458
  let pj;
1428
1459
  try {
1429
- pj = JSON.parse(readFileSync5(pjPath, "utf8"));
1460
+ pj = JSON.parse(readFileSync6(pjPath, "utf8"));
1430
1461
  } catch {
1431
1462
  pj = void 0;
1432
1463
  }
@@ -1435,10 +1466,10 @@ function detectCommands(root) {
1435
1466
  if (typeof value === "string" && value !== "") out[name] = value;
1436
1467
  }
1437
1468
  }
1438
- const makefile = join6(root, "Makefile");
1439
- if (existsSync5(makefile)) {
1469
+ const makefile = join7(root, "Makefile");
1470
+ if (existsSync6(makefile)) {
1440
1471
  try {
1441
- const raw = readFileSync5(makefile, "utf8");
1472
+ const raw = readFileSync6(makefile, "utf8");
1442
1473
  for (const name of ["build", "test", "lint"]) {
1443
1474
  if (!(name in out) && new RegExp(`^${name}:`, "m").test(raw)) out[name] = `make ${name}`;
1444
1475
  }
@@ -1448,7 +1479,7 @@ function detectCommands(root) {
1448
1479
  return out;
1449
1480
  }
1450
1481
  function detectEntryPoints(root) {
1451
- return ENTRY_POINTS.filter((p) => existsSync5(join6(root, p)));
1482
+ return ENTRY_POINTS.filter((p) => existsSync6(join7(root, p)));
1452
1483
  }
1453
1484
  function renderBaseline(stack, commands, entryPoints) {
1454
1485
  const lang = stack.languages.length > 0 ? stack.languages.join(", ") : "unknown";
@@ -1468,18 +1499,18 @@ function renderBaseline(stack, commands, entryPoints) {
1468
1499
  }
1469
1500
 
1470
1501
  // src/scaffold-version.ts
1471
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync6 } from "fs";
1472
- import { dirname as dirname4, join as join7 } from "path";
1502
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
1503
+ import { dirname as dirname5, join as join8 } from "path";
1473
1504
  import { NOIR_DIR as NOIR_DIR2 } from "@noir-ai/core";
1474
1505
  var CURRENT_SCAFFOLD_VERSION = "1.5.0";
1475
1506
  var PREFIX = "noir-scaffold=";
1476
1507
  function scaffoldVersionPath(root) {
1477
- return join7(root, NOIR_DIR2, "scaffold-version");
1508
+ return join8(root, NOIR_DIR2, "scaffold-version");
1478
1509
  }
1479
1510
  function readScaffoldVersion(root) {
1480
1511
  let raw;
1481
1512
  try {
1482
- raw = readFileSync6(scaffoldVersionPath(root), "utf8");
1513
+ raw = readFileSync7(scaffoldVersionPath(root), "utf8");
1483
1514
  } catch {
1484
1515
  return null;
1485
1516
  }
@@ -1494,14 +1525,14 @@ function readScaffoldVersion(root) {
1494
1525
  }
1495
1526
  function writeScaffoldVersion(root, version) {
1496
1527
  const file = scaffoldVersionPath(root);
1497
- mkdirSync2(dirname4(file), { recursive: true });
1528
+ mkdirSync3(dirname5(file), { recursive: true });
1498
1529
  regenerate(file, `${PREFIX}${version}
1499
1530
  `);
1500
1531
  }
1501
1532
 
1502
1533
  // src/stack-detect.ts
1503
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
1504
- import { join as join8 } from "path";
1534
+ import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
1535
+ import { join as join9 } from "path";
1505
1536
  var NODE_FRAMEWORKS = [
1506
1537
  ["next", "next"],
1507
1538
  ["vite", "vite"],
@@ -1528,7 +1559,7 @@ var PYTHON_FRAMEWORKS = [
1528
1559
  ];
1529
1560
  function readJson(file) {
1530
1561
  try {
1531
- return JSON.parse(readFileSync7(file, "utf8"));
1562
+ return JSON.parse(readFileSync8(file, "utf8"));
1532
1563
  } catch {
1533
1564
  return void 0;
1534
1565
  }
@@ -1538,11 +1569,11 @@ function detectStack(root) {
1538
1569
  const frameworks = /* @__PURE__ */ new Set();
1539
1570
  let monorepo = false;
1540
1571
  let packageManager = null;
1541
- const pjPath = join8(root, "package.json");
1542
- if (existsSync6(pjPath)) {
1572
+ const pjPath = join9(root, "package.json");
1573
+ if (existsSync7(pjPath)) {
1543
1574
  const pj = readJson(pjPath);
1544
1575
  if (pj) {
1545
- const hasTs = Boolean(pj.devDependencies?.typescript) || existsSync6(join8(root, "tsconfig.json"));
1576
+ const hasTs = Boolean(pj.devDependencies?.typescript) || existsSync7(join9(root, "tsconfig.json"));
1546
1577
  languages.add(hasTs ? "typescript" : "javascript");
1547
1578
  const deps = /* @__PURE__ */ new Set([
1548
1579
  ...Object.keys(pj.dependencies ?? {}),
@@ -1561,22 +1592,22 @@ function detectStack(root) {
1561
1592
  }
1562
1593
  }
1563
1594
  }
1564
- if (existsSync6(join8(root, "pnpm-workspace.yaml"))) {
1595
+ if (existsSync7(join9(root, "pnpm-workspace.yaml"))) {
1565
1596
  monorepo = true;
1566
1597
  packageManager = packageManager ?? "pnpm";
1567
1598
  }
1568
- if (existsSync6(join8(root, "turbo.json")) || existsSync6(join8(root, "nx.json"))) {
1599
+ if (existsSync7(join9(root, "turbo.json")) || existsSync7(join9(root, "nx.json"))) {
1569
1600
  monorepo = true;
1570
1601
  }
1571
1602
  if (!packageManager) {
1572
- if (existsSync6(join8(root, "pnpm-lock.yaml"))) packageManager = "pnpm";
1573
- else if (existsSync6(join8(root, "yarn.lock"))) packageManager = "yarn";
1574
- else if (existsSync6(join8(root, "package-lock.json"))) packageManager = "npm";
1603
+ if (existsSync7(join9(root, "pnpm-lock.yaml"))) packageManager = "pnpm";
1604
+ else if (existsSync7(join9(root, "yarn.lock"))) packageManager = "yarn";
1605
+ else if (existsSync7(join9(root, "package-lock.json"))) packageManager = "npm";
1575
1606
  }
1576
- if (existsSync6(join8(root, "pyproject.toml")) || existsSync6(join8(root, "requirements.txt")) || existsSync6(join8(root, "Pipfile")) || existsSync6(join8(root, "setup.py"))) {
1607
+ if (existsSync7(join9(root, "pyproject.toml")) || existsSync7(join9(root, "requirements.txt")) || existsSync7(join9(root, "Pipfile")) || existsSync7(join9(root, "setup.py"))) {
1577
1608
  languages.add("python");
1578
- if (existsSync6(join8(root, "pyproject.toml"))) {
1579
- const raw = safeRead(join8(root, "pyproject.toml"));
1609
+ if (existsSync7(join9(root, "pyproject.toml"))) {
1610
+ const raw = safeRead(join9(root, "pyproject.toml"));
1580
1611
  if (raw) {
1581
1612
  for (const [dep, id] of PYTHON_FRAMEWORKS) {
1582
1613
  if (pyprojectHasDep(raw, dep)) frameworks.add(id);
@@ -1584,13 +1615,13 @@ function detectStack(root) {
1584
1615
  }
1585
1616
  }
1586
1617
  }
1587
- if (existsSync6(join8(root, "go.mod"))) {
1618
+ if (existsSync7(join9(root, "go.mod"))) {
1588
1619
  languages.add("go");
1589
1620
  packageManager = packageManager ?? "go-modules";
1590
1621
  }
1591
- if (existsSync6(join8(root, "Cargo.toml"))) {
1622
+ if (existsSync7(join9(root, "Cargo.toml"))) {
1592
1623
  languages.add("rust");
1593
- const raw = safeRead(join8(root, "Cargo.toml"));
1624
+ const raw = safeRead(join9(root, "Cargo.toml"));
1594
1625
  if (raw) {
1595
1626
  if (/^\s*actix-web\b/m.test(raw)) frameworks.add("actix-web");
1596
1627
  if (/^\s*actix\s*=/m.test(raw)) frameworks.add("actix");
@@ -1602,7 +1633,7 @@ function detectStack(root) {
1602
1633
  let pmSource = "unknown";
1603
1634
  let pmConflict = false;
1604
1635
  if (packageManager) {
1605
- if (pjPath && existsSync6(pjPath)) {
1636
+ if (pjPath && existsSync7(pjPath)) {
1606
1637
  const pj = readJson(pjPath);
1607
1638
  if (pj?.packageManager?.split("@")[0] === packageManager) {
1608
1639
  pmSource = "packageManager-field";
@@ -1631,10 +1662,10 @@ function detectStack(root) {
1631
1662
  }
1632
1663
  }
1633
1664
  let ci = null;
1634
- if (existsSync6(join8(root, ".github", "workflows"))) ci = "github";
1635
- else if (existsSync6(join8(root, ".gitlab-ci.yml"))) ci = "gitlab";
1636
- else if (existsSync6(join8(root, ".circleci", "config.yml"))) ci = "circleci";
1637
- else if (existsSync6(join8(root, "Jenkinsfile"))) ci = "jenkins";
1665
+ if (existsSync7(join9(root, ".github", "workflows"))) ci = "github";
1666
+ else if (existsSync7(join9(root, ".gitlab-ci.yml"))) ci = "gitlab";
1667
+ else if (existsSync7(join9(root, ".circleci", "config.yml"))) ci = "circleci";
1668
+ else if (existsSync7(join9(root, "Jenkinsfile"))) ci = "jenkins";
1638
1669
  const aiFiles = [
1639
1670
  ["AGENTS.md", "agents-md"],
1640
1671
  ["CLAUDE.md", "claude"],
@@ -1644,12 +1675,12 @@ function detectStack(root) {
1644
1675
  ];
1645
1676
  const existingAiFiles = [];
1646
1677
  for (const [p, kind] of aiFiles) {
1647
- const full = join8(root, p);
1648
- if (existsSync6(full)) existingAiFiles.push({ path: p, kind });
1678
+ const full = join9(root, p);
1679
+ if (existsSync7(full)) existingAiFiles.push({ path: p, kind });
1649
1680
  }
1650
- const cursorRulesDir = join8(root, ".cursor", "rules");
1681
+ const cursorRulesDir = join9(root, ".cursor", "rules");
1651
1682
  try {
1652
- if (existsSync6(cursorRulesDir)) {
1683
+ if (existsSync7(cursorRulesDir)) {
1653
1684
  const entries = readdirSync2(cursorRulesDir);
1654
1685
  for (const e of entries) {
1655
1686
  if (e.endsWith(".md"))
@@ -1671,7 +1702,7 @@ function detectStack(root) {
1671
1702
  }
1672
1703
  function safeRead(file) {
1673
1704
  try {
1674
- return readFileSync7(file, "utf8");
1705
+ return readFileSync8(file, "utf8");
1675
1706
  } catch {
1676
1707
  return void 0;
1677
1708
  }
@@ -1698,17 +1729,17 @@ function assertSafeRoot(root) {
1698
1729
  `Refusing to scaffold inside a .noir/ directory (${root}). Run \`noir init\` from the project root, not from inside .noir/.`
1699
1730
  );
1700
1731
  }
1701
- const parent = dirname5(cur);
1732
+ const parent = dirname6(cur);
1702
1733
  if (parent === cur) break;
1703
1734
  cur = parent;
1704
1735
  }
1705
1736
  }
1706
1737
  function envOwnerOnly(opts) {
1707
1738
  if (opts.dryRun === true) return void 0;
1708
- return ensureOwnerOnly(join9(paths2.noirDir(opts.root), ".env"));
1739
+ return ensureOwnerOnly(join10(paths2.noirDir(opts.root), ".env"));
1709
1740
  }
1710
1741
  function readConfiguredHost(root) {
1711
- if (!existsSync7(paths2.config(root))) return "claude";
1742
+ if (!existsSync8(paths2.config(root))) return "claude";
1712
1743
  try {
1713
1744
  return loadProjectInfo2(root).config.host;
1714
1745
  } catch {
@@ -1726,7 +1757,7 @@ async function scaffold(opts) {
1726
1757
  throw new Error("transport 'streamable-http' requires opts.url");
1727
1758
  }
1728
1759
  if (opts.mode === "create" && !opts.dryRun) {
1729
- mkdirSync3(opts.root, { recursive: true });
1760
+ mkdirSync4(opts.root, { recursive: true });
1730
1761
  }
1731
1762
  const idFile = readProjectIdFile(opts.root);
1732
1763
  const projectId = resolveProjectId(opts, idFile);
@@ -1782,7 +1813,7 @@ async function scaffold(opts) {
1782
1813
  migrationConflicts.push(...m.conflicts);
1783
1814
  migrationChanged.push(...m.changed);
1784
1815
  }
1785
- const command = resolveNoirCommand2();
1816
+ const command = resolveNoirCommand3();
1786
1817
  const manifest = buildManifest({
1787
1818
  root: opts.root,
1788
1819
  projectId,
@@ -1826,7 +1857,7 @@ async function scaffold(opts) {
1826
1857
  };
1827
1858
  const groups = groupApplicableByPath(manifest, host, emitContext);
1828
1859
  for (const [relPath, entries] of groups) {
1829
- const abs = join9(opts.root, relPath);
1860
+ const abs = join10(opts.root, relPath);
1830
1861
  const managed = entries.filter((e) => e.mode === "managedBlock");
1831
1862
  if (managed.length >= 2) {
1832
1863
  if (opts.dryRun) {
@@ -1849,7 +1880,7 @@ async function scaffold(opts) {
1849
1880
  written.push(relPath);
1850
1881
  continue;
1851
1882
  }
1852
- mkdirSync3(dirname5(abs), { recursive: true });
1883
+ mkdirSync4(dirname6(abs), { recursive: true });
1853
1884
  const onDisk = readOptional(abs);
1854
1885
  let hadMergeConflict = false;
1855
1886
  const regions = managed.map((e) => {
@@ -1890,7 +1921,7 @@ async function scaffold(opts) {
1890
1921
  }
1891
1922
  continue;
1892
1923
  }
1893
- if (!opts.dryRun) mkdirSync3(dirname5(abs), { recursive: true });
1924
+ if (!opts.dryRun) mkdirSync4(dirname6(abs), { recursive: true });
1894
1925
  for (const entry of entries) {
1895
1926
  if (opts.dryRun) {
1896
1927
  if (opts.check === true && (entry.mode === "regenerate" || entry.mode === "managedBlock")) {
@@ -1918,7 +1949,7 @@ async function scaffold(opts) {
1918
1949
  }
1919
1950
  const seedKind = refreshableSeedKind(entry, emitContext);
1920
1951
  if (seedKind === void 0) {
1921
- if (existsSync7(abs)) {
1952
+ if (existsSync8(abs)) {
1922
1953
  skipped.push(entry.path);
1923
1954
  } else {
1924
1955
  written.push(entry.path);
@@ -1985,7 +2016,7 @@ async function scaffold(opts) {
1985
2016
  if (onDisk !== void 0 && predicted === onDisk) {
1986
2017
  identical.push(entry.path);
1987
2018
  } else {
1988
- managedBlock2(abs, block, regionText);
2019
+ managedBlock(abs, block, regionText);
1989
2020
  written.push(entry.path);
1990
2021
  }
1991
2022
  } else if (entry.mode === "mergeJson") {
@@ -2111,7 +2142,7 @@ async function resolveManagedConflict(relPath, mode, opts) {
2111
2142
  function readProjectIdFile(root) {
2112
2143
  let raw;
2113
2144
  try {
2114
- raw = readFileSync8(paths2.projectId(root), "utf8");
2145
+ raw = readFileSync9(paths2.projectId(root), "utf8");
2115
2146
  } catch {
2116
2147
  return { state: "absent", id: null };
2117
2148
  }
@@ -2151,7 +2182,7 @@ function refreshableSeedKind(entry, emitContext) {
2151
2182
  async function writeWithConflict(abs, relPath, proposed, opts, internals) {
2152
2183
  let existing;
2153
2184
  try {
2154
- existing = readFileSync8(abs, "utf8");
2185
+ existing = readFileSync9(abs, "utf8");
2155
2186
  } catch {
2156
2187
  existing = void 0;
2157
2188
  }
@@ -2212,7 +2243,7 @@ function uniqueAside(abs, relPath, suffix) {
2212
2243
  rel: `${relPath}${s}`
2213
2244
  });
2214
2245
  let candidate = make(suffix);
2215
- for (let n = 1; existsSync7(candidate.abs); n++) candidate = make(`${suffix}.${n}`);
2246
+ for (let n = 1; existsSync8(candidate.abs); n++) candidate = make(`${suffix}.${n}`);
2216
2247
  return candidate;
2217
2248
  }
2218
2249
  function groupApplicableByPath(manifest, host, emitContext) {
@@ -2232,7 +2263,7 @@ function isNoirMdPath(relPath) {
2232
2263
  function healLegacyNoirMd(absPath) {
2233
2264
  let content;
2234
2265
  try {
2235
- content = readFileSync8(absPath, "utf8");
2266
+ content = readFileSync9(absPath, "utf8");
2236
2267
  } catch {
2237
2268
  return;
2238
2269
  }
@@ -2248,7 +2279,7 @@ function renderEntry(entry, vars) {
2248
2279
  }
2249
2280
  function readOptional(absPath) {
2250
2281
  try {
2251
- return readFileSync8(absPath, "utf8");
2282
+ return readFileSync9(absPath, "utf8");
2252
2283
  } catch (err) {
2253
2284
  if (err instanceof Error && err.code === "ENOENT") {
2254
2285
  return void 0;
@@ -2270,11 +2301,12 @@ export {
2270
2301
  buildManifest,
2271
2302
  buildRegion,
2272
2303
  detectStack,
2304
+ emitHooksToFile,
2273
2305
  ensureOwnerOnly,
2274
2306
  isStaleSeed,
2275
2307
  lineDiff,
2276
2308
  loadTemplate,
2277
- managedBlock2 as managedBlock,
2309
+ managedBlock,
2278
2310
  mergeThreeWay,
2279
2311
  readAncestors,
2280
2312
  readScaffoldVersion,