@noir-ai/create 1.19.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +32 -1
- package/dist/index.js +297 -265
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
35
|
-
import { join as
|
|
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
|
|
65
|
-
import { dirname as
|
|
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 =
|
|
72
|
-
return
|
|
267
|
+
const here = dirname4(fileURLToPath(import.meta.url));
|
|
268
|
+
return join4(here, "..", "templates");
|
|
73
269
|
}
|
|
74
270
|
function loadTemplate(name) {
|
|
75
|
-
return
|
|
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 =
|
|
83
|
-
var README_BLOCK =
|
|
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(
|
|
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 ||
|
|
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) ??
|
|
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
|
|
509
|
-
import { join as
|
|
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 =
|
|
976
|
-
if (
|
|
977
|
-
const prev =
|
|
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 =
|
|
1012
|
-
if (!
|
|
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 =
|
|
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 =
|
|
1061
|
-
if (!
|
|
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 =
|
|
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 (!
|
|
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(
|
|
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:
|
|
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 =
|
|
1194
|
-
if (
|
|
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(
|
|
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 = `"${
|
|
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 =
|
|
1246
|
-
if (!
|
|
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 =
|
|
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 (!
|
|
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(
|
|
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 =
|
|
1341
|
-
const command = host === "gemini" ? `"${
|
|
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
|
|
1383
|
-
import { basename as basename2, dirname as
|
|
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
|
|
1421
|
+
resolveNoirCommand as resolveNoirCommand3,
|
|
1391
1422
|
sha256Hex12
|
|
1392
1423
|
} from "@noir-ai/core";
|
|
1393
1424
|
|
|
1394
1425
|
// src/baseline.ts
|
|
1395
|
-
import { existsSync as
|
|
1396
|
-
import { join as
|
|
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 =
|
|
1426
|
-
if (
|
|
1456
|
+
const pjPath = join7(root, "package.json");
|
|
1457
|
+
if (existsSync6(pjPath)) {
|
|
1427
1458
|
let pj;
|
|
1428
1459
|
try {
|
|
1429
|
-
pj = JSON.parse(
|
|
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 =
|
|
1439
|
-
if (
|
|
1469
|
+
const makefile = join7(root, "Makefile");
|
|
1470
|
+
if (existsSync6(makefile)) {
|
|
1440
1471
|
try {
|
|
1441
|
-
const raw =
|
|
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) =>
|
|
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
|
|
1472
|
-
import { dirname as
|
|
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
|
|
1508
|
+
return join8(root, NOIR_DIR2, "scaffold-version");
|
|
1478
1509
|
}
|
|
1479
1510
|
function readScaffoldVersion(root) {
|
|
1480
1511
|
let raw;
|
|
1481
1512
|
try {
|
|
1482
|
-
raw =
|
|
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
|
-
|
|
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
|
|
1504
|
-
import { join as
|
|
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(
|
|
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 =
|
|
1542
|
-
if (
|
|
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) ||
|
|
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 (
|
|
1595
|
+
if (existsSync7(join9(root, "pnpm-workspace.yaml"))) {
|
|
1565
1596
|
monorepo = true;
|
|
1566
1597
|
packageManager = packageManager ?? "pnpm";
|
|
1567
1598
|
}
|
|
1568
|
-
if (
|
|
1599
|
+
if (existsSync7(join9(root, "turbo.json")) || existsSync7(join9(root, "nx.json"))) {
|
|
1569
1600
|
monorepo = true;
|
|
1570
1601
|
}
|
|
1571
1602
|
if (!packageManager) {
|
|
1572
|
-
if (
|
|
1573
|
-
else if (
|
|
1574
|
-
else if (
|
|
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 (
|
|
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 (
|
|
1579
|
-
const raw = safeRead(
|
|
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 (
|
|
1618
|
+
if (existsSync7(join9(root, "go.mod"))) {
|
|
1588
1619
|
languages.add("go");
|
|
1589
1620
|
packageManager = packageManager ?? "go-modules";
|
|
1590
1621
|
}
|
|
1591
|
-
if (
|
|
1622
|
+
if (existsSync7(join9(root, "Cargo.toml"))) {
|
|
1592
1623
|
languages.add("rust");
|
|
1593
|
-
const raw = safeRead(
|
|
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 &&
|
|
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 (
|
|
1635
|
-
else if (
|
|
1636
|
-
else if (
|
|
1637
|
-
else if (
|
|
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 =
|
|
1648
|
-
if (
|
|
1678
|
+
const full = join9(root, p);
|
|
1679
|
+
if (existsSync7(full)) existingAiFiles.push({ path: p, kind });
|
|
1649
1680
|
}
|
|
1650
|
-
const cursorRulesDir =
|
|
1681
|
+
const cursorRulesDir = join9(root, ".cursor", "rules");
|
|
1651
1682
|
try {
|
|
1652
|
-
if (
|
|
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
|
|
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 =
|
|
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(
|
|
1739
|
+
return ensureOwnerOnly(join10(paths2.noirDir(opts.root), ".env"));
|
|
1709
1740
|
}
|
|
1710
1741
|
function readConfiguredHost(root) {
|
|
1711
|
-
if (!
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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)
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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;
|
|
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 =
|
|
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
|
|
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
|
-
|
|
2309
|
+
managedBlock,
|
|
2278
2310
|
mergeThreeWay,
|
|
2279
2311
|
readAncestors,
|
|
2280
2312
|
readScaffoldVersion,
|