craftar 0.0.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,472 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ import { isDeepStrictEqual } from "node:util";
4
+ import YAML from "yaml";
5
+ import { fingerprintDir } from "./fingerprint.js";
6
+ import { exists, listFiles, loadForge, readIngredientText } from "./forge.js";
7
+ import { splitLines } from "./diff.js";
8
+ import { detectEol, withEol } from "./text.js";
9
+ // Spelled out rather than embedded as a raw character: in the one function whose job is byte
10
+ // fidelity, correctness should not hinge on a glyph no diff viewer, editor or re-encoding shows.
11
+ const BOM = String.fromCharCode(0xfeff);
12
+ /** Where a hunk sits, worded exactly as `forge diff` prints it. */
13
+ export function hunkAt(h) {
14
+ return h.a.lines.length ? `lines ${h.a.start}–${h.a.start + h.a.lines.length - 1}` : `after line ${h.a.start - 1}`;
15
+ }
16
+ const strictUtf8 = new TextDecoder("utf-8", { fatal: true });
17
+ /** Valid UTF-8, BOM included (a BOM is a valid UTF-8 sequence). */
18
+ function isUtf8(bytes) {
19
+ try {
20
+ strictUtf8.decode(bytes);
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ /** Every file of an ingredient but `ingredient.yaml`, as the raw bytes on disk. */
28
+ async function rawFilesOf(ing) {
29
+ const out = new Map();
30
+ for (const rel of await listFiles(ing.dir)) {
31
+ if (rel === "ingredient.yaml")
32
+ continue;
33
+ out.set(rel, await fs.readFile(path.join(ing.dir, rel)));
34
+ }
35
+ return out;
36
+ }
37
+ /**
38
+ * Ruling 31: the merge engine is text-only, and `diffIngredients` decodes every file as UTF-8 —
39
+ * which folds each invalid byte to U+FFFD, so two different non-UTF-8 files can look identical to
40
+ * it (and to every decision built on it). Compare the raw bytes of every file present on both
41
+ * sides instead; when they differ and either side is not valid UTF-8, refuse by name rather than
42
+ * merge or drop what the text view cannot see. `diffIngredients` itself is left alone: it also
43
+ * serves `forge diff` and `forge variants`.
44
+ */
45
+ export async function assertTextMergeable(base, variant) {
46
+ const a = await rawFilesOf(base);
47
+ const b = await rawFilesOf(variant);
48
+ for (const [rel, bytes] of a) {
49
+ const other = b.get(rel);
50
+ if (other === undefined || bytes.equals(other))
51
+ continue;
52
+ const bad = [!isUtf8(bytes) && "base", !isUtf8(other) && "variant"].filter(Boolean).join(" and ");
53
+ if (bad) {
54
+ throw new Error(`unify: "${rel}" differs between the base and the variant and is not valid UTF-8 in the ${bad} — ` +
55
+ `the merge engine is text-only and cannot merge it; resolve it by hand.`);
56
+ }
57
+ }
58
+ }
59
+ /** A plan with every decision deferred. The human edits it; nothing is assumed. */
60
+ export async function planFrom(base, variant, diff, profile) {
61
+ await assertTextMergeable(base, variant);
62
+ const files = [];
63
+ for (const f of diff.files) {
64
+ files.push({ file: f.file, hunks: f.hunks.map((h, i) => ({ hunk: i + 1, at: hunkAt(h), take: "keep" })) });
65
+ }
66
+ for (const file of diff.onlyInBase)
67
+ files.push({ file, onlyIn: "base", take: "keep" });
68
+ for (const file of diff.onlyInVariant)
69
+ files.push({ file, onlyIn: "variant", take: "keep" });
70
+ return {
71
+ schema: 1,
72
+ base: base.ref,
73
+ profile,
74
+ variant: variant.ref,
75
+ baseFingerprint: await fingerprintDir(base.dir),
76
+ variantFingerprint: await fingerprintDir(variant.dir),
77
+ files,
78
+ };
79
+ }
80
+ /** Metadata the two sides may legitimately disagree on: identity and provenance, not behaviour. */
81
+ function comparableMeta(meta) {
82
+ const rest = { ...meta };
83
+ delete rest.name;
84
+ delete rest.as;
85
+ delete rest.origin;
86
+ return rest;
87
+ }
88
+ /**
89
+ * Ruling 28: the top-level `ingredient.yaml` fields on which base and variant differ, sorted.
90
+ * `diffIngredients` leaves `ingredient.yaml` out, so without this a variant that differs only in
91
+ * its metadata (an MCP `server`, an agent's `model` or `tools`) would resolve with zero decisions
92
+ * and be deleted. Compared on the zod-validated `meta` with a real deep equality — never through
93
+ * `fingerprintOf` or `JSON.stringify(x, keys)`, whose key-array replacer filters at every depth
94
+ * and would see two different nested `server` objects as the same `{}`.
95
+ */
96
+ export function metaDifferences(base, variant) {
97
+ const a = comparableMeta(base.meta);
98
+ const b = comparableMeta(variant.meta);
99
+ return [...new Set([...Object.keys(a), ...Object.keys(b)])].filter((k) => !isDeepStrictEqual(a[k], b[k])).sort();
100
+ }
101
+ /**
102
+ * Rebuild one file by walking the base's lines and swapping in the variant's where chosen.
103
+ *
104
+ * Two things the base decides no matter what wins a hunk: its BOM and its line endings — the
105
+ * base is what survives, so both are read off `baseText` and restored around the merge, the same
106
+ * way `src/emitters/shared.ts` re-prepends a BOM onto a file it replaces.
107
+ *
108
+ * The final newline is the other subtlety: it comes from whichever side won the *last* hunk, and
109
+ * only when that hunk actually reaches the end of the file — i.e. no unchanged base line follows
110
+ * it. That "reaches the end" check has to be taken right after applying the last hunk, before the
111
+ * catch-up loop below copies any remaining base lines: that loop always drives `i` up to
112
+ * `A.lines.length`, so testing position after it would call every last hunk "at the tail" even
113
+ * when a common suffix follows it untouched — silently forcing (or dropping) a trailing newline
114
+ * a hunk in the middle of the file never decided.
115
+ *
116
+ * The decision itself is read off the whole file each side came from (`A.eofNewline` /
117
+ * `B.eofNewline`), not reconstructed from the winning hunk's own `noEofNewline` flag — that flag
118
+ * has nothing to say when the winning side contributes no lines (a pure removal taken as the
119
+ * winner), so `variantText` is passed in alongside the hunks for exactly this reason.
120
+ */
121
+ function mergeFile(baseText, variantText, hunks, takes) {
122
+ const bom = baseText.charCodeAt(0) === BOM.charCodeAt(0);
123
+ const A = splitLines(baseText);
124
+ const B = splitLines(variantText);
125
+ const out = [];
126
+ let i = 0; // 0-based index into A.lines
127
+ let iAfterLastHunk = 0;
128
+ let lastWinnerIsVariant = false;
129
+ hunks.forEach((h, k) => {
130
+ const start = h.a.start - 1; // a.start is 1-based and marks where the hunk applies
131
+ while (i < start)
132
+ out.push(A.lines[i++]);
133
+ const takeVariant = takes[k] === "variant";
134
+ const side = takeVariant ? h.b : h.a;
135
+ out.push(...side.lines);
136
+ i += h.a.lines.length; // the base's lines for this hunk are consumed either way
137
+ if (k === hunks.length - 1) {
138
+ iAfterLastHunk = i;
139
+ lastWinnerIsVariant = takeVariant;
140
+ }
141
+ });
142
+ while (i < A.lines.length)
143
+ out.push(A.lines[i++]);
144
+ // The tail decides the final newline only when the last hunk reaches the end of the file.
145
+ const endsAtTail = hunks.length > 0 && iAfterLastHunk >= A.lines.length;
146
+ const eofNewline = endsAtTail ? (lastWinnerIsVariant ? B.eofNewline : A.eofNewline) : A.eofNewline;
147
+ // An empty result is the empty string, not a lone newline — `out.join("\n")` on an empty array
148
+ // is already "", but the `+ eofNewline` term must not turn that into a false "\n" on its own.
149
+ const body = out.length ? out.join("\n") + (eofNewline ? "\n" : "") : "";
150
+ return (bom ? BOM : "") + withEol(body, detectEol(baseText));
151
+ }
152
+ /**
153
+ * Turn a plan's per-hunk, per-file decisions into the bytes to write. Only paths whose merged
154
+ * content differs from the base's current bytes appear in `write` — a plan of all-`base`
155
+ * decisions writes nothing, so `unify` never rewrites a file it did not change.
156
+ */
157
+ export async function applyPlan(base, variant, diff, plan, opts = {}) {
158
+ await assertTextMergeable(base, variant);
159
+ const write = {};
160
+ const remove = [];
161
+ let unresolved = 0;
162
+ const hunksByFile = new Map(diff.files.map((f) => [f.file, f.hunks]));
163
+ const onlyInBase = new Set(diff.onlyInBase);
164
+ const onlyInVariant = new Set(diff.onlyInVariant);
165
+ // Ruling 29 (spec §8 refusal 7, in both directions): the plan must cover every file the diff
166
+ // has — paired, base-only and variant-only — each exactly once. The walk below only visits the
167
+ // plan's own entries, so without this a plan stripped of an entry would apply as "resolved" and
168
+ // the caller would delete the variant together with the one copy of whatever was left out; two
169
+ // entries for the same file would let the last one silently win. Checked before any decision is
170
+ // read, so a contradiction never gets half-applied.
171
+ const planned = new Set();
172
+ for (const pf of plan.files) {
173
+ if (pf.hunks && pf.onlyIn) {
174
+ throw new Error(`unify plan: "${pf.file}" carries both hunk decisions and a side ("onlyIn: ${pf.onlyIn}") — the plan no longer matches this diff.`);
175
+ }
176
+ if (planned.has(pf.file)) {
177
+ throw new Error(`unify plan: "${pf.file}" has more than one entry — each file in the diff must be decided exactly once.`);
178
+ }
179
+ planned.add(pf.file);
180
+ }
181
+ for (const file of [...hunksByFile.keys(), ...onlyInBase, ...onlyInVariant]) {
182
+ if (!planned.has(file)) {
183
+ throw new Error(`unify plan: "${file}" differs between the base and the variant but has no entry in the plan — the plan no longer matches this diff.`);
184
+ }
185
+ }
186
+ for (const pf of plan.files) {
187
+ if (pf.hunks) {
188
+ const hunks = hunksByFile.get(pf.file);
189
+ if (!hunks)
190
+ throw new Error(`unify plan: no diff hunks recorded for paired file "${pf.file}"`);
191
+ // A plan is hand-edited YAML: bind each decision by its own `hunk` field, never by array
192
+ // position, so a reordered entry still lands on the hunk it names. A length mismatch, or a
193
+ // `hunk` value outside 1..n or repeated, means the plan no longer describes this diff —
194
+ // refuse rather than silently misapplying a decision to the wrong hunk or defaulting a gap
195
+ // to "base".
196
+ if (pf.hunks.length !== hunks.length) {
197
+ throw new Error(`unify plan: "${pf.file}" carries ${pf.hunks.length} hunk decision(s), but the diff has ${hunks.length} — the plan no longer matches this diff.`);
198
+ }
199
+ const takes = new Array(hunks.length);
200
+ const seen = new Set();
201
+ for (const ph of pf.hunks) {
202
+ if (ph.hunk < 1 || ph.hunk > hunks.length || seen.has(ph.hunk)) {
203
+ throw new Error(`unify plan: "${pf.file}" hunk index ${ph.hunk} is out of range or duplicated — expected each of 1..${hunks.length} exactly once.`);
204
+ }
205
+ seen.add(ph.hunk);
206
+ takes[ph.hunk - 1] = ph.take;
207
+ }
208
+ unresolved += takes.filter((t) => t === "keep").length;
209
+ // A plan with no `variant` decision cannot change this file's bytes — skip the merge
210
+ // entirely rather than round-tripping the base through `splitLines`/`withEol` for nothing,
211
+ // which would re-terminate a base with mixed line endings even though no decision moved it.
212
+ if (takes.includes("variant")) {
213
+ const baseText = await readIngredientText(base, pf.file);
214
+ const variantText = await readIngredientText(variant, pf.file);
215
+ const merged = mergeFile(baseText, variantText, hunks, takes);
216
+ if (merged !== baseText)
217
+ write[pf.file] = merged;
218
+ }
219
+ continue;
220
+ }
221
+ // A plan entry with neither `hunks` nor `onlyIn` describes nothing to apply — the schema
222
+ // leaves both optional (a hand-edited plan can drop either), so the engine is where this
223
+ // contradiction is caught, per spec §6: "it throws on contradiction."
224
+ if (!pf.onlyIn) {
225
+ throw new Error(`unify plan: "${pf.file}" has neither hunk decisions nor a side ("onlyIn") — the plan no longer matches this diff.`);
226
+ }
227
+ // One-sided file: present only in the base or only in the variant. Checked against the diff
228
+ // the same way a paired file's hunk count is checked above (spec §8 refusal 7: "names a file
229
+ // ... the diff does not have") — a hand-edited plan can name a file that is shared and
230
+ // identical (so the diff never lists it as one-sided at all, on either side) or has simply
231
+ // moved sides since the plan was saved. Left unchecked, `take: "variant"` on such an entry
232
+ // would fall through to `writeUnified`'s `fs.rm(..., { force: true })`, which deletes or
233
+ // silently no-ops on a path the engine never proposed touching — a Forge with no lock cannot
234
+ // afford that.
235
+ const onlySet = pf.onlyIn === "base" ? onlyInBase : onlyInVariant;
236
+ if (!onlySet.has(pf.file)) {
237
+ throw new Error(`unify plan: "${pf.file}" is recorded as present only in the ${pf.onlyIn}, but the diff does not have it there — the plan no longer matches this diff.`);
238
+ }
239
+ const take = pf.take ?? "keep";
240
+ if (take === "keep") {
241
+ unresolved += 1;
242
+ continue;
243
+ }
244
+ if (pf.onlyIn === "base") {
245
+ // take === "base" already matches the base as it stands; take === "variant" means the
246
+ // variant's absence of the file wins, so it comes out of the base.
247
+ if (take === "variant")
248
+ remove.push(pf.file);
249
+ }
250
+ else if (pf.onlyIn === "variant") {
251
+ // take === "base" already matches the base's absence of the file; take === "variant"
252
+ // brings the variant's copy in — read without an encoding, so the bytes written are the
253
+ // bytes on disk (Ruling 31). Valid UTF-8 round-trips exactly through a string, BOM
254
+ // included, so it is carried as text; anything else stays a Buffer, which a UTF-8
255
+ // decode/encode would corrupt.
256
+ if (take === "variant") {
257
+ const bytes = await fs.readFile(path.join(variant.dir, pf.file));
258
+ write[pf.file] = isUtf8(bytes) ? bytes.toString("utf8") : bytes;
259
+ }
260
+ }
261
+ }
262
+ const metaDiffers = opts.discardVariantMeta ? [] : metaDifferences(base, variant);
263
+ return { write, remove, resolved: unresolved === 0 && metaDiffers.length === 0, unresolved, metaDiffers };
264
+ }
265
+ async function note(journal, abs) {
266
+ if (journal)
267
+ journal.push({ abs, created: !(await exists(abs)) });
268
+ }
269
+ /**
270
+ * Put a `UnifyResult` on disk: write the merged files, delete the ones the plan resolved away.
271
+ * `ingredient.yaml` can never appear in either list — `diffIngredients` excludes it from both
272
+ * sides of the diff a plan is built from — so this function never touches an ingredient's
273
+ * metadata. Every path is noted in `journal`, when given, before it is touched.
274
+ *
275
+ * Returns every base-relative path touched, written or removed, sorted — what the CLI reports as
276
+ * "what changed."
277
+ */
278
+ export async function writeUnified(base, result, journal) {
279
+ for (const [rel, content] of Object.entries(result.write)) {
280
+ const abs = path.join(base.dir, rel);
281
+ await note(journal, abs);
282
+ await fs.mkdir(path.dirname(abs), { recursive: true });
283
+ await fs.writeFile(abs, content);
284
+ }
285
+ for (const rel of result.remove) {
286
+ const abs = path.join(base.dir, rel);
287
+ await note(journal, abs);
288
+ await fs.rm(abs, { force: true });
289
+ }
290
+ return [...Object.keys(result.write), ...result.remove].sort();
291
+ }
292
+ /**
293
+ * Find the recipe file under `recipes/` whose `name` field matches, by scanning rather than
294
+ * assuming `<name>.yaml` — `loadForge` keys `forge.recipes` by the `name` field *inside* the
295
+ * YAML, never by filename, so a recipe can legally live in a file named after something else.
296
+ * When two files declare the same `name`, the *last* one `readdir` returns wins — matching
297
+ * `loadForge`'s `recipes.set(r.name, r)`, which overwrites on every later match in the same
298
+ * directory order. There is no duplicate-name refusal for recipes the way there is for
299
+ * ingredients, so agreeing with `loadForge` is the only consistency this can offer.
300
+ */
301
+ async function findRecipeFile(recipesDir, name) {
302
+ let entries = [];
303
+ try {
304
+ entries = await fs.readdir(recipesDir);
305
+ }
306
+ catch {
307
+ entries = [];
308
+ }
309
+ let found;
310
+ for (const f of entries) {
311
+ if (!/\.ya?ml$/.test(f))
312
+ continue;
313
+ const abs = path.join(recipesDir, f);
314
+ let parsed;
315
+ try {
316
+ parsed = YAML.parse(await fs.readFile(abs, "utf8"));
317
+ }
318
+ catch {
319
+ continue;
320
+ }
321
+ if (parsed && typeof parsed === "object" && parsed.name === name)
322
+ found = abs;
323
+ }
324
+ if (!found)
325
+ throw new Error(`recipe "${name}" not found under ${recipesDir}`);
326
+ return found;
327
+ }
328
+ /**
329
+ * Read a YAML file for an in-place edit, capturing the EOL and BOM of the bytes on disk so the
330
+ * edit can restore them — `YAML.parseDocument` / `doc.toString()` always emit LF without a BOM,
331
+ * so a one-token change would otherwise turn into a whole-file diff in the git repo the user
332
+ * reviews. The BOM check mirrors the one `mergeFile` already does on `baseText` in this file:
333
+ * `charCodeAt(0)` against the BOM code point, rather than reading the file again as a `Buffer`
334
+ * for `hasBom`.
335
+ */
336
+ async function readYamlEdit(file) {
337
+ const raw = await fs.readFile(file, "utf8");
338
+ return { doc: YAML.parseDocument(raw), eol: detectEol(raw), bom: raw.charCodeAt(0) === BOM.charCodeAt(0) };
339
+ }
340
+ /** Serialize an edited document back with the original file's EOL and BOM, flow collections unpadded. */
341
+ function serializeYamlEdit(doc, eol, bom) {
342
+ return (bom ? BOM : "") + withEol(doc.toString({ flowCollectionPadding: false }), eol);
343
+ }
344
+ /**
345
+ * Replace every scalar entry equal to `from` with `to` inside the sequence at `key`, matched by
346
+ * value — never by rebuilding the sequence, so untouched entries, comments and formatting survive
347
+ * the round trip. Returns how many entries were replaced, so a caller that expected at least one
348
+ * (because the schema-parsed value it read off `loadForge` already contained `from`) can tell a
349
+ * no-op apart from a real edit — e.g. `key` resolving to an alias/anchor node rather than a plain
350
+ * `YAMLSeq`, which this function cannot rewrite safely and reports as zero.
351
+ */
352
+ function replaceSeqEntry(doc, key, from, to) {
353
+ const seq = doc.get(key, true);
354
+ if (!(seq instanceof YAML.YAMLSeq))
355
+ return 0;
356
+ let count = 0;
357
+ seq.items.forEach((item, i) => {
358
+ const value = item instanceof YAML.Scalar ? item.value : item;
359
+ if (value === from) {
360
+ seq.set(i, to);
361
+ count++;
362
+ }
363
+ });
364
+ return count;
365
+ }
366
+ /**
367
+ * Render an edit without writing it. `loadForge` already said the recipe's `ingredients` holds
368
+ * `from`; if the sequence edit still replaces nothing (an alias/anchor, for instance), that is a
369
+ * file unify cannot safely rewrite — throw rather than record a rewrite that did not happen
370
+ * (Ruling 14).
371
+ */
372
+ async function renderIngredientEdit(e) {
373
+ const { doc, eol, bom } = await readYamlEdit(e.file);
374
+ if (replaceSeqEntry(doc, "ingredients", e.from, e.to) === 0) {
375
+ throw new Error(`unify: recipe "${e.name}" (${e.file}) is recorded as naming ${e.from} in its "ingredients", ` +
376
+ `but no matching entry was found to rewrite (it may reach "ingredients" through a YAML alias/anchor) — refusing to report it as rewritten.`);
377
+ }
378
+ return serializeYamlEdit(doc, eol, bom);
379
+ }
380
+ /** Every recipe whose `ingredients` holds the variant, found by its `name` field (Ruling 9). */
381
+ async function ingredientEdits(forge, baseRef, variantRef) {
382
+ const recipesDir = path.join(forge.root, "recipes");
383
+ const out = [];
384
+ for (const [name, r] of forge.recipes) {
385
+ if (!r.ingredients.includes(variantRef))
386
+ continue;
387
+ out.push({ name, file: await findRecipeFile(recipesDir, name), from: variantRef, to: baseRef });
388
+ }
389
+ return out;
390
+ }
391
+ /**
392
+ * Ruling 42: among the rewritten recipes, each `<r>--<profile>` that is now identical to its
393
+ * unsuffixed sibling `<r>` — compared as `ingredients` (sorted), `extends`, `slot` and `params`,
394
+ * never `description`, which is prose. It is only reported: deleting it and repointing the lists
395
+ * that name it is safe only if `<r>` takes part in no other resolution — a transitive `extends`, a
396
+ * workspace's `recipes.add` — and param defaults are last-wins while recipes apply at their first
397
+ * occurrence, so either could shift. That cannot be proven from inside the Forge.
398
+ */
399
+ function identicalToSibling(recipes, rewritten, profile) {
400
+ const suffix = `--${profile}`;
401
+ const out = [];
402
+ for (const rn of rewritten) {
403
+ if (!rn.endsWith(suffix))
404
+ continue;
405
+ const suffixed = recipes.get(rn);
406
+ const sibling = recipes.get(rn.slice(0, -suffix.length));
407
+ if (!suffixed || !sibling)
408
+ continue;
409
+ const same = isDeepStrictEqual([...suffixed.ingredients].sort(), [...sibling.ingredients].sort()) &&
410
+ isDeepStrictEqual(suffixed.extends, sibling.extends) &&
411
+ suffixed.slot === sibling.slot &&
412
+ isDeepStrictEqual(suffixed.params, sibling.params);
413
+ if (same)
414
+ out.push(rn);
415
+ }
416
+ return out;
417
+ }
418
+ /**
419
+ * The cascade's dry pass (Ruling 33): render every `ingredients` rewrite `rewriteRecipes` will
420
+ * make and check that each one lands — alias checks included — writing nothing. A caller runs it
421
+ * before the first write of the whole operation, so a refusal found here leaves the Forge exactly
422
+ * as it was.
423
+ *
424
+ * Returns the recipe files the cascade will rewrite, absolute — the set the caller checks git
425
+ * actually holds before writing (Ruling 37). Since Ruling 42 that is all the cascade ever touches:
426
+ * it deletes no recipe and edits no profile or `extends`.
427
+ */
428
+ export async function checkRecipeCascade(forge, baseRef, variantRef) {
429
+ const files = [];
430
+ for (const edit of await ingredientEdits(forge, baseRef, variantRef)) {
431
+ await renderIngredientEdit(edit);
432
+ files.push(edit.file);
433
+ }
434
+ return files;
435
+ }
436
+ /**
437
+ * Follow the recipe cascade a resolved variant causes. Since Ruling 42 (a product decision) it is
438
+ * one pass: every recipe whose `ingredients` holds `variantRef` is rewritten to hold `baseRef`,
439
+ * which is required because the variant ingredient is about to be removed. It never deletes a
440
+ * recipe and never edits a profile or an `extends` list.
441
+ *
442
+ * It starts with `checkRecipeCascade`, the dry pass, so a rewrite that would not land throws with
443
+ * nothing written (Ruling 33). A caller that writes before calling this — the merged base, in
444
+ * `forge unify` — runs the dry pass itself first, for the same guarantee over the whole
445
+ * operation. What a dry pass cannot foresee (an I/O error, a file locked on Windows) can still
446
+ * fail midway; every path is noted in `journal`, when given, before it is touched.
447
+ *
448
+ * Each file is written back through `YAML.parseDocument` so only that one sequence entry changes
449
+ * (Ruling 10) — `RecipeSchema` materializes defaults a re-serialized schema object would write
450
+ * into a file that may have had far fewer keys, dropping comments too — with its EOL and BOM kept
451
+ * (Ruling 13). The caller orders this cascade *before* removing the variant's directory, so a late
452
+ * failure leaves the variant in place rather than a recipe naming a directory that no longer
453
+ * exists.
454
+ *
455
+ * Afterwards, against a reload of the Forge, it reports each rewritten `<r>--<profile>` now
456
+ * identical to its sibling `<r>` (see `identicalToSibling`) for a human to remove.
457
+ */
458
+ export async function rewriteRecipes(forge, baseRef, variantRef, profile, journal) {
459
+ await checkRecipeCascade(forge, baseRef, variantRef);
460
+ const rewritten = [];
461
+ for (const edit of await ingredientEdits(forge, baseRef, variantRef)) {
462
+ const content = await renderIngredientEdit(edit);
463
+ await note(journal, edit.file);
464
+ await fs.writeFile(edit.file, content);
465
+ rewritten.push(edit.name);
466
+ }
467
+ if (rewritten.length === 0)
468
+ return { rewritten, identicalToSibling: [] };
469
+ const reloaded = await loadForge(forge.root);
470
+ return { rewritten, identicalToSibling: identicalToSibling(reloaded.recipes, rewritten, profile) };
471
+ }
472
+ //# sourceMappingURL=unify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unify.js","sourceRoot":"","sources":["../../src/core/unify.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
- import { diffLines } from "./diff.js";
3
+ import { diffLines, splitLines } from "./diff.js";
4
4
  import { fingerprintDir } from "./fingerprint.js";
5
5
  import { listFiles } from "./forge.js";
6
6
  /**
@@ -48,44 +48,55 @@ async function distanceOf(base, variant) {
48
48
  const d = await diffIngredients(base, variant);
49
49
  const baseFiles = await filesOf(base);
50
50
  const variantFiles = await filesOf(variant);
51
- // A file present on one side only counts as one hunk carrying all of its lines, so a variant
52
- // that adds or removes a whole file never sorts as nearer than one with a small edit.
53
- const oneSided = [
54
- ...d.onlyInBase.map((f) => diffLines(baseFiles.get(f), "")),
55
- ...d.onlyInVariant.map((f) => diffLines("", variantFiles.get(f))),
56
- ];
57
- const lineCount = (hunks) => hunks.reduce((k, h) => k + h.a.lines.length + h.b.lines.length, 0);
51
+ // A file present on one side only counts as one hunk carrying all of its lines (spec 04,
52
+ // Ruling 9). Counted directly: diffing against "" mismatches a file with no final newline.
53
+ const oneSidedLines = d.onlyInBase.reduce((n, f) => n + splitLines(baseFiles.get(f)).lines.length, 0) +
54
+ d.onlyInVariant.reduce((n, f) => n + splitLines(variantFiles.get(f)).lines.length, 0);
55
+ // A hunk whose two sides carry the same lines exists only to carry the final-newline fact
56
+ // (diff.ts' forceTrailingHunk): no line content changed, so it costs 0 lines. It still counts
57
+ // as one hunk — it is a real difference — which keeps a newline-only variant nearer than any
58
+ // variant with a changed line instead of tied with it.
59
+ const sameLines = (h) => h.a.lines.length === h.b.lines.length && h.a.lines.every((line, k) => line === h.b.lines[k]);
60
+ const lineCount = (hunks) => hunks.reduce((k, h) => (sameLines(h) ? k : k + h.a.lines.length + h.b.lines.length), 0);
58
61
  const hunks = d.files.reduce((n, f) => n + f.hunks.length, 0) + d.onlyInBase.length + d.onlyInVariant.length;
59
- const lines = d.files.reduce((n, f) => n + lineCount(f.hunks), 0) + oneSided.reduce((n, h) => n + lineCount(h), 0);
62
+ const lines = d.files.reduce((n, f) => n + lineCount(f.hunks), 0) + oneSidedLines;
60
63
  const bodyDiffers = hunks > 0;
61
64
  const sameFingerprint = (await fingerprintDir(base.dir)) === (await fingerprintDir(variant.dir));
62
65
  return {
63
66
  lines,
64
67
  hunks,
65
- metaDiffers: !bodyDiffers && !sameFingerprint,
68
+ sameBodyDifferentMeta: !bodyDiffers && !sameFingerprint,
66
69
  identicalAfterNormalization: !bodyDiffers && sameFingerprint,
67
70
  };
68
71
  }
69
72
  const nearest = (x, y) => x.lines - y.lines || x.hunks - y.hunks;
70
- /** Bases that have at least one variant, nearest first. */
73
+ /** Bases that have at least one variant, nearest first, plus variants whose base is missing. */
71
74
  export async function listVariants(forge) {
72
75
  const groups = new Map();
76
+ const orphans = [];
73
77
  for (const ing of forge.ingredients.values()) {
74
78
  const profile = profileOf(ing.meta);
75
79
  if (!profile || !ing.meta.as)
76
80
  continue;
77
81
  const baseRef = `${ing.meta.type}/${ing.meta.as}`;
78
82
  const base = forge.ingredients.get(baseRef);
79
- if (!base)
83
+ if (!base) {
84
+ // A recipe including this still emits it under the base name, so silence would hide a
85
+ // broken Forge — the listing is where that state becomes visible.
86
+ orphans.push({ ref: ing.ref, profile, missingBase: baseRef });
80
87
  continue;
88
+ }
81
89
  const entry = { ref: ing.ref, profile, distance: await distanceOf(base, ing) };
82
90
  groups.set(baseRef, [...(groups.get(baseRef) ?? []), entry]);
83
91
  }
84
- return [...groups]
85
- .map(([base, variants]) => ({
86
- base,
87
- variants: variants.sort((x, y) => nearest(x.distance, y.distance) || x.ref.localeCompare(y.ref)),
88
- }))
89
- .sort((x, y) => nearest(x.variants[0].distance, y.variants[0].distance) || x.base.localeCompare(y.base));
92
+ return {
93
+ groups: [...groups]
94
+ .map(([base, variants]) => ({
95
+ base,
96
+ variants: variants.sort((x, y) => nearest(x.distance, y.distance) || x.ref.localeCompare(y.ref)),
97
+ }))
98
+ .sort((x, y) => nearest(x.variants[0].distance, y.variants[0].distance) || x.base.localeCompare(y.base)),
99
+ orphans: orphans.sort((x, y) => x.ref.localeCompare(y.ref)),
100
+ };
90
101
  }
91
102
  //# sourceMappingURL=variants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"variants.js","sourceRoot":"","sources":["../../src/core/variants.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"variants.js","sourceRoot":"","sources":["../../src/core/variants.ts"],"names":[],"mappings":"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"}