@hawkeyexl/manni 2.10.2 → 2.11.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.
@@ -8,7 +8,7 @@ var __export = (target, all) => {
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@hawkeyexl/manni",
11
- version: "2.10.2",
11
+ version: "2.11.1",
12
12
  description: "One command line for documentation that is meant to be checked. `manni meta` validates the presence and format of document metadata (Markdown frontmatter and more) against JSON Schema, built for CI. Formerly docmeta.",
13
13
  type: "module",
14
14
  bin: {
@@ -141,6 +141,99 @@ function matchesFileGlob(relPath, glob) {
141
141
  return m(relPath.replace(/\\/g, "/"));
142
142
  }
143
143
 
144
+ // src/shared/collections.ts
145
+ import { posix as posix2, win32 } from "node:path";
146
+
147
+ // src/shared/page-manifest.ts
148
+ import { readFileSync, realpathSync } from "node:fs";
149
+ import { isAbsolute, posix, relative, resolve, sep } from "node:path";
150
+ import fg from "fast-glob";
151
+ import { LineCounter, isMap, isNode, isScalar, parseDocument } from "yaml";
152
+ var PAGE_PLACEHOLDER = "{page}";
153
+ function hasPagePlaceholder(file) {
154
+ return file.includes(PAGE_PLACEHOLDER);
155
+ }
156
+ function slashes(path) {
157
+ return path.split(sep).join("/").replace(/\\/g, "/");
158
+ }
159
+ function normalizeManifestPattern(file) {
160
+ const forward = file.replace(/\\/g, "/");
161
+ return forward.startsWith("./") ? forward.slice(2) : forward;
162
+ }
163
+ function pageManifestPath(file, configDir, pageAbs) {
164
+ const between = relative(configDir, pageAbs);
165
+ const pageRel = slashes(between);
166
+ if (isAbsolute(between) || pageRel === ".." || pageRel.startsWith("../")) {
167
+ return { outside: true, pageRel };
168
+ }
169
+ const ext = posix.extname(pageRel);
170
+ const page = ext === "" ? pageRel : pageRel.slice(0, -ext.length);
171
+ const named = normalizeManifestPattern(file).replace(PAGE_PLACEHOLDER, () => page);
172
+ const abs = resolve(configDir, named);
173
+ return { abs, rel: slashes(relative(configDir, abs)) };
174
+ }
175
+ function strayManifestGlob(file) {
176
+ const normalized = normalizeManifestPattern(file);
177
+ const at2 = normalized.indexOf(PAGE_PLACEHOLDER);
178
+ if (at2 < 0) return literal(normalized);
179
+ const prefix = literal(normalized.slice(0, at2));
180
+ const suffix = literal(normalized.slice(at2 + PAGE_PLACEHOLDER.length));
181
+ return `${prefix}**/*${suffix}`;
182
+ }
183
+ function literal(text2) {
184
+ return text2 === "" ? "" : fg.escapePath(text2);
185
+ }
186
+ function findPageManifests(file, configDir, isMember2) {
187
+ const matched = fg.sync(strayManifestGlob(file), {
188
+ cwd: configDir,
189
+ absolute: true,
190
+ onlyFiles: true,
191
+ dot: false,
192
+ ignore: ["**/node_modules/**", "**/.git/**"]
193
+ }).map((path) => resolve(path)).sort();
194
+ const found = [];
195
+ for (const abs of matched) {
196
+ const entries = ownEntries(abs, file, configDir, isMember2);
197
+ if (entries.length > 0) found.push({ abs, entries });
198
+ }
199
+ return found;
200
+ }
201
+ function ownEntries(abs, file, configDir, isMember2) {
202
+ let text2;
203
+ try {
204
+ text2 = readFileSync(abs, "utf8");
205
+ } catch {
206
+ return [];
207
+ }
208
+ const lc = new LineCounter();
209
+ const doc = parseDocument(text2, { lineCounter: lc, uniqueKeys: false });
210
+ const root = doc.contents;
211
+ if (doc.errors.length > 0 || !isMap(root)) return [];
212
+ const entries = [];
213
+ for (const pair of root.items) {
214
+ const spelled = isScalar(pair.key) ? String(pair.key.value) : String(pair.key);
215
+ const pageAbs = resolve(configDir, spelled);
216
+ if (!isMember2(slashes(relative(configDir, pageAbs)))) continue;
217
+ const resolved = pageManifestPath(file, configDir, pageAbs);
218
+ if ("outside" in resolved || !sameFile(resolved.abs, abs)) continue;
219
+ const range = isNode(pair.key) ? pair.key.range : void 0;
220
+ entries.push({
221
+ spelled,
222
+ pageAbs,
223
+ ...range ? { line: lc.linePos(range[0]).line } : {}
224
+ });
225
+ }
226
+ return entries;
227
+ }
228
+ function sameFile(a, b) {
229
+ if (a === b) return true;
230
+ try {
231
+ return realpathSync(a) === realpathSync(b);
232
+ } catch {
233
+ return false;
234
+ }
235
+ }
236
+
144
237
  // src/shared/collections.ts
145
238
  var COLLECTION_KEYS = [
146
239
  "name",
@@ -150,6 +243,7 @@ var COLLECTION_KEYS = [
150
243
  "externalMetadata"
151
244
  ];
152
245
  var EXTERNAL_METADATA_KEYS = ["file", "keys", "tokenEnv", "join"];
246
+ var PATH_JOIN = "path";
153
247
  var SCHEMA_KEY = "$schema";
154
248
  var STDIN = "<stdin>";
155
249
  function isUrlRef(ref) {
@@ -285,6 +379,7 @@ function isHttpUrl(value) {
285
379
  }
286
380
  function parseExternalMetadata(raw, collectionWhere, source, toError) {
287
381
  const owners = /* @__PURE__ */ new Map();
382
+ const patterns = /* @__PURE__ */ new Map();
288
383
  return raw.map((entry, i) => {
289
384
  const where = `${collectionWhere}.externalMetadata[${i}]`;
290
385
  if (!isMapping(entry)) {
@@ -296,6 +391,16 @@ function parseExternalMetadata(raw, collectionWhere, source, toError) {
296
391
  `${source}: ${where}.file must be a non-empty string naming the manifest, relative to the config file.`
297
392
  );
298
393
  }
394
+ const placeholderProblem = pagePlaceholderProblem(entry.file);
395
+ if (placeholderProblem !== null) {
396
+ throw toError(`${source}: ${where}.file ${placeholderProblem}`);
397
+ }
398
+ const perPage = hasPagePlaceholder(entry.file);
399
+ if (perPage && (posix2.isAbsolute(entry.file) || win32.isAbsolute(entry.file))) {
400
+ throw toError(
401
+ `${source}: ${where}.file uses ${PAGE_PLACEHOLDER} in an absolute path, so every manifest would sit outside the repository. Make it relative to the config file.`
402
+ );
403
+ }
299
404
  if (!Array.isArray(entry.keys) || entry.keys.length === 0 || entry.keys.some((k) => !isNonEmptyString(k))) {
300
405
  throw toError(
301
406
  `${source}: ${where}.keys must be a non-empty list of key names.`
@@ -307,6 +412,11 @@ function parseExternalMetadata(raw, collectionWhere, source, toError) {
307
412
  if (problem !== null) {
308
413
  throw toError(`${source}: ${where}.file ${problem}.`);
309
414
  }
415
+ if (perPage) {
416
+ throw toError(
417
+ `${source}: ${where}.file uses ${PAGE_PLACEHOLDER} in a URL. A placeholder names a file this run can write, and a URL is not one.`
418
+ );
419
+ }
310
420
  }
311
421
  if (entry.tokenEnv !== void 0) {
312
422
  if (!isNonEmptyString(entry.tokenEnv)) {
@@ -336,6 +446,11 @@ function parseExternalMetadata(raw, collectionWhere, source, toError) {
336
446
  );
337
447
  }
338
448
  }
449
+ if (perPage && typeof entry.join === "string" && entry.join !== PATH_JOIN) {
450
+ throw toError(
451
+ `${source}: ${where}.file uses ${PAGE_PLACEHOLDER}, and join is "${entry.join}". A per-page manifest is found by path, so it cannot be keyed by a field. Remove ${PAGE_PLACEHOLDER}, or remove join.`
452
+ );
453
+ }
339
454
  const seen = /* @__PURE__ */ new Set();
340
455
  for (const key of keys) {
341
456
  if (key === SCHEMA_KEY) {
@@ -355,14 +470,46 @@ function parseExternalMetadata(raw, collectionWhere, source, toError) {
355
470
  }
356
471
  owners.set(key, i);
357
472
  }
358
- return {
473
+ if (perPage) {
474
+ const pattern = normalizeManifestPattern(entry.file);
475
+ const first = patterns.get(pattern);
476
+ if (first !== void 0) {
477
+ throw toError(
478
+ `${source}: ${where}.file resolves to the same manifest as externalMetadata[${String(first)}].file for every page. Give each entry its own file name.`
479
+ );
480
+ }
481
+ patterns.set(pattern, i);
482
+ }
483
+ const parsed2 = {
359
484
  file: entry.file,
360
485
  keys,
361
486
  ...typeof entry.tokenEnv === "string" ? { tokenEnv: entry.tokenEnv } : {},
362
487
  ...typeof entry.join === "string" ? { join: entry.join } : {}
363
488
  };
489
+ origins.set(parsed2, { source, where });
490
+ return parsed2;
364
491
  });
365
492
  }
493
+ function pagePlaceholderProblem(file) {
494
+ for (const match of file.matchAll(/\{[^{}]*\}/g)) {
495
+ if (match[0] !== PAGE_PLACEHOLDER) {
496
+ return `uses "${match[0]}", which is not a placeholder. The only placeholder is ${PAGE_PLACEHOLDER}.`;
497
+ }
498
+ }
499
+ const tokens2 = file.split(PAGE_PLACEHOLDER);
500
+ const stray = /[{}]/.exec(tokens2.join(""));
501
+ if (stray !== null) {
502
+ return `contains "${stray[0]}" outside a ${PAGE_PLACEHOLDER} placeholder. Remove it, or write ${PAGE_PLACEHOLDER}.`;
503
+ }
504
+ if (tokens2.length > 2) {
505
+ return `uses ${PAGE_PLACEHOLDER} twice. One manifest names one page.`;
506
+ }
507
+ return null;
508
+ }
509
+ var origins = /* @__PURE__ */ new WeakMap();
510
+ function externalMetadataOrigin(entry) {
511
+ return origins.get(entry);
512
+ }
366
513
  function selectCollections(collections, names, source, toError) {
367
514
  if (names === void 0 || names.length === 0) return [...collections];
368
515
  const wanted = new Set(names);
@@ -383,6 +530,9 @@ function selectCollections(collections, names, source, toError) {
383
530
  function isMember(collection, relPath) {
384
531
  if (relPath === STDIN) return false;
385
532
  if (relPath.startsWith("../")) return false;
533
+ return matchesCollectionPaths(collection, relPath);
534
+ }
535
+ function matchesCollectionPaths(collection, relPath) {
386
536
  const matched = collection.paths.some((entry) => {
387
537
  const normalized = normalizeEntry(entry);
388
538
  if (normalized === "") return false;
@@ -393,15 +543,15 @@ function isMember(collection, relPath) {
393
543
  return !collection.exclude.some((glob) => matchesFileGlob(relPath, glob));
394
544
  }
395
545
  function normalizeEntry(entry) {
396
- const posix5 = entry.replace(/\\/g, "/").replace(/^\.\//, "");
397
- return posix5.endsWith("/") ? posix5.slice(0, -1) : posix5;
546
+ const posix7 = entry.replace(/\\/g, "/").replace(/^\.\//, "");
547
+ return posix7.endsWith("/") ? posix7.slice(0, -1) : posix7;
398
548
  }
399
549
  function isGlob(entry) {
400
550
  return picomatch2.scan(entry).isGlob;
401
551
  }
402
552
 
403
553
  // src/shared/run.ts
404
- import { realpathSync } from "node:fs";
554
+ import { realpathSync as realpathSync2 } from "node:fs";
405
555
  import { fileURLToPath } from "node:url";
406
556
  import { CommanderError } from "commander";
407
557
 
@@ -472,7 +622,7 @@ function isMainModule(moduleUrl) {
472
622
  const entry = process.argv[1];
473
623
  if (!entry) return false;
474
624
  try {
475
- return realpathSync(entry) === fileURLToPath(moduleUrl);
625
+ return realpathSync2(entry) === fileURLToPath(moduleUrl);
476
626
  } catch {
477
627
  return false;
478
628
  }
@@ -532,7 +682,7 @@ function diagnoseIntegrity(bytes, expected) {
532
682
  }
533
683
 
534
684
  // src/meta/extractors/frontmatter.ts
535
- import { parseDocument, LineCounter, isMap, isSeq, isScalar } from "yaml";
685
+ import { parseDocument as parseDocument2, LineCounter as LineCounter2, isMap as isMap2, isSeq, isScalar as isScalar2 } from "yaml";
536
686
  import { parse as parseToml } from "smol-toml";
537
687
 
538
688
  // src/meta/extractors/date-value.ts
@@ -612,9 +762,9 @@ function buildLineMap(doc, lc, prefixLines) {
612
762
  map.set("", 1);
613
763
  const lineAt = (offset) => offset == null ? void 0 : lc.linePos(offset).line + prefixLines;
614
764
  const walk = (node, pointer) => {
615
- if (isMap(node)) {
765
+ if (isMap2(node)) {
616
766
  for (const pair of node.items) {
617
- const key = isScalar(pair.key) ? String(pair.key.value) : String(pair.key);
767
+ const key = isScalar2(pair.key) ? String(pair.key.value) : String(pair.key);
618
768
  const ptr = `${pointer}/${escapePointerSegment(key)}`;
619
769
  const line = lineAt(
620
770
  pair.key?.range?.[0]
@@ -668,8 +818,8 @@ function rootObject(parsed2, label) {
668
818
  throw new Error(`Invalid ${label} frontmatter: root must be an object`);
669
819
  }
670
820
  function parseYamlBlock(raw, prefixLines, format) {
671
- const lc = new LineCounter();
672
- const doc = parseDocument(raw, { lineCounter: lc });
821
+ const lc = new LineCounter2();
822
+ const doc = parseDocument2(raw, { lineCounter: lc });
673
823
  if (doc.errors.length > 0) {
674
824
  const e = doc.errors[0];
675
825
  throw new Error(`Invalid YAML frontmatter: ${e?.message ?? "parse error"}`);
@@ -693,8 +843,8 @@ function parseJsonBlock(raw, prefixLines, format) {
693
843
  );
694
844
  }
695
845
  const data = rootObject(parsed2, "JSON");
696
- const lc = new LineCounter();
697
- const doc = parseDocument(raw, { lineCounter: lc });
846
+ const lc = new LineCounter2();
847
+ const doc = parseDocument2(raw, { lineCounter: lc });
698
848
  const map = buildLineMap(doc, lc, prefixLines);
699
849
  return { data, present: true, format, lineFor: positionForFactory(map) };
700
850
  }
@@ -751,7 +901,12 @@ var DocmetaError = class extends ToolError {
751
901
  };
752
902
 
753
903
  // src/meta/extractors/frontmatter-write.ts
754
- import { parseDocument as parseDocument2, isMap as isMap2, isCollection } from "yaml";
904
+ import {
905
+ parseDocument as parseDocument3,
906
+ isMap as isMap4,
907
+ isCollection,
908
+ isScalar as isScalar4
909
+ } from "yaml";
755
910
  import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
756
911
 
757
912
  // src/meta/extractors/patch-util.ts
@@ -797,6 +952,174 @@ function toJsonText(value) {
797
952
  return JSON.stringify(value);
798
953
  }
799
954
 
955
+ // src/meta/core/yaml-splice.ts
956
+ import {
957
+ isMap as isMap3,
958
+ isNode as isNode2,
959
+ isPair,
960
+ isScalar as isScalar3,
961
+ isSeq as isSeq2,
962
+ stringify
963
+ } from "yaml";
964
+ function insertKey(text2, entryMap, key, value, style) {
965
+ const firstKey = rangeOf(entryMap.items[0]?.key);
966
+ const end = contentEnd(text2, entryMap);
967
+ if (!firstKey || end === void 0) return void 0;
968
+ const pos = lineEnd(text2, end);
969
+ return {
970
+ start: pos,
971
+ end: pos,
972
+ insert: style.eol + blockLines({ [key]: value }, colOf(text2, firstKey[0]), style)
973
+ };
974
+ }
975
+ function replaceBlockValue(text2, pair, value, style) {
976
+ const colon = colonEnd(text2, pair);
977
+ const keyRange = rangeOf(pair.key);
978
+ if (colon === void 0 || !keyRange) return void 0;
979
+ const keyCol = colOf(text2, keyRange[0]);
980
+ let start = colon;
981
+ let ownLine = false;
982
+ let i = colon;
983
+ while (text2.charAt(i) === " " || text2.charAt(i) === " ") i++;
984
+ if (text2.charAt(i) === "#") {
985
+ start = lineEnd(text2, i);
986
+ ownLine = true;
987
+ }
988
+ const old = pair.value;
989
+ const oldEnd = isScalar3(old) && old.value === null && emptySource(text2, old) ? start : contentEnd(text2, old) ?? start;
990
+ const end = Math.max(start, oldEnd);
991
+ let insert = afterColon(value, keyCol, style);
992
+ if (ownLine && !insert.startsWith(style.eol)) {
993
+ insert = style.eol + " ".repeat(keyCol + style.step) + insert.trimStart();
994
+ }
995
+ return { start, end, insert };
996
+ }
997
+ function replaceFlowValue(text2, pair, value, padded) {
998
+ const r = rangeOf(pair.value);
999
+ if (!r || emptySource(text2, pair.value)) return void 0;
1000
+ const end = contentEnd(text2, pair.value) ?? r[1];
1001
+ return { start: r[0], end, insert: flowText(value, padded) };
1002
+ }
1003
+ function keyLines(text2, from, to, eol) {
1004
+ let start = text2.lastIndexOf("\n", from - 1) + 1;
1005
+ let end = lineEnd(text2, to);
1006
+ if (text2.startsWith(eol, end)) end += eol.length;
1007
+ else if (start >= eol.length) start -= eol.length;
1008
+ return { start, end };
1009
+ }
1010
+ function removeKey(text2, pair, eol) {
1011
+ const from = rangeOf(pair.key);
1012
+ const to = contentEnd(text2, pair.value) ?? contentEnd(text2, pair.key);
1013
+ if (!from || to === void 0) return void 0;
1014
+ const { start, end } = keyLines(text2, from[0], to, eol);
1015
+ return { start, end, insert: "" };
1016
+ }
1017
+ function blockLines(pair, col, style) {
1018
+ const s = stringify(pair, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
1019
+ return indentLines(s.replace(/\n$/, "").split("\n"), col, 0).join(style.eol);
1020
+ }
1021
+ function afterColon(value, col, style) {
1022
+ const s = stringify({ k: value }, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
1023
+ const lines = s.replace(/\n$/, "").slice("k:".length).split("\n");
1024
+ return indentLines(lines, col, 1).join(style.eol);
1025
+ }
1026
+ function indentLines(lines, col, from) {
1027
+ const pad2 = " ".repeat(col);
1028
+ return lines.map((l, i) => i < from || l === "" ? l : pad2 + l);
1029
+ }
1030
+ function flowText(value, padded) {
1031
+ const s = stringify(value, {
1032
+ lineWidth: 0,
1033
+ collectionStyle: "flow",
1034
+ ...padded === void 0 ? {} : { flowCollectionPadding: padded }
1035
+ }).replace(/\n$/, "");
1036
+ return s.includes("\n") ? JSON.stringify(value) : s;
1037
+ }
1038
+ function detectEol(text2) {
1039
+ const i = text2.indexOf("\n");
1040
+ return i > 0 && text2.charAt(i - 1) === "\r" ? "\r\n" : "\n";
1041
+ }
1042
+ function detectStyle(text2, root, eol) {
1043
+ let step = 2;
1044
+ for (const pair of root.items) {
1045
+ const v = pair.value;
1046
+ const entryKey = rangeOf(pair.key);
1047
+ const ownedKey = isMap3(v) && v.flow !== true ? rangeOf(v.items[0]?.key) : void 0;
1048
+ if (entryKey && ownedKey) {
1049
+ const d = colOf(text2, ownedKey[0]) - colOf(text2, entryKey[0]);
1050
+ if (d > 0) {
1051
+ step = d;
1052
+ break;
1053
+ }
1054
+ }
1055
+ }
1056
+ return { step, indentSeq: seqIndented(text2, root) ?? true, eol };
1057
+ }
1058
+ function seqIndented(text2, node) {
1059
+ if (isMap3(node)) {
1060
+ for (const pair of node.items) {
1061
+ const v = pair.value;
1062
+ if (isSeq2(v) && v.flow !== true && v.items.length > 0) {
1063
+ const k = rangeOf(pair.key);
1064
+ const s = rangeOf(v);
1065
+ if (k && s) return colOf(text2, s[0]) > colOf(text2, k[0]);
1066
+ }
1067
+ const inner = seqIndented(text2, v);
1068
+ if (inner !== void 0) return inner;
1069
+ }
1070
+ } else if (isSeq2(node)) {
1071
+ for (const item of node.items) {
1072
+ const inner = seqIndented(text2, item);
1073
+ if (inner !== void 0) return inner;
1074
+ }
1075
+ }
1076
+ return void 0;
1077
+ }
1078
+ function contentEnd(text2, node) {
1079
+ if (isMap3(node) && node.flow !== true) {
1080
+ const last = node.items.at(-1);
1081
+ if (last) return contentEnd(text2, last.value) ?? contentEnd(text2, last.key);
1082
+ } else if (isSeq2(node) && node.flow !== true) {
1083
+ const last = node.items.at(-1);
1084
+ if (last !== void 0) {
1085
+ return isPair(last) ? contentEnd(text2, last.value) ?? contentEnd(text2, last.key) : contentEnd(text2, last);
1086
+ }
1087
+ }
1088
+ const r = rangeOf(node);
1089
+ if (!r) return void 0;
1090
+ let end = r[1];
1091
+ while (end > r[0] && /\s/.test(text2.charAt(end - 1))) end--;
1092
+ return end;
1093
+ }
1094
+ function colonEnd(text2, pair) {
1095
+ const r = rangeOf(pair.key);
1096
+ if (!r) return void 0;
1097
+ let i = r[1];
1098
+ while (text2.charAt(i) === " " || text2.charAt(i) === " ") i++;
1099
+ return text2.charAt(i) === ":" ? i + 1 : void 0;
1100
+ }
1101
+ function lineEnd(text2, i) {
1102
+ const j = text2.indexOf("\n", i);
1103
+ if (j < 0) return text2.length;
1104
+ return j > 0 && text2.charAt(j - 1) === "\r" ? j - 1 : j;
1105
+ }
1106
+ function colOf(text2, offset) {
1107
+ if (offset === 0) return 0;
1108
+ let start = text2.lastIndexOf("\n", offset - 1) + 1;
1109
+ if (start === 0 && text2.startsWith("\uFEFF")) start = 1;
1110
+ return offset - start;
1111
+ }
1112
+ function rangeOf(node) {
1113
+ return isNode2(node) ? node.range ?? void 0 : void 0;
1114
+ }
1115
+ function emptySource(text2, node) {
1116
+ const r = rangeOf(node);
1117
+ return !r || text2.slice(r[0], r[1]).trim() === "";
1118
+ }
1119
+ function keyString(key) {
1120
+ return isScalar3(key) ? String(key.value) : String(key);
1121
+ }
1122
+
800
1123
  // src/meta/extractors/frontmatter-write.ts
801
1124
  var FENCE = {
802
1125
  yaml: "---",
@@ -874,17 +1197,91 @@ function mergeBlock(flavor, inner, patch, deletions = []) {
874
1197
  }
875
1198
  }
876
1199
  function mergeYaml(inner, patch, deletions = []) {
877
- const doc = parseDocument2(inner);
878
- if (doc.errors.length > 0) {
1200
+ const parsed2 = parseYamlBlock2(inner);
1201
+ const root = parsed2.contents;
1202
+ if (root != null && !isMap4(root)) {
879
1203
  throw new DocmetaError(
880
- `Cannot rewrite invalid YAML front matter: ${doc.errors[0]?.message ?? "parse error"}`
1204
+ "Cannot rewrite front matter: the root must be a mapping."
881
1205
  );
882
1206
  }
883
- if (doc.contents != null && !isMap2(doc.contents)) {
1207
+ if (isMap4(root) && root.flow === true) {
1208
+ return reemitYaml(inner, patch, deletions);
1209
+ }
1210
+ let text2 = inner;
1211
+ let first = parsed2;
1212
+ for (const [key, value] of Object.entries(patch)) {
1213
+ const next = spliceYamlKey(text2, key, value, first);
1214
+ first = void 0;
1215
+ if (next === void 0) return reemitYaml(inner, patch, deletions);
1216
+ text2 = next;
1217
+ }
1218
+ for (const key of deletions) {
1219
+ const next = removeYamlKey(text2, key);
1220
+ if (next === void 0) return reemitYaml(inner, patch, deletions);
1221
+ text2 = next;
1222
+ }
1223
+ return text2;
1224
+ }
1225
+ function parseYamlBlock2(text2) {
1226
+ const doc = parseDocument3(text2);
1227
+ if (doc.errors.length > 0) {
884
1228
  throw new DocmetaError(
885
- "Cannot rewrite front matter: the root must be a mapping."
1229
+ `Cannot rewrite invalid YAML front matter: ${doc.errors[0]?.message ?? "parse error"}`
886
1230
  );
887
1231
  }
1232
+ return doc;
1233
+ }
1234
+ function rootPair(root, key) {
1235
+ return root.items.find((p) => keyString(p.key) === key);
1236
+ }
1237
+ function spliceYamlKey(text2, key, value, parsed2 = parseYamlBlock2(text2)) {
1238
+ const root = parsed2.contents;
1239
+ const eol = detectEol(text2);
1240
+ if (root == null) {
1241
+ const lines = blockLines({ [key]: value }, 0, { step: 2, indentSeq: true, eol });
1242
+ if (text2 === "") return lines;
1243
+ return text2.endsWith("\n") ? text2 + lines : text2 + eol + lines;
1244
+ }
1245
+ if (!isMap4(root) || root.flow === true) return void 0;
1246
+ const style = detectStyle(text2, root, eol);
1247
+ const pair = rootPair(root, key);
1248
+ let edit;
1249
+ if (pair === void 0) {
1250
+ edit = insertKey(text2, root, key, value, style);
1251
+ } else {
1252
+ const old = pair.value;
1253
+ const isCollectionValue = value !== null && typeof value === "object";
1254
+ if (isCollectionValue && isCollection(old) && old.flow === true) {
1255
+ const r = rangeOf(old);
1256
+ const padded = r !== void 0 && /^[[{][ \t]/.test(text2.slice(r[0], r[0] + 2));
1257
+ edit = replaceFlowValue(text2, pair, value, padded);
1258
+ } else if (!isCollectionValue && isScalar4(old)) {
1259
+ const node = old.clone();
1260
+ if (!isScalar4(node)) return void 0;
1261
+ node.value = value;
1262
+ node.comment = void 0;
1263
+ node.commentBefore = void 0;
1264
+ node.spaceBefore = void 0;
1265
+ edit = replaceBlockValue(text2, pair, node, style);
1266
+ } else {
1267
+ edit = replaceBlockValue(text2, pair, value, style);
1268
+ }
1269
+ }
1270
+ if (edit === void 0) return void 0;
1271
+ return text2.slice(0, edit.start) + edit.insert + text2.slice(edit.end);
1272
+ }
1273
+ function removeYamlKey(text2, key) {
1274
+ const root = parseYamlBlock2(text2).contents;
1275
+ if (!isMap4(root)) return text2;
1276
+ if (root.flow === true) return void 0;
1277
+ const pair = rootPair(root, key);
1278
+ if (pair === void 0) return text2;
1279
+ const edit = removeKey(text2, pair, detectEol(text2));
1280
+ if (edit === void 0) return void 0;
1281
+ return text2.slice(0, edit.start) + text2.slice(edit.end);
1282
+ }
1283
+ function reemitYaml(inner, patch, deletions) {
1284
+ const doc = parseYamlBlock2(inner);
888
1285
  for (const [key, value] of Object.entries(patch)) {
889
1286
  if (value !== null && typeof value === "object") {
890
1287
  const prev = doc.get(key, true);
@@ -1041,7 +1438,7 @@ function parseBlock(flavor, text2) {
1041
1438
  case "toml":
1042
1439
  return text2.trim() === "" ? {} : parseToml2(text2);
1043
1440
  case "yaml": {
1044
- const doc = parseDocument2(text2);
1441
+ const doc = parseDocument3(text2);
1045
1442
  if (doc.errors.length > 0) {
1046
1443
  throw new DocmetaError(
1047
1444
  `Cannot rewrite invalid YAML front matter: ${doc.errors[0]?.message ?? "parse error"}`
@@ -1308,8 +1705,8 @@ function ditaShape(content, root, filePath) {
1308
1705
  const lower = filePath?.toLowerCase() ?? "";
1309
1706
  const byExtension2 = (lower.endsWith(".dita") || lower.endsWith(".ditamap")) && DITA_ROOTS.has(name2);
1310
1707
  if (!byDoctype && !byClass && !byArch && !byExtension2) return void 0;
1311
- const isMap9 = /^\s*[-+]\s+map\//.test(cls) || MAP_ROOTS.has(name2) || lower.endsWith(".ditamap");
1312
- return isMap9 ? { kind: "map", container: "topicmeta", preamble: /* @__PURE__ */ new Set(["title"]) } : {
1708
+ const isMap11 = /^\s*[-+]\s+map\//.test(cls) || MAP_ROOTS.has(name2) || lower.endsWith(".ditamap");
1709
+ return isMap11 ? { kind: "map", container: "topicmeta", preamble: /* @__PURE__ */ new Set(["title"]) } : {
1313
1710
  kind: "topic",
1314
1711
  container: "metadata",
1315
1712
  preamble: /* @__PURE__ */ new Set([
@@ -2731,8 +3128,8 @@ function listFormats() {
2731
3128
  }
2732
3129
 
2733
3130
  // src/meta/core/write-file.ts
2734
- import { writeFile, rename, rm, stat as stat2, chmod } from "node:fs/promises";
2735
- import { dirname, join, basename, resolve } from "node:path";
3131
+ import { writeFile, rename, rm, stat as stat2, chmod, mkdir } from "node:fs/promises";
3132
+ import { dirname, join, basename, resolve as resolve2 } from "node:path";
2736
3133
 
2737
3134
  // src/meta/core/manifest-cache.ts
2738
3135
  import { stat } from "node:fs/promises";
@@ -2789,15 +3186,16 @@ var RENAME_ATTEMPTS = 3;
2789
3186
  function errorCode(err) {
2790
3187
  return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
2791
3188
  }
2792
- var wait = (ms) => new Promise((resolve22) => {
2793
- setTimeout(resolve22, ms);
3189
+ var wait = (ms) => new Promise((resolve23) => {
3190
+ setTimeout(resolve23, ms);
2794
3191
  });
2795
- async function writeFileAtomic(path, contents) {
3192
+ async function writeFileAtomic(path, contents, options = {}) {
2796
3193
  const tmp = join(
2797
3194
  dirname(path),
2798
3195
  `.${basename(path)}.manni-${process.pid}-${Math.random().toString(36).slice(2, 8)}.tmp`
2799
3196
  );
2800
3197
  try {
3198
+ if (options.createParents === true) await mkdir(dirname(path), { recursive: true });
2801
3199
  await writeFile(tmp, contents);
2802
3200
  try {
2803
3201
  const { mode } = await stat2(path);
@@ -2827,13 +3225,13 @@ async function writeFileAtomic(path, contents) {
2827
3225
  }
2828
3226
  } finally {
2829
3227
  await rm(tmp, { force: true });
2830
- invalidateManifestCache(resolve(path));
3228
+ invalidateManifestCache(resolve2(path));
2831
3229
  }
2832
3230
  }
2833
3231
 
2834
3232
  // src/meta/core/schema-cache.ts
2835
3233
  import { createHash as createHash2 } from "node:crypto";
2836
- import { mkdir, readFile, stat as stat3 } from "node:fs/promises";
3234
+ import { mkdir as mkdir2, readFile, stat as stat3 } from "node:fs/promises";
2837
3235
  import { join as join2 } from "node:path";
2838
3236
  var SCHEMA_CACHE_DIR = ".manni/meta/schema-cache";
2839
3237
  var SCHEMA_CACHE_VERSION = 1;
@@ -2926,7 +3324,7 @@ var SchemaCache = class {
2926
3324
  schema
2927
3325
  };
2928
3326
  try {
2929
- await mkdir(this.dir, { recursive: true });
3327
+ await mkdir2(this.dir, { recursive: true });
2930
3328
  await writeFileAtomic(
2931
3329
  this.entryPath(url),
2932
3330
  `${JSON.stringify(entry, null, 2)}
@@ -5344,8 +5742,8 @@ var SCHEMA_KEYS = [
5344
5742
  "multipleOf"
5345
5743
  ];
5346
5744
  var RETRY_DELAY_MS = 500;
5347
- var wait2 = (ms) => new Promise((resolve22) => {
5348
- setTimeout(resolve22, ms);
5745
+ var wait2 = (ms) => new Promise((resolve23) => {
5746
+ setTimeout(resolve23, ms);
5349
5747
  });
5350
5748
  var RetryableFetchError = class extends DocmetaError {
5351
5749
  };
@@ -5598,7 +5996,7 @@ async function loadSchema(ref, options = {}) {
5598
5996
  }
5599
5997
 
5600
5998
  // src/meta/core/resolve-schema.ts
5601
- import { isAbsolute, relative, resolve as resolve2 } from "node:path";
5999
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "node:path";
5602
6000
  var DEFAULT_SCHEMAS = Object.freeze([
5603
6001
  "google:okf:0.1",
5604
6002
  "passo-uno:seven-action:1.0"
@@ -5620,8 +6018,8 @@ function collectSchemaPins(config) {
5620
6018
  return pins;
5621
6019
  }
5622
6020
  function rebaseConfigSchemaRefs(config, configDir, cwd) {
5623
- if (resolve2(configDir) === resolve2(cwd)) return config;
5624
- const rebase2 = (ref) => classifyRef(ref).kind === "file" && !isAbsolute(ref) ? resolve2(configDir, ref) : ref;
6021
+ if (resolve3(configDir) === resolve3(cwd)) return config;
6022
+ const rebase2 = (ref) => classifyRef(ref).kind === "file" && !isAbsolute2(ref) ? resolve3(configDir, ref) : ref;
5625
6023
  const rebaseEntry = (entry) => typeof entry === "string" ? rebase2(entry) : {
5626
6024
  ...entry,
5627
6025
  ref: rebase2(entry.ref),
@@ -5697,9 +6095,9 @@ function assertDocumentRefAllowed(ref, mode, params) {
5697
6095
  }
5698
6096
  const root = params.trustRoot;
5699
6097
  if (!root) return;
5700
- const abs = resolve2(params.fileBase ?? process.cwd(), ref);
5701
- const within = relative(root.dir, abs);
5702
- if (within !== "" && !within.startsWith("..") && !isAbsolute(within)) return;
6098
+ const abs = resolve3(params.fileBase ?? process.cwd(), ref);
6099
+ const within = relative2(root.dir, abs);
6100
+ if (within !== "" && !within.startsWith("..") && !isAbsolute2(within)) return;
5703
6101
  const boundary = root.source === "git" ? `the repository root (${root.dir})` : root.source === "config" ? `${root.dir} \u2014 no git repository was found, so the config file's own directory is the boundary` : `${root.dir} \u2014 no git repository and no config file were found, so the directory the command was run from is the boundary`;
5704
6102
  const scope = root.source === "git" ? "the repository" : "the project";
5705
6103
  throw new DocmetaError(
@@ -5849,12 +6247,12 @@ function isPlainObject(v) {
5849
6247
  }
5850
6248
 
5851
6249
  // src/meta/core/config.ts
5852
- import { resolve as resolve6 } from "node:path";
6250
+ import { resolve as resolve7 } from "node:path";
5853
6251
  import { parse as parseYaml2 } from "yaml";
5854
6252
 
5855
6253
  // src/shared/config-file.ts
5856
6254
  import { readFile as readFile3 } from "node:fs/promises";
5857
- import { basename as basename2, dirname as dirname3, join as join4, relative as relative2, resolve as resolve5 } from "node:path";
6255
+ import { basename as basename2, dirname as dirname3, join as join4, relative as relative3, resolve as resolve6 } from "node:path";
5858
6256
  import { parse as parseYaml } from "yaml";
5859
6257
 
5860
6258
  // src/shared/encryption.ts
@@ -5985,9 +6383,9 @@ function keyedPin(text2, key) {
5985
6383
 
5986
6384
  // src/shared/git-root.ts
5987
6385
  import { existsSync } from "node:fs";
5988
- import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
6386
+ import { dirname as dirname2, join as join3, resolve as resolve4 } from "node:path";
5989
6387
  function findGitRoot(cwd) {
5990
- let dir = resolve3(cwd);
6388
+ let dir = resolve4(cwd);
5991
6389
  for (; ; ) {
5992
6390
  if (existsSync(join3(dir, ".git"))) return dir;
5993
6391
  const parent = dirname2(dir);
@@ -5996,7 +6394,7 @@ function findGitRoot(cwd) {
5996
6394
  }
5997
6395
  }
5998
6396
  function searchPath(cwd) {
5999
- const start = resolve3(cwd);
6397
+ const start = resolve4(cwd);
6000
6398
  const root = findGitRoot(start);
6001
6399
  if (root === null) return [start];
6002
6400
  const chain = [];
@@ -6011,7 +6409,7 @@ function searchPath(cwd) {
6011
6409
  }
6012
6410
 
6013
6411
  // src/shared/tools.ts
6014
- import { isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
6412
+ import { isAbsolute as isAbsolute3, resolve as resolve5 } from "node:path";
6015
6413
  var TOOL_KEYS = ["vale"];
6016
6414
  var VALE_KEYS = ["config"];
6017
6415
  function isMapping2(value) {
@@ -6056,7 +6454,7 @@ function parseVale(raw, source, toError) {
6056
6454
  function valeConfigPath(tools, configDir) {
6057
6455
  const config = tools.vale?.config;
6058
6456
  if (config === void 0) return void 0;
6059
- return isAbsolute2(config) ? config : resolve4(configDir, config);
6457
+ return isAbsolute3(config) ? config : resolve5(configDir, config);
6060
6458
  }
6061
6459
 
6062
6460
  // src/shared/warn.ts
@@ -6161,7 +6559,7 @@ function tolerantKeysOf(document) {
6161
6559
  };
6162
6560
  }
6163
6561
  function relativeSource(cwd, path) {
6164
- return relative2(cwd, path).replace(/\\/g, "/");
6562
+ return relative3(cwd, path).replace(/\\/g, "/");
6165
6563
  }
6166
6564
  function mooseWarning(name2) {
6167
6565
  return `"${name2}" is the pre-rename name of the family config file. Rename it to "${FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"}".`;
@@ -6171,7 +6569,7 @@ var FAMILY_FILES = [
6171
6569
  { names: MOOSE_CONFIG_NAMES, kind: "moose" }
6172
6570
  ];
6173
6571
  async function findConfigFile(cwd, opts) {
6174
- const start = resolve5(cwd);
6572
+ const start = resolve6(cwd);
6175
6573
  for (const dir of searchPath(start)) {
6176
6574
  for (const { names, kind } of FAMILY_FILES) {
6177
6575
  for (const name2 of names) {
@@ -6219,7 +6617,7 @@ async function findConfigFile(cwd, opts) {
6219
6617
  return null;
6220
6618
  }
6221
6619
  async function readConfigFile(explicitPath, cwd, opts) {
6222
- const path = resolve5(cwd, explicitPath);
6620
+ const path = resolve6(cwd, explicitPath);
6223
6621
  const document = await readDocument(path, explicitPath, opts.toError);
6224
6622
  if (document === null) {
6225
6623
  throw opts.toError(`Config file not found: "${explicitPath}".`);
@@ -6243,7 +6641,7 @@ async function readConfigFile(explicitPath, cwd, opts) {
6243
6641
  };
6244
6642
  }
6245
6643
  async function findFamilyConfigFile(cwd, toError) {
6246
- const start = resolve5(cwd);
6644
+ const start = resolve6(cwd);
6247
6645
  for (const dir of searchPath(start)) {
6248
6646
  for (const { names, kind } of FAMILY_FILES) {
6249
6647
  for (const name2 of names) {
@@ -6270,7 +6668,7 @@ async function findFamilyConfigFile(cwd, toError) {
6270
6668
  return null;
6271
6669
  }
6272
6670
  async function readFamilyConfigFile(explicitPath, cwd, toError) {
6273
- const path = resolve5(cwd, explicitPath);
6671
+ const path = resolve6(cwd, explicitPath);
6274
6672
  const document = await readDocument(path, explicitPath, toError);
6275
6673
  if (document === null) {
6276
6674
  throw toError(`Config file not found: "${explicitPath}".`);
@@ -6939,8 +7337,8 @@ function parseFill(value, source) {
6939
7337
  function schemaTrustRoot(cwd, configDir) {
6940
7338
  const git2 = findGitRoot(cwd);
6941
7339
  if (git2 !== null) return { dir: git2, source: "git" };
6942
- if (configDir !== void 0) return { dir: resolve6(configDir), source: "config" };
6943
- return { dir: resolve6(cwd), source: "cwd" };
7340
+ if (configDir !== void 0) return { dir: resolve7(configDir), source: "config" };
7341
+ return { dir: resolve7(cwd), source: "cwd" };
6944
7342
  }
6945
7343
  async function loadConfig(explicitPath, cwd = process.cwd()) {
6946
7344
  const file = explicitPath ? await readConfigFile(explicitPath, cwd, CONFIG_FILE) : await findConfigFile(cwd, CONFIG_FILE);
@@ -7003,23 +7401,23 @@ async function resolveRunConfig(opts) {
7003
7401
  }
7004
7402
 
7005
7403
  // src/meta/reporters/sarif.ts
7006
- import { isAbsolute as isAbsolute3 } from "node:path";
7404
+ import { isAbsolute as isAbsolute4 } from "node:path";
7007
7405
 
7008
7406
  // src/meta/core/baseline.ts
7009
7407
  import { createHash as createHash3 } from "node:crypto";
7010
7408
  import { existsSync as existsSync2 } from "node:fs";
7011
7409
  import { readFile as readFile4 } from "node:fs/promises";
7012
- import { relative as relative5, resolve as resolve9 } from "node:path";
7410
+ import { relative as relative6, resolve as resolve10 } from "node:path";
7013
7411
 
7014
7412
  // src/meta/core/load-files.ts
7015
7413
  import { stat as stat4 } from "node:fs/promises";
7016
- import { extname, relative as relative4, resolve as resolve8 } from "node:path";
7017
- import fg from "fast-glob";
7414
+ import { extname, relative as relative5, resolve as resolve9 } from "node:path";
7415
+ import fg2 from "fast-glob";
7018
7416
  import picomatch3 from "picomatch";
7019
7417
 
7020
7418
  // src/meta/core/gitignore.ts
7021
7419
  import { spawn } from "node:child_process";
7022
- import { dirname as dirname4, relative as relative3, resolve as resolve7, sep } from "node:path";
7420
+ import { dirname as dirname4, relative as relative4, resolve as resolve8, sep as sep2 } from "node:path";
7023
7421
  var GITIGNORE_UNAVAILABLE = ".gitignore filtering was requested but git could not answer here (no repository, or no git on PATH); no files were skipped.";
7024
7422
  var unavailable = () => ({
7025
7423
  ignored: /* @__PURE__ */ new Set(),
@@ -7033,7 +7431,7 @@ async function gitIgnored(paths, cwd) {
7033
7431
  const byRoot = /* @__PURE__ */ new Map();
7034
7432
  let inSomeRepo = false;
7035
7433
  for (const spelled of paths) {
7036
- const abs = resolve7(cwd, spelled);
7434
+ const abs = resolve8(cwd, spelled);
7037
7435
  const dir = dirname4(abs);
7038
7436
  let root = rootOf.get(dir);
7039
7437
  if (root === void 0) {
@@ -7042,7 +7440,7 @@ async function gitIgnored(paths, cwd) {
7042
7440
  }
7043
7441
  if (root === null) continue;
7044
7442
  inSomeRepo = true;
7045
- const rel = relative3(root, abs).split(sep).join("/");
7443
+ const rel = relative4(root, abs).split(sep2).join("/");
7046
7444
  const bucket = byRoot.get(root);
7047
7445
  if (bucket) bucket.push({ rel, spelled });
7048
7446
  else byRoot.set(root, [{ rel, spelled }]);
@@ -7145,14 +7543,14 @@ async function resolveTargetSet(opts) {
7145
7543
  for (const input of opts.inputs) {
7146
7544
  if (input === STDIN_TOKEN) continue;
7147
7545
  const posixInput = toPosix(input);
7148
- const abs = resolve8(cwd, posixInput);
7546
+ const abs = resolve9(cwd, posixInput);
7149
7547
  const st = await statOrNull(abs);
7150
7548
  if (st?.isFile()) {
7151
- named.add(toPosix(relative4(cwd, abs)));
7549
+ named.add(toPosix(relative5(cwd, abs)));
7152
7550
  continue;
7153
7551
  }
7154
7552
  if (st?.isDirectory()) {
7155
- const found2 = await fg(`${posixInput}/**/*`, {
7553
+ const found2 = await fg2(`${posixInput}/**/*`, {
7156
7554
  cwd,
7157
7555
  ignore,
7158
7556
  onlyFiles: true,
@@ -7165,7 +7563,7 @@ async function resolveTargetSet(opts) {
7165
7563
  missing.push(posixInput);
7166
7564
  continue;
7167
7565
  }
7168
- const found = await fg(posixInput, {
7566
+ const found = await fg2(posixInput, {
7169
7567
  cwd,
7170
7568
  ignore,
7171
7569
  onlyFiles: true,
@@ -7230,13 +7628,13 @@ var FINGERPRINT_RE = /^[0-9a-f]{16}$/;
7230
7628
  function canonicalFilePath(file, ctx) {
7231
7629
  if (!ctx) return file;
7232
7630
  const from = ctx.runBase ?? ctx.base;
7233
- return relative5(ctx.base, resolve9(from, file)).replace(/\\/g, "/");
7631
+ return relative6(ctx.base, resolve10(from, file)).replace(/\\/g, "/");
7234
7632
  }
7235
7633
  function canonicalSchemaRef(ref, ctx) {
7236
7634
  if (!ctx) return ref;
7237
7635
  if (classifyRef(ref).kind !== "file") return ref;
7238
- const abs = resolve9(ctx.cwd, ref);
7239
- return relative5(ctx.base, abs).replace(/\\/g, "/");
7636
+ const abs = resolve10(ctx.cwd, ref);
7637
+ return relative6(ctx.base, abs).replace(/\\/g, "/");
7240
7638
  }
7241
7639
  function fingerprint(e, ctx) {
7242
7640
  const parts = [
@@ -7393,17 +7791,17 @@ function countFingerprints(baseline) {
7393
7791
  }
7394
7792
  function resolveBaselineRequest(opts, configured, configDir, cwd, defaults) {
7395
7793
  const named = (label) => ({
7396
- absPath: resolve9(cwd, label),
7794
+ absPath: resolve10(cwd, label),
7397
7795
  label
7398
7796
  });
7399
7797
  const implied = () => {
7400
7798
  const base = configDir ?? cwd;
7401
7799
  if (configured !== void 0) {
7402
- return { absPath: resolve9(base, configured), label: configured };
7800
+ return { absPath: resolve10(base, configured), label: configured };
7403
7801
  }
7404
- const current = resolve9(base, defaults.current);
7802
+ const current = resolve10(base, defaults.current);
7405
7803
  if (defaults.legacy !== void 0) {
7406
- const legacy = resolve9(base, defaults.legacy);
7804
+ const legacy = resolve10(base, defaults.legacy);
7407
7805
  if (!existsSync2(current) && existsSync2(legacy)) {
7408
7806
  warn(
7409
7807
  `"${defaults.legacy}" is the pre-rename baseline file name. Rename it to "${defaults.current}".`
@@ -7516,7 +7914,7 @@ function uriFrame(frame) {
7516
7914
  }
7517
7915
  function artifactUri(file, frame) {
7518
7916
  const uri = canonicalFilePath(file, frame);
7519
- if (uri === "" || uri.startsWith("../") || isAbsolute3(uri)) return null;
7917
+ if (uri === "" || uri.startsWith("../") || isAbsolute4(uri)) return null;
7520
7918
  return uri;
7521
7919
  }
7522
7920
  function renderSarif(results, opts = {}) {
@@ -7910,22 +8308,92 @@ async function readCapped(url, res, maxBytes) {
7910
8308
 
7911
8309
  // src/meta/core/external-metadata.ts
7912
8310
  import { readFile as readFile6 } from "node:fs/promises";
7913
- import { isAbsolute as isAbsolute4, relative as relative7, resolve as resolve11, sep as sep2 } from "node:path";
8311
+ import { isAbsolute as isAbsolute5, relative as relative8, resolve as resolve12, sep as sep3 } from "node:path";
7914
8312
  import {
7915
- LineCounter as LineCounter2,
7916
- isMap as isMap4,
7917
- isNode,
7918
- isScalar as isScalar2,
7919
- isSeq as isSeq2,
7920
- parseDocument as parseDocument4
8313
+ LineCounter as LineCounter3,
8314
+ isMap as isMap6,
8315
+ isNode as isNode3,
8316
+ isScalar as isScalar5,
8317
+ isSeq as isSeq3,
8318
+ parseDocument as parseDocument5
7921
8319
  } from "yaml";
7922
8320
 
8321
+ // src/shared/manifest-cas.ts
8322
+ import { posix as posix3 } from "node:path";
8323
+ var COMMIT_ATTEMPTS = 5;
8324
+ function conflictRefusal(file) {
8325
+ return `${file} changed under the command while it was being written. Nothing was written to it. Re-run the command.`;
8326
+ }
8327
+ function heldManifest(path, file, before, absent = false) {
8328
+ return { path, file, before, text: before, ops: [], bases: /* @__PURE__ */ new Map(), absent };
8329
+ }
8330
+ function encodeValue(value) {
8331
+ return value === void 0 ? "absent" : JSON.stringify(value);
8332
+ }
8333
+ function baseKey(at2) {
8334
+ const entry = at2.join === "path" ? posix3.normalize(at2.entry) : at2.entry;
8335
+ return `${entry}\0${at2.key}`;
8336
+ }
8337
+ function rebaseable(held, op, codec) {
8338
+ const at2 = baseKey(op);
8339
+ if (held.bases.has(at2)) return;
8340
+ held.bases.set(at2, {
8341
+ entry: op.entry,
8342
+ key: op.key,
8343
+ join: op.join,
8344
+ value: encodeValue(codec.read(held.before, op))
8345
+ });
8346
+ }
8347
+ function rebase(held, current, codec) {
8348
+ for (const base of held.bases.values()) {
8349
+ if (encodeValue(codec.read(current, base)) !== base.value) return void 0;
8350
+ }
8351
+ let text2 = current;
8352
+ for (const op of held.ops) text2 = codec.apply(text2, op);
8353
+ return text2;
8354
+ }
8355
+ function rebaseline(held) {
8356
+ held.before = held.text;
8357
+ held.ops = [];
8358
+ held.bases.clear();
8359
+ held.absent = false;
8360
+ }
8361
+ async function settle(held, io) {
8362
+ for (let attempt = 1; attempt <= COMMIT_ATTEMPTS; attempt++) {
8363
+ let current;
8364
+ try {
8365
+ current = await io.read(held.path);
8366
+ } catch (error) {
8367
+ if (held.absent && isMissing(error)) {
8368
+ await io.write(held.path, held.text);
8369
+ held.absent = false;
8370
+ return;
8371
+ }
8372
+ const reason = error instanceof Error ? error.message : String(error);
8373
+ throw io.toError(`Manifest ${held.file} could not be read: ${reason}`);
8374
+ }
8375
+ if (!held.absent && current === held.before) {
8376
+ await io.write(held.path, held.text);
8377
+ return;
8378
+ }
8379
+ const replayed = rebase(held, current, io.codec);
8380
+ if (replayed === void 0) throw io.toError(conflictRefusal(held.file));
8381
+ held.before = current;
8382
+ held.text = replayed;
8383
+ held.absent = false;
8384
+ }
8385
+ throw io.toError(conflictRefusal(held.file));
8386
+ }
8387
+ function isMissing(error) {
8388
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
8389
+ }
8390
+
7923
8391
  // src/shared/encryption-key.ts
7924
8392
  import { spawnSync } from "node:child_process";
7925
8393
  import { readFile as readFile5 } from "node:fs/promises";
7926
- import { basename as basename4, dirname as dirname6, join as join6, relative as relative6, resolve as resolve10 } from "node:path";
8394
+ import { basename as basename4, dirname as dirname6, join as join6, relative as relative7, resolve as resolve11 } from "node:path";
7927
8395
  import { isDeepStrictEqual } from "node:util";
7928
- import { isMap as isMap3, parseDocument as parseDocument3, parse as parseYaml3, stringify } from "yaml";
8396
+ import { isMap as isMap5, parseDocument as parseDocument4, parse as parseYaml3, stringify as stringify2 } from "yaml";
7929
8397
 
7930
8398
  // src/shared/write-file.ts
7931
8399
  import { chmod as chmod2, rename as rename2, rm as rm2, stat as stat5, writeFile as writeFile2 } from "node:fs/promises";
@@ -7935,8 +8403,8 @@ var RENAME_ATTEMPTS2 = 3;
7935
8403
  function errorCode2(err) {
7936
8404
  return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
7937
8405
  }
7938
- var wait3 = (ms) => new Promise((resolve22) => {
7939
- setTimeout(resolve22, ms);
8406
+ var wait3 = (ms) => new Promise((resolve23) => {
8407
+ setTimeout(resolve23, ms);
7940
8408
  });
7941
8409
  async function writeTextAtomic(path, text2) {
7942
8410
  const tmp = join5(
@@ -7985,7 +8453,7 @@ function resolveEncryptionKey(opts) {
7985
8453
  return { key: void 0, source: "none" };
7986
8454
  }
7987
8455
  function relativeSource2(cwd, path) {
7988
- return relative6(resolve10(cwd), path).replace(/\\/g, "/");
8456
+ return relative7(resolve11(cwd), path).replace(/\\/g, "/");
7989
8457
  }
7990
8458
  function errorCode3(err) {
7991
8459
  return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
@@ -8008,7 +8476,7 @@ async function fileTarget(file, toError) {
8008
8476
  return { path: file.path, source: file.source, text: text2 ?? file.text };
8009
8477
  }
8010
8478
  async function defaultTarget(cwd) {
8011
- const root = findGitRoot(cwd) ?? resolve10(cwd);
8479
+ const root = findGitRoot(cwd) ?? resolve11(cwd);
8012
8480
  for (const name2 of [...FAMILY_CONFIG_NAMES, ...MOOSE_CONFIG_NAMES]) {
8013
8481
  const path2 = join6(root, name2);
8014
8482
  const text2 = await readIfPresent(path2);
@@ -8018,12 +8486,12 @@ async function defaultTarget(cwd) {
8018
8486
  return { path, source: relativeSource2(cwd, path), text: null };
8019
8487
  }
8020
8488
  function withKey(text2, key, previous, source, toError) {
8021
- const doc = parseDocument3(text2);
8489
+ const doc = parseDocument4(text2);
8022
8490
  const [first] = doc.errors;
8023
8491
  if (first !== void 0) {
8024
8492
  throw toError(`${source}: invalid YAML: ${first.message}`);
8025
8493
  }
8026
- if (doc.contents !== null && !isMap3(doc.contents)) {
8494
+ if (doc.contents !== null && !isMap5(doc.contents)) {
8027
8495
  throw toError(`${source}: top level must be a mapping.`);
8028
8496
  }
8029
8497
  doc.set(ENCRYPTION_KEY_FIELD, key);
@@ -8063,7 +8531,7 @@ async function writeEncryptionKey(opts) {
8063
8531
  );
8064
8532
  }
8065
8533
  const target = file === null ? await defaultTarget(opts.cwd) : await fileTarget(file, toError);
8066
- const text2 = target.text === null ? stringify({
8534
+ const text2 = target.text === null ? stringify2({
8067
8535
  [ENCRYPTION_KEY_FIELD]: key,
8068
8536
  ...typeof previous === "string" ? { [ENCRYPTION_KEY_PREVIOUS_FIELD]: previous } : {}
8069
8537
  }) : withKey(target.text, key, previous, target.source, toError);
@@ -8293,18 +8761,20 @@ function redactedDerived(f) {
8293
8761
  var EXTERNAL_OWNED_SCHEMA = "external:owned";
8294
8762
  var EXTERNAL_DUPLICATE_SCHEMA = "external:duplicate";
8295
8763
  var EXTERNAL_KEYWORD = "external";
8296
- var PATH_JOIN = "path";
8764
+ var PATH_JOIN2 = "path";
8297
8765
  var parsedManifests = new ManifestCache();
8298
- var posix = (p) => p.split(sep2).join("/");
8766
+ var perPageRules = /* @__PURE__ */ new WeakMap();
8767
+ var strayEntries = /* @__PURE__ */ new WeakSet();
8768
+ var posix4 = (p) => p.split(sep3).join("/");
8299
8769
  function reportedPath(abs, base) {
8300
- const rel = relative7(base, abs);
8301
- return rel === "" ? "." : posix(rel);
8770
+ const rel = relative8(base, abs);
8771
+ return rel === "" ? "." : posix4(rel);
8302
8772
  }
8303
8773
  function reported(of, file) {
8304
8774
  return of.file === file ? of : { ...of, file };
8305
8775
  }
8306
8776
  function externalMetadataJoin(manifest) {
8307
- return manifest.join ?? PATH_JOIN;
8777
+ return manifest.join ?? PATH_JOIN2;
8308
8778
  }
8309
8779
  async function loadExternalMetadata(collections, opts) {
8310
8780
  const configured = collections.flatMap(
@@ -8315,7 +8785,17 @@ async function loadExternalMetadata(collections, opts) {
8315
8785
  const byPath = /* @__PURE__ */ new Map();
8316
8786
  const byField = /* @__PURE__ */ new Map();
8317
8787
  const entries = [];
8788
+ const resolvedBy = /* @__PURE__ */ new Map();
8789
+ const rules = [];
8318
8790
  for (const { collection, manifest } of configured) {
8791
+ const declared = collections.find(
8792
+ (c) => c.name === collection && c.externalMetadata.includes(manifest)
8793
+ );
8794
+ if (declared !== void 0 && classifyRef(manifest.file).kind !== "url" && hasPagePlaceholder(manifest.file)) {
8795
+ await loadPerPage(declared, manifest, opts, resolvedBy, { owners, byPath, byField, entries });
8796
+ rules.push({ collection: declared, manifest, configDir: opts.configDir });
8797
+ continue;
8798
+ }
8319
8799
  const join11 = externalMetadataJoin(manifest);
8320
8800
  let parsed2;
8321
8801
  let file;
@@ -8329,7 +8809,7 @@ async function loadExternalMetadata(collections, opts) {
8329
8809
  });
8330
8810
  parsed2 = parseManifest(manifest, collection, text2, file, opts.configDir, join11);
8331
8811
  } else {
8332
- const abs = isAbsolute4(manifest.file) ? manifest.file : resolve11(opts.configDir, manifest.file);
8812
+ const abs = isAbsolute5(manifest.file) ? manifest.file : resolve12(opts.configDir, manifest.file);
8333
8813
  const label = reportedPath(abs, opts.base);
8334
8814
  file = label;
8335
8815
  parsed2 = await parsedManifests.parse(
@@ -8367,7 +8847,7 @@ async function loadExternalMetadata(collections, opts) {
8367
8847
  else owners.set(key, [{ collection, file }]);
8368
8848
  }
8369
8849
  let target;
8370
- if (join11 === PATH_JOIN) {
8850
+ if (join11 === PATH_JOIN2) {
8371
8851
  target = byPath;
8372
8852
  } else {
8373
8853
  const existing = byField.get(join11);
@@ -8384,23 +8864,112 @@ async function loadExternalMetadata(collections, opts) {
8384
8864
  }
8385
8865
  for (const entry of parsed2.entries) entries.push(reported(entry, file));
8386
8866
  }
8387
- return { owners, byPath, byField, entries };
8867
+ const index = { owners, byPath, byField, entries };
8868
+ if (rules.length > 0) perPageRules.set(index, rules);
8869
+ return index;
8870
+ }
8871
+ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
8872
+ const collection = declared.name;
8873
+ const join11 = externalMetadataJoin(manifest);
8874
+ for (const key of manifest.keys) {
8875
+ const list2 = into.owners.get(key);
8876
+ if (list2) list2.push({ collection, file: manifest.file });
8877
+ else into.owners.set(key, [{ collection, file: manifest.file }]);
8878
+ }
8879
+ const target = indexFor(join11, into);
8880
+ for (const pageAbs of opts.pages ?? []) {
8881
+ const resolved = pageManifestPath(manifest.file, opts.configDir, pageAbs);
8882
+ if ("outside" in resolved) {
8883
+ if (resolved.pageRel.startsWith("../") && matchesCollectionPaths(declared, resolved.pageRel)) {
8884
+ throw new DocmetaError(outsideRefusal(manifest, collection, resolved.pageRel));
8885
+ }
8886
+ continue;
8887
+ }
8888
+ const rel = posix4(relative8(opts.configDir, pageAbs));
8889
+ if (!isMember(declared, rel)) continue;
8890
+ const earlier = resolvedBy.get(resolved.abs);
8891
+ if (earlier !== void 0 && earlier !== pageAbs) {
8892
+ const [a, b] = [reportedPath(earlier, opts.base), reportedPath(pageAbs, opts.base)].sort();
8893
+ throw new DocmetaError(
8894
+ `${a ?? ""} and ${b ?? ""} both resolve ${PAGE_PLACEHOLDER} to ${reportedPath(resolved.abs, opts.base)}. A ${PAGE_PLACEHOLDER} manifest names one page, so rename one of them.`
8895
+ );
8896
+ }
8897
+ resolvedBy.set(resolved.abs, pageAbs);
8898
+ const label = reportedPath(resolved.abs, opts.base);
8899
+ const parsed2 = await parsedManifests.parse(
8900
+ resolved.abs,
8901
+ JSON.stringify([collection, join11, opts.configDir, [...manifest.keys].sort()]),
8902
+ async () => parseManifest(
8903
+ manifest,
8904
+ collection,
8905
+ await readPageManifest(resolved.abs, label),
8906
+ label,
8907
+ opts.configDir,
8908
+ join11
8909
+ )
8910
+ );
8911
+ const pageLabel = reportedPath(pageAbs, opts.base);
8912
+ for (const entry of parsed2.entries) {
8913
+ if (entry.abs === void 0 || sameFile(entry.abs, pageAbs)) continue;
8914
+ const at2 = entry.line === void 0 ? label : `${label}:${String(entry.line)}`;
8915
+ throw new DocmetaError(
8916
+ `Manifest ${at2} names "${entry.spelled}", but ${PAGE_PLACEHOLDER} resolved this file for "${pageLabel}". A per-page manifest holds one entry, for its own page.`
8917
+ );
8918
+ }
8919
+ for (const [indexKey, values] of parsed2.values) {
8920
+ const key = join11 === PATH_JOIN2 ? pageAbs : indexKey;
8921
+ const merged = target.get(key) ?? /* @__PURE__ */ new Map();
8922
+ for (const [owned, value] of values) merged.set(owned, reported(value, label));
8923
+ target.set(key, merged);
8924
+ }
8925
+ for (const entry of parsed2.entries) {
8926
+ into.entries.push({
8927
+ ...reported(entry, label),
8928
+ ...entry.abs === void 0 ? {} : { abs: pageAbs }
8929
+ });
8930
+ }
8931
+ }
8932
+ }
8933
+ function indexFor(join11, into) {
8934
+ if (join11 === PATH_JOIN2) return into.byPath;
8935
+ const existing = into.byField.get(join11);
8936
+ if (existing) return existing;
8937
+ const created = /* @__PURE__ */ new Map();
8938
+ into.byField.set(join11, created);
8939
+ return created;
8940
+ }
8941
+ function outsideRefusal(manifest, collection, pageRel) {
8942
+ const tail = `for "${pageRel}". A ${PAGE_PLACEHOLDER} manifest stays under the config file.`;
8943
+ const origin = externalMetadataOrigin(manifest);
8944
+ if (origin === void 0) {
8945
+ return `externalMetadata file "${manifest.file}" of collection ${collection} resolves outside the config file's directory ${tail}`;
8946
+ }
8947
+ return `${origin.source}: ${origin.where}.file resolves outside ${origin.source}'s directory ${tail}`;
8948
+ }
8949
+ async function readPageManifest(abs, file) {
8950
+ try {
8951
+ return await readFile6(abs, "utf8");
8952
+ } catch (err) {
8953
+ if (isMissing(err)) return "";
8954
+ throw unreadable(file, err);
8955
+ }
8388
8956
  }
8389
8957
  async function readManifest(abs, file) {
8390
8958
  try {
8391
8959
  return await readFile6(abs, "utf8");
8392
8960
  } catch (err) {
8393
- const reason = err instanceof Error ? err.message : String(err);
8394
- throw new DocmetaError(
8395
- `Manifest ${file} could not be read: ${reason}`
8396
- );
8961
+ throw unreadable(file, err);
8397
8962
  }
8398
8963
  }
8964
+ function unreadable(file, err) {
8965
+ const reason = err instanceof Error ? err.message : String(err);
8966
+ return new DocmetaError(`Manifest ${file} could not be read: ${reason}`);
8967
+ }
8399
8968
  function parseManifest(manifest, collection, text2, file, configDir, join11) {
8400
8969
  const target = /* @__PURE__ */ new Map();
8401
8970
  const entries = [];
8402
- const lc = new LineCounter2();
8403
- const doc = parseDocument4(text2, { lineCounter: lc, uniqueKeys: false });
8971
+ const lc = new LineCounter3();
8972
+ const doc = parseDocument5(text2, { lineCounter: lc, uniqueKeys: false });
8404
8973
  const problem = doc.errors[0];
8405
8974
  if (problem) {
8406
8975
  throw new DocmetaError(
@@ -8408,21 +8977,21 @@ function parseManifest(manifest, collection, text2, file, configDir, join11) {
8408
8977
  );
8409
8978
  }
8410
8979
  const root = doc.contents;
8411
- if (root === null || isScalar2(root) && root.value === null)
8980
+ if (root === null || isScalar5(root) && root.value === null)
8412
8981
  return { values: target, entries };
8413
- if (!isMap4(root)) {
8982
+ if (!isMap6(root)) {
8414
8983
  throw new DocmetaError(
8415
- `Manifest ${file}: the manifest must be a mapping from document ${join11 === PATH_JOIN ? "path" : `"${join11}"`} to owned keys.`
8984
+ `Manifest ${file}: the manifest must be a mapping from document ${join11 === PATH_JOIN2 ? "path" : `"${join11}"`} to owned keys.`
8416
8985
  );
8417
8986
  }
8418
8987
  const owned = new Set(manifest.keys);
8419
8988
  const lineAt = (node) => {
8420
- const range = isNode(node) ? node.range : void 0;
8989
+ const range = isNode3(node) ? node.range : void 0;
8421
8990
  return range ? lc.linePos(range[0]).line : void 0;
8422
8991
  };
8423
8992
  const seen = /* @__PURE__ */ new Map();
8424
8993
  for (const pair of root.items) {
8425
- const spelled = isScalar2(pair.key) ? String(pair.key.value) : String(pair.key);
8994
+ const spelled = isScalar5(pair.key) ? String(pair.key.value) : String(pair.key);
8426
8995
  const entryLine = lineAt(pair.key);
8427
8996
  const where = entryLine === void 0 ? file : `${file}:${entryLine}`;
8428
8997
  if (seen.has(spelled)) {
@@ -8432,15 +9001,15 @@ function parseManifest(manifest, collection, text2, file, configDir, join11) {
8432
9001
  );
8433
9002
  }
8434
9003
  seen.set(spelled, entryLine);
8435
- if (!isMap4(pair.value)) {
9004
+ if (!isMap6(pair.value)) {
8436
9005
  throw new DocmetaError(
8437
9006
  `Manifest ${where}: "${spelled}" must be a mapping of owned keys to values.`
8438
9007
  );
8439
9008
  }
8440
- const indexKey = join11 === PATH_JOIN ? resolve11(configDir, spelled) : spelled;
9009
+ const indexKey = join11 === PATH_JOIN2 ? resolve12(configDir, spelled) : spelled;
8441
9010
  const values = target.get(indexKey) ?? /* @__PURE__ */ new Map();
8442
9011
  for (const kv of pair.value.items) {
8443
- const key = isScalar2(kv.key) ? String(kv.key.value) : String(kv.key);
9012
+ const key = isScalar5(kv.key) ? String(kv.key.value) : String(kv.key);
8444
9013
  if (key === FILE_SCHEMA_KEY) {
8445
9014
  throw new DocmetaError(
8446
9015
  `Manifest ${where}: "${spelled}" sets "${FILE_SCHEMA_KEY}" \u2014 a manifest never chooses the schema a document is judged by; use "overrides" in the config.`
@@ -8466,7 +9035,7 @@ function parseManifest(manifest, collection, text2, file, configDir, join11) {
8466
9035
  entries.push({
8467
9036
  collection,
8468
9037
  join: join11,
8469
- ...join11 === PATH_JOIN ? { abs: indexKey } : {},
9038
+ ...join11 === PATH_JOIN2 ? { abs: indexKey } : {},
8470
9039
  spelled,
8471
9040
  file,
8472
9041
  ...entryLine === void 0 ? {} : { line: entryLine }
@@ -8475,15 +9044,15 @@ function parseManifest(manifest, collection, text2, file, configDir, join11) {
8475
9044
  return { values: target, entries };
8476
9045
  }
8477
9046
  function nodeLines(node, lineAt, prefix = "", out = /* @__PURE__ */ new Map()) {
8478
- if (isMap4(node)) {
9047
+ if (isMap6(node)) {
8479
9048
  for (const kv of node.items) {
8480
- const seg = isScalar2(kv.key) ? String(kv.key.value) : String(kv.key);
9049
+ const seg = isScalar5(kv.key) ? String(kv.key.value) : String(kv.key);
8481
9050
  const pointer = `${prefix}/${escapePointerSegment(seg)}`;
8482
9051
  const line = lineAt(kv.key) ?? lineAt(kv.value);
8483
9052
  if (line !== void 0) out.set(pointer, line);
8484
9053
  nodeLines(kv.value, lineAt, pointer, out);
8485
9054
  }
8486
- } else if (isSeq2(node)) {
9055
+ } else if (isSeq3(node)) {
8487
9056
  node.items.forEach((item, i) => {
8488
9057
  const pointer = `${prefix}/${String(i)}`;
8489
9058
  const line = lineAt(item);
@@ -8516,7 +9085,7 @@ function mergeExternalMetadata(label, extracted, index, memberOf2, base, options
8516
9085
  const sources = [];
8517
9086
  const joins = [];
8518
9087
  if (label !== STDIN_LABEL) {
8519
- const byPath = index.byPath.get(resolve11(base, label));
9088
+ const byPath = index.byPath.get(resolve12(base, label));
8520
9089
  if (byPath) sources.push(byPath);
8521
9090
  }
8522
9091
  for (const [field, byValue] of index.byField) {
@@ -8611,10 +9180,41 @@ function externalMetadataPointer(key) {
8611
9180
  }
8612
9181
  function orphanEntries(index, loaded, base, collections) {
8613
9182
  if (!index) return [];
8614
- const have = new Set(loaded.map((l) => resolve11(base, l)));
8615
- return index.entries.filter(
8616
- (e) => inCollections(e, collections) && e.join === PATH_JOIN && e.abs !== void 0 && !have.has(e.abs)
9183
+ const have = new Set(loaded.map((l) => resolve12(base, l)));
9184
+ const orphans = index.entries.filter(
9185
+ (e) => inCollections(e, collections) && e.join === PATH_JOIN2 && e.abs !== void 0 && !have.has(e.abs)
8617
9186
  );
9187
+ if (collections !== void 0) return orphans;
9188
+ return [...orphans, ...strayManifests(perPageRules.get(index) ?? [], have, base)];
9189
+ }
9190
+ function strayManifests(rules, loaded, base) {
9191
+ const strays = [];
9192
+ for (const { collection, manifest, configDir } of rules) {
9193
+ const read = /* @__PURE__ */ new Set();
9194
+ for (const pageAbs of loaded) {
9195
+ const resolved = pageManifestPath(manifest.file, configDir, pageAbs);
9196
+ if (!("outside" in resolved)) read.add(resolved.abs);
9197
+ }
9198
+ const found = findPageManifests(manifest.file, configDir, (rel) => isMember(collection, rel));
9199
+ for (const { abs, entries } of found) {
9200
+ if (read.has(abs)) continue;
9201
+ const file = reportedPath(abs, base);
9202
+ for (const entry of entries) {
9203
+ if (loaded.has(entry.pageAbs)) continue;
9204
+ const stray = {
9205
+ collection: collection.name,
9206
+ join: PATH_JOIN2,
9207
+ abs: entry.pageAbs,
9208
+ spelled: entry.spelled,
9209
+ file,
9210
+ ...entry.line === void 0 ? {} : { line: entry.line }
9211
+ };
9212
+ strayEntries.add(stray);
9213
+ strays.push(stray);
9214
+ }
9215
+ }
9216
+ }
9217
+ return strays;
8618
9218
  }
8619
9219
  function inCollections(entry, collections) {
8620
9220
  return collections === void 0 || collections.includes(entry.collection);
@@ -8622,7 +9222,7 @@ function inCollections(entry, collections) {
8622
9222
  function orphanJoins(index, matched, collections) {
8623
9223
  if (!index) return [];
8624
9224
  return index.entries.filter(
8625
- (e) => inCollections(e, collections) && e.join !== PATH_JOIN && !(matched.get(e.join)?.has(e.spelled) ?? false)
9225
+ (e) => inCollections(e, collections) && e.join !== PATH_JOIN2 && !(matched.get(e.join)?.has(e.spelled) ?? false)
8626
9226
  );
8627
9227
  }
8628
9228
  function orphanError(orphans) {
@@ -8630,9 +9230,10 @@ function orphanError(orphans) {
8630
9230
  if (!first) throw new Error("orphanError called with no orphans");
8631
9231
  const where = first.line === void 0 ? first.file : `${first.file}:${first.line}`;
8632
9232
  const more = orphans.length > 1 ? ` (${orphans.length - 1} more)` : "";
8633
- const what = first.join === PATH_JOIN ? `names "${first.spelled}", which this run did not load` : `names ${first.join} "${first.spelled}", which no loaded document carries`;
9233
+ const what = first.join === PATH_JOIN2 ? `names "${first.spelled}", which this run did not load` : `names ${first.join} "${first.spelled}", which no loaded document carries`;
9234
+ const remove = strayEntries.has(first) ? "the file" : "it";
8634
9235
  return new DocmetaError(
8635
- `Manifest ${where} ${what}${more}. Fix the entry, or remove it.`
9236
+ `Manifest ${where} ${what}${more}. Fix the entry, or remove ${remove}.`
8636
9237
  );
8637
9238
  }
8638
9239
 
@@ -8646,7 +9247,7 @@ function isFieldLocation(value) {
8646
9247
  }
8647
9248
 
8648
9249
  // src/meta/core/collections.ts
8649
- import { isAbsolute as isAbsolute5, relative as relative8, resolve as resolvePath2, sep as sep3 } from "node:path";
9250
+ import { isAbsolute as isAbsolute6, relative as relative9, resolve as resolvePath2, sep as sep4 } from "node:path";
8650
9251
 
8651
9252
  // src/meta/core/projection.ts
8652
9253
  var SYSTEM_COLUMNS = ["_path", "_format", "_present", "_data"];
@@ -8867,8 +9468,8 @@ function loadSqlite() {
8867
9468
  function memberOf(collections, configDir, base, label) {
8868
9469
  if (collections.length === 0) return [];
8869
9470
  if (label === STDIN_LABEL) return [];
8870
- const rel = relative8(configDir, resolvePath2(base, label)).split(sep3).join("/");
8871
- if (isAbsolute5(rel)) return [];
9471
+ const rel = relative9(configDir, resolvePath2(base, label)).split(sep4).join("/");
9472
+ if (isAbsolute6(rel)) return [];
8872
9473
  return collections.filter((c) => isMember(c, rel)).map((c) => c.name);
8873
9474
  }
8874
9475
  function retainMembers(files, membersFor) {
@@ -9322,7 +9923,7 @@ function toFieldError(schema, e, lineFor, colFor, locate) {
9322
9923
 
9323
9924
  // src/meta/core/derive/cache.ts
9324
9925
  import { createHash as createHash4 } from "node:crypto";
9325
- import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
9926
+ import { mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync } from "node:fs";
9326
9927
  import { join as join7 } from "node:path";
9327
9928
  var REVIEW_CACHE_DIR = ".manni/meta/review-cache";
9328
9929
  var REVIEW_CACHE_VERSION = 1;
@@ -9342,7 +9943,7 @@ var ReviewCache = class {
9342
9943
  if (!this.enabled) return void 0;
9343
9944
  let raw;
9344
9945
  try {
9345
- raw = JSON.parse(readFileSync(this.entryPath(key), "utf8"));
9946
+ raw = JSON.parse(readFileSync2(this.entryPath(key), "utf8"));
9346
9947
  } catch {
9347
9948
  return void 0;
9348
9949
  }
@@ -9906,12 +10507,12 @@ function utcDate(iso) {
9906
10507
  }
9907
10508
 
9908
10509
  // src/meta/core/derive/index.ts
9909
- import { dirname as dirname9, resolve as resolve13 } from "node:path";
10510
+ import { dirname as dirname9, resolve as resolve14 } from "node:path";
9910
10511
 
9911
10512
  // src/meta/core/derive/codeowners.ts
9912
10513
  import { existsSync as existsSync3 } from "node:fs";
9913
10514
  import { readFile as readFile7 } from "node:fs/promises";
9914
- import { dirname as dirname7, relative as relative9, resolve as resolve12, sep as sep4 } from "node:path";
10515
+ import { dirname as dirname7, relative as relative10, resolve as resolve13, sep as sep5 } from "node:path";
9915
10516
  import picomatch4 from "picomatch";
9916
10517
  var CODEOWNERS_LOCATIONS = [
9917
10518
  ".github/CODEOWNERS",
@@ -9921,11 +10522,11 @@ var CODEOWNERS_LOCATIONS = [
9921
10522
  ];
9922
10523
  function findCodeowners(root, explicit) {
9923
10524
  if (explicit !== void 0) {
9924
- const path = resolve12(root, explicit);
10525
+ const path = resolve13(root, explicit);
9925
10526
  return existsSync3(path) ? path : null;
9926
10527
  }
9927
10528
  for (const loc of CODEOWNERS_LOCATIONS) {
9928
- const path = resolve12(root, ...loc.split("/"));
10529
+ const path = resolve13(root, ...loc.split("/"));
9929
10530
  if (existsSync3(path)) return path;
9930
10531
  }
9931
10532
  return null;
@@ -10040,7 +10641,7 @@ function toGlobs(pattern) {
10040
10641
  var NOT_FOUND_REASON = `no CODEOWNERS file found (looked for ${CODEOWNERS_LOCATIONS.join(", ")})`;
10041
10642
  async function deriveFromCodeowners(inputs, opts) {
10042
10643
  const records = /* @__PURE__ */ new Map();
10043
- const explicitPath = opts.explicit === void 0 ? void 0 : resolve12(opts.configDir ?? process.cwd(), opts.explicit);
10644
+ const explicitPath = opts.explicit === void 0 ? void 0 : resolve13(opts.configDir ?? process.cwd(), opts.explicit);
10044
10645
  if (explicitPath !== void 0 && !existsSync3(explicitPath)) {
10045
10646
  return {
10046
10647
  status: { available: false, reason: `CODEOWNERS not found at ${explicitPath}` },
@@ -10070,7 +10671,7 @@ async function deriveFromCodeowners(inputs, opts) {
10070
10671
  continue;
10071
10672
  }
10072
10673
  found += 1;
10073
- const label = toPosix2(relative9(opts.configDir ?? root, path));
10674
+ const label = toPosix2(relative10(opts.configDir ?? root, path));
10074
10675
  const key = `${path}\0${label}`;
10075
10676
  let file = parsed2.get(key);
10076
10677
  if (!file) {
@@ -10092,7 +10693,7 @@ async function deriveFromCodeowners(inputs, opts) {
10092
10693
  };
10093
10694
  }
10094
10695
  for (const input of bucket) {
10095
- const rel = toPosix2(relative9(root, input.absPath));
10696
+ const rel = toPosix2(relative10(root, input.absPath));
10096
10697
  const hit = ownersFor(owners, rel);
10097
10698
  records.set(
10098
10699
  input.label,
@@ -10116,13 +10717,13 @@ async function deriveFromCodeowners(inputs, opts) {
10116
10717
  return { status: { available: true }, records };
10117
10718
  }
10118
10719
  function toPosix2(p) {
10119
- return p.split(sep4).join("/");
10720
+ return p.split(sep5).join("/");
10120
10721
  }
10121
10722
 
10122
10723
  // src/meta/core/derive/git.ts
10123
10724
  import { constants as bufferConstants } from "node:buffer";
10124
10725
  import { spawn as spawn3 } from "node:child_process";
10125
- import { dirname as dirname8, extname as extname2, isAbsolute as isAbsolute6, posix as posix2, relative as relative10, sep as sep5 } from "node:path";
10726
+ import { dirname as dirname8, extname as extname2, isAbsolute as isAbsolute7, posix as posix5, relative as relative11, sep as sep6 } from "node:path";
10126
10727
  import { parse as parseYaml4 } from "yaml";
10127
10728
 
10128
10729
  // src/shared/pin.ts
@@ -10772,7 +11373,7 @@ function groupByRoot(inputs) {
10772
11373
  rootOf.set(dir, root);
10773
11374
  }
10774
11375
  if (root === null) continue;
10775
- const rel = relative10(root, input.absPath).split(sep5).join("/");
11376
+ const rel = relative11(root, input.absPath).split(sep6).join("/");
10776
11377
  const bucket = byRoot.get(root);
10777
11378
  if (bucket) bucket.push({ input, rel });
10778
11379
  else byRoot.set(root, [{ input, rel }]);
@@ -11225,14 +11826,14 @@ async function commitEvidence(run2, root, entry, blame, history, blobs, manifest
11225
11826
  return commits;
11226
11827
  }
11227
11828
  function insideRoot(root, abs) {
11228
- const rel = relative10(root, abs);
11229
- if (rel === "" || rel.startsWith("..") || isAbsolute6(rel)) return void 0;
11230
- return rel.split(sep5).join("/");
11829
+ const rel = relative11(root, abs);
11830
+ if (rel === "" || rel.startsWith("..") || isAbsolute7(rel)) return void 0;
11831
+ return rel.split(sep6).join("/");
11231
11832
  }
11232
11833
  function entryAt(entry, rel, relAtCommit) {
11233
11834
  if (relAtCommit === rel) return entry;
11234
- const fromHere = posix2.relative(posix2.dirname(rel), relAtCommit);
11235
- return posix2.join(posix2.dirname(entry), fromHere);
11835
+ const fromHere = posix5.relative(posix5.dirname(rel), relAtCommit);
11836
+ return posix5.join(posix5.dirname(entry), fromHere);
11236
11837
  }
11237
11838
  function manifestStamp(text2, key, join11) {
11238
11839
  if (text2 === void 0) return [];
@@ -11244,7 +11845,7 @@ function manifestStamp(text2, key, join11) {
11244
11845
  }
11245
11846
  if (!isRecord5(data)) return [];
11246
11847
  for (const [candidate, value] of Object.entries(data)) {
11247
- const same2 = join11 === "path" ? posix2.normalize(candidate) === posix2.normalize(key) : candidate === key;
11848
+ const same2 = join11 === "path" ? posix5.normalize(candidate) === posix5.normalize(key) : candidate === key;
11248
11849
  if (same2 && isRecord5(value)) return provenanceEntries(value[PROVENANCE_FIELD]);
11249
11850
  }
11250
11851
  return [];
@@ -11554,7 +12155,7 @@ async function deriveReviewsByRoot(inputs, git2, rootFor, ctx, requested) {
11554
12155
  if (byRoot.size === 0) {
11555
12156
  return unavailable3(requested, "no git repository contains the documents");
11556
12157
  }
11557
- const cacheDir = resolve13(ctx.configDir ?? ctx.cwd, REVIEW_CACHE_DIR);
12158
+ const cacheDir = resolve14(ctx.configDir ?? ctx.cwd, REVIEW_CACHE_DIR);
11558
12159
  for (const [root, bucket] of byRoot) {
11559
12160
  const client = ctx.reviews ?? await createReviewClient(root, { cwd: ctx.cwd, cache: ctx.cache, cacheDir, requested });
11560
12161
  const identity = await client.detect();
@@ -11736,33 +12337,31 @@ import { readFile as readFile9 } from "node:fs/promises";
11736
12337
 
11737
12338
  // src/meta/core/relocation.ts
11738
12339
  import { access, readFile as readFile8, stat as stat6, unlink } from "node:fs/promises";
11739
- import { basename as basename5, extname as extname3, isAbsolute as isAbsolute7, join as join8, relative as relative11, resolve as resolve14, sep as sep6 } from "node:path";
12340
+ import { basename as basename5, extname as extname3, isAbsolute as isAbsolute8, join as join8, relative as relative12, resolve as resolve15, sep as sep7 } from "node:path";
11740
12341
  import picomatch6 from "picomatch";
11741
12342
  import {
11742
- LineCounter as LineCounter4,
11743
- isMap as isMap6,
11744
- isNode as isNode3,
11745
- isScalar as isScalar4,
12343
+ LineCounter as LineCounter5,
12344
+ isMap as isMap8,
12345
+ isNode as isNode5,
12346
+ isScalar as isScalar7,
11746
12347
  isSeq as isSeq4,
11747
12348
  parse as parseYaml5,
11748
- parseDocument as parseDocument6
12349
+ parseDocument as parseDocument7
11749
12350
  } from "yaml";
11750
12351
 
11751
12352
  // src/meta/core/external-metadata-write.ts
11752
- import { posix as posix3 } from "node:path";
12353
+ import { posix as posix6 } from "node:path";
11753
12354
  import {
11754
- LineCounter as LineCounter3,
11755
- isMap as isMap5,
11756
- isNode as isNode2,
11757
- isPair,
11758
- isScalar as isScalar3,
11759
- isSeq as isSeq3,
11760
- parseDocument as parseDocument5,
11761
- stringify as stringify2
12355
+ LineCounter as LineCounter4,
12356
+ isMap as isMap7,
12357
+ isNode as isNode4,
12358
+ isScalar as isScalar6,
12359
+ parseDocument as parseDocument6,
12360
+ stringify as stringify3
11762
12361
  } from "yaml";
11763
12362
  function spliceManifestValue(text2, options) {
11764
12363
  const { entry, key, value, file } = options;
11765
- const join11 = options.join ?? PATH_JOIN;
12364
+ const join11 = options.join ?? PATH_JOIN2;
11766
12365
  const at2 = (line2) => {
11767
12366
  const name2 = file === void 0 ? "Manifest" : `Manifest ${file}`;
11768
12367
  if (line2 === void 0) return name2;
@@ -11777,8 +12376,8 @@ function spliceManifestValue(text2, options) {
11777
12376
  throw new DocmetaError(`${at2()}: refusing to write "${key}" for "${entry}" with no value.`);
11778
12377
  }
11779
12378
  const eol = detectEol(text2);
11780
- const lc = new LineCounter3();
11781
- const doc = parseDocument5(text2, { lineCounter: lc, uniqueKeys: false });
12379
+ const lc = new LineCounter4();
12380
+ const doc = parseDocument6(text2, { lineCounter: lc, uniqueKeys: false });
11782
12381
  const problem = doc.errors[0];
11783
12382
  if (problem) {
11784
12383
  throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
@@ -11787,15 +12386,15 @@ function spliceManifestValue(text2, options) {
11787
12386
  const r = rangeOf(node);
11788
12387
  return r ? lc.linePos(r[0]).line : void 0;
11789
12388
  };
11790
- const same2 = join11 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
12389
+ const same2 = join11 === PATH_JOIN2 ? (a, b) => posix6.normalize(a) === posix6.normalize(b) : (a, b) => a === b;
11791
12390
  const root = doc.contents;
11792
12391
  let spelled = entry;
11793
12392
  let edit;
11794
- if (root === null || isScalar3(root) && root.value === null && emptySource(text2, root)) {
12393
+ if (root === null || isScalar6(root) && root.value === null && emptySource(text2, root)) {
11795
12394
  edit = startManifest(text2, entry, key, value, eol);
11796
- } else if (!isMap5(root)) {
12395
+ } else if (!isMap7(root)) {
11797
12396
  throw new DocmetaError(
11798
- `${at2()}: the manifest must be a mapping from document ${join11 === PATH_JOIN ? "path" : `"${join11}"`} to owned keys.`
12397
+ `${at2()}: the manifest must be a mapping from document ${join11 === PATH_JOIN2 ? "path" : `"${join11}"`} to owned keys.`
11799
12398
  );
11800
12399
  } else {
11801
12400
  const style = detectStyle(text2, root, eol);
@@ -11817,7 +12416,7 @@ function spliceManifestValue(text2, options) {
11817
12416
  spelled = keyString(first.key);
11818
12417
  const where = at2(keyLine(first.key));
11819
12418
  const v = first.value;
11820
- if (!isMap5(v)) {
12419
+ if (!isMap7(v)) {
11821
12420
  throw new DocmetaError(`${where}: "${spelled}" must be a mapping of owned keys to values.`);
11822
12421
  }
11823
12422
  const hits = v.items.filter((kv) => keyString(kv.key) === key);
@@ -11860,30 +12459,30 @@ function spliceManifestValue(text2, options) {
11860
12459
  return { text: out, line };
11861
12460
  }
11862
12461
  function readManifestValue(text2, options) {
11863
- const join11 = options.join ?? PATH_JOIN;
11864
- const doc = parseDocument5(text2, { uniqueKeys: false });
12462
+ const join11 = options.join ?? PATH_JOIN2;
12463
+ const doc = parseDocument6(text2, { uniqueKeys: false });
11865
12464
  if (doc.errors.length > 0) return void 0;
11866
12465
  const root = doc.contents;
11867
- if (!isMap5(root)) return void 0;
11868
- const same2 = join11 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
12466
+ if (!isMap7(root)) return void 0;
12467
+ const same2 = join11 === PATH_JOIN2 ? (a, b) => posix6.normalize(a) === posix6.normalize(b) : (a, b) => a === b;
11869
12468
  const pair = root.items.find((p) => same2(keyString(p.key), options.entry));
11870
12469
  const held = pair?.value;
11871
- if (!isMap5(held)) return void 0;
12470
+ if (!isMap7(held)) return void 0;
11872
12471
  const kv = held.items.find((p) => keyString(p.key) === options.key);
11873
12472
  const node = kv?.value;
11874
- return isNode2(node) ? node.toJSON() : void 0;
12473
+ return isNode4(node) ? node.toJSON() : void 0;
11875
12474
  }
11876
12475
  function removeManifestKey(text2, options) {
11877
12476
  const { entry, key, file } = options;
11878
- const join11 = options.join ?? PATH_JOIN;
12477
+ const join11 = options.join ?? PATH_JOIN2;
11879
12478
  const at2 = (line) => {
11880
12479
  const name2 = file === void 0 ? "Manifest" : `Manifest ${file}`;
11881
12480
  if (line === void 0) return name2;
11882
12481
  return file === void 0 ? `${name2} line ${String(line)}` : `${name2}:${String(line)}`;
11883
12482
  };
11884
12483
  const eol = detectEol(text2);
11885
- const lc = new LineCounter3();
11886
- const doc = parseDocument5(text2, { lineCounter: lc, uniqueKeys: false });
12484
+ const lc = new LineCounter4();
12485
+ const doc = parseDocument6(text2, { lineCounter: lc, uniqueKeys: false });
11887
12486
  const problem = doc.errors[0];
11888
12487
  if (problem) {
11889
12488
  throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
@@ -11893,13 +12492,13 @@ function removeManifestKey(text2, options) {
11893
12492
  return r ? lc.linePos(r[0]).line : void 0;
11894
12493
  };
11895
12494
  const root = doc.contents;
11896
- if (root === null || isScalar3(root) && root.value === null) return { text: text2 };
11897
- if (!isMap5(root)) {
12495
+ if (root === null || isScalar6(root) && root.value === null) return { text: text2 };
12496
+ if (!isMap7(root)) {
11898
12497
  throw new DocmetaError(
11899
- `${at2()}: the manifest must be a mapping from document ${join11 === PATH_JOIN ? "path" : `"${join11}"`} to owned keys.`
12498
+ `${at2()}: the manifest must be a mapping from document ${join11 === PATH_JOIN2 ? "path" : `"${join11}"`} to owned keys.`
11900
12499
  );
11901
12500
  }
11902
- const same2 = join11 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
12501
+ const same2 = join11 === PATH_JOIN2 ? (a, b) => posix6.normalize(a) === posix6.normalize(b) : (a, b) => a === b;
11903
12502
  const matches = root.items.filter((p) => same2(keyString(p.key), entry));
11904
12503
  const [first, second] = matches;
11905
12504
  if (first && second) {
@@ -11911,7 +12510,7 @@ function removeManifestKey(text2, options) {
11911
12510
  const spelled = keyString(first.key);
11912
12511
  const where = at2(keyLine(first.key));
11913
12512
  const v = first.value;
11914
- if (!isMap5(v)) {
12513
+ if (!isMap7(v)) {
11915
12514
  throw new DocmetaError(`${where}: "${spelled}" must be a mapping of owned keys to values.`);
11916
12515
  }
11917
12516
  const hits = v.items.filter((kv) => keyString(kv.key) === key);
@@ -11935,16 +12534,13 @@ function removeManifestKey(text2, options) {
11935
12534
  `${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
11936
12535
  );
11937
12536
  }
11938
- let start = text2.lastIndexOf("\n", from[0] - 1) + 1;
11939
- let end = lineEnd(text2, to);
11940
- if (text2.startsWith(eol, end)) end += eol.length;
11941
- else if (start >= eol.length) start -= eol.length;
12537
+ let { start, end } = keyLines(text2, from[0], to, eol);
11942
12538
  if (whole) {
11943
12539
  if (text2.slice(start - 2 * eol.length, start) === eol + eol) start -= eol.length;
11944
12540
  else if (start === 0 && text2.startsWith(eol, end)) end += eol.length;
11945
12541
  }
11946
12542
  const out = text2.slice(0, start) + text2.slice(end);
11947
- const after = parseDocument5(out, { uniqueKeys: false });
12543
+ const after = parseDocument6(out, { uniqueKeys: false });
11948
12544
  const fail2 = (why) => new DocmetaError(
11949
12545
  `${at2()}: removing "${key}" for "${entry}" did not read back as removed, so nothing was written. ${why}`
11950
12546
  );
@@ -11964,14 +12560,14 @@ function removeManifestKey(text2, options) {
11964
12560
  }
11965
12561
  function renameManifestEntry(text2, options) {
11966
12562
  const { entry, to, file } = options;
11967
- const join11 = options.join ?? PATH_JOIN;
12563
+ const join11 = options.join ?? PATH_JOIN2;
11968
12564
  const at2 = (line) => {
11969
12565
  const name2 = file === void 0 ? "Manifest" : `Manifest ${file}`;
11970
12566
  if (line === void 0) return name2;
11971
12567
  return file === void 0 ? `${name2} line ${String(line)}` : `${name2}:${String(line)}`;
11972
- };
11973
- const lc = new LineCounter3();
11974
- const doc = parseDocument5(text2, { lineCounter: lc, uniqueKeys: false });
12568
+ };
12569
+ const lc = new LineCounter4();
12570
+ const doc = parseDocument6(text2, { lineCounter: lc, uniqueKeys: false });
11975
12571
  const problem = doc.errors[0];
11976
12572
  if (problem) {
11977
12573
  throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
@@ -11981,13 +12577,13 @@ function renameManifestEntry(text2, options) {
11981
12577
  return r2 ? lc.linePos(r2[0]).line : void 0;
11982
12578
  };
11983
12579
  const root = doc.contents;
11984
- if (root === null || isScalar3(root) && root.value === null) return { text: text2 };
11985
- if (!isMap5(root)) {
12580
+ if (root === null || isScalar6(root) && root.value === null) return { text: text2 };
12581
+ if (!isMap7(root)) {
11986
12582
  throw new DocmetaError(
11987
- `${at2()}: the manifest must be a mapping from document ${join11 === PATH_JOIN ? "path" : `"${join11}"`} to owned keys.`
12583
+ `${at2()}: the manifest must be a mapping from document ${join11 === PATH_JOIN2 ? "path" : `"${join11}"`} to owned keys.`
11988
12584
  );
11989
12585
  }
11990
- const same2 = join11 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
12586
+ const same2 = join11 === PATH_JOIN2 ? (a, b) => posix6.normalize(a) === posix6.normalize(b) : (a, b) => a === b;
11991
12587
  const [first, second] = root.items.filter((p) => same2(keyString(p.key), entry));
11992
12588
  if (first && second) {
11993
12589
  throw new DocmetaError(
@@ -12009,11 +12605,11 @@ function renameManifestEntry(text2, options) {
12009
12605
  `${at2(keyLine(first.key))}: "${spelled}" is written in a form this writer does not edit. Write it as "${spelled}: \u2026".`
12010
12606
  );
12011
12607
  }
12012
- const quoted = isScalar3(first.key) && (first.key.type === "QUOTE_DOUBLE" || first.key.type === "QUOTE_SINGLE");
12013
- const plain = stringify2(to, { lineWidth: 0 }).replace(/\n$/, "");
12608
+ const quoted = isScalar6(first.key) && (first.key.type === "QUOTE_DOUBLE" || first.key.type === "QUOTE_SINGLE");
12609
+ const plain = stringify3(to, { lineWidth: 0 }).replace(/\n$/, "");
12014
12610
  const insert = quoted || plain.includes("\n") ? JSON.stringify(to) : plain;
12015
12611
  const out = text2.slice(0, r[0]) + insert + text2.slice(end);
12016
- const after = parseDocument5(out, { uniqueKeys: false });
12612
+ const after = parseDocument6(out, { uniqueKeys: false });
12017
12613
  const fail2 = (why) => new DocmetaError(
12018
12614
  `${at2()}: renaming "${spelled}" to "${to}" did not read back as renamed, so nothing was written. ${why}`
12019
12615
  );
@@ -12047,24 +12643,13 @@ function appendEntry(text2, root, entry, key, value, style) {
12047
12643
  const col = firstKey ? colOf(text2, firstKey[0]) : 0;
12048
12644
  const pos = lineEnd(text2, contentEnd(text2, root) ?? text2.length);
12049
12645
  const last = root.items.at(-1);
12050
- const spaced = root.items.length > 1 && isNode2(last?.key) && last.key.spaceBefore === true;
12646
+ const spaced = root.items.length > 1 && isNode4(last?.key) && last.key.spaceBefore === true;
12051
12647
  return {
12052
12648
  start: pos,
12053
12649
  end: pos,
12054
12650
  insert: style.eol + (spaced ? style.eol : "") + blockLines(body, col, style)
12055
12651
  };
12056
12652
  }
12057
- function insertKey(text2, entryMap, key, value, style) {
12058
- const firstKey = rangeOf(entryMap.items[0]?.key);
12059
- const end = contentEnd(text2, entryMap);
12060
- if (!firstKey || end === void 0) return void 0;
12061
- const pos = lineEnd(text2, end);
12062
- return {
12063
- start: pos,
12064
- end: pos,
12065
- insert: style.eol + blockLines({ [key]: value }, colOf(text2, firstKey[0]), style)
12066
- };
12067
- }
12068
12653
  function fillEmptyEntry(text2, entryPair, key, value, style) {
12069
12654
  const start = colonEnd(text2, entryPair);
12070
12655
  const keyRange = rangeOf(entryPair.key);
@@ -12073,37 +12658,9 @@ function fillEmptyEntry(text2, entryPair, key, value, style) {
12073
12658
  const col = colOf(text2, keyRange[0]) + style.step;
12074
12659
  return { start, end, insert: style.eol + blockLines({ [key]: value }, col, style) };
12075
12660
  }
12076
- function replaceBlockValue(text2, pair, value, style) {
12077
- const colon = colonEnd(text2, pair);
12078
- const keyRange = rangeOf(pair.key);
12079
- if (colon === void 0 || !keyRange) return void 0;
12080
- const keyCol = colOf(text2, keyRange[0]);
12081
- let start = colon;
12082
- let ownLine = false;
12083
- let i = colon;
12084
- while (text2.charAt(i) === " " || text2.charAt(i) === " ") i++;
12085
- if (text2.charAt(i) === "#") {
12086
- start = lineEnd(text2, i);
12087
- ownLine = true;
12088
- }
12089
- const old = pair.value;
12090
- const oldEnd = isScalar3(old) && old.value === null && emptySource(text2, old) ? start : contentEnd(text2, old) ?? start;
12091
- const end = Math.max(start, oldEnd);
12092
- let insert = afterColon(value, keyCol, style);
12093
- if (ownLine && !insert.startsWith(style.eol)) {
12094
- insert = style.eol + " ".repeat(keyCol + style.step) + insert.trimStart();
12095
- }
12096
- return { start, end, insert };
12097
- }
12098
- function replaceFlowValue(text2, pair, value) {
12099
- const r = rangeOf(pair.value);
12100
- if (!r || emptySource(text2, pair.value)) return void 0;
12101
- const end = contentEnd(text2, pair.value) ?? r[1];
12102
- return { start: r[0], end, insert: flowText(value) };
12103
- }
12104
12661
  function readBack(before, out, spelled, key, value, fail2) {
12105
- const lc = new LineCounter3();
12106
- const after = parseDocument5(out, { lineCounter: lc, uniqueKeys: false });
12662
+ const lc = new LineCounter4();
12663
+ const after = parseDocument6(out, { lineCounter: lc, uniqueKeys: false });
12107
12664
  const problem = after.errors[0];
12108
12665
  if (problem) throw fail2(`The result is not valid YAML: ${problem.message}`);
12109
12666
  const was = before.toJS({ maxAliasCount: 100 });
@@ -12124,114 +12681,13 @@ function readBack(before, out, spelled, key, value, fail2) {
12124
12681
  throw fail2("Another value in the manifest would have changed.");
12125
12682
  }
12126
12683
  const root = after.contents;
12127
- const pair = isMap5(root) ? root.items.find((p) => keyString(p.key) === spelled) : void 0;
12684
+ const pair = isMap7(root) ? root.items.find((p) => keyString(p.key) === spelled) : void 0;
12128
12685
  const entryMap = pair?.value;
12129
- const kv = isMap5(entryMap) ? entryMap.items.find((p) => keyString(p.key) === key) : void 0;
12686
+ const kv = isMap7(entryMap) ? entryMap.items.find((p) => keyString(p.key) === key) : void 0;
12130
12687
  const r = rangeOf(kv?.value) ?? rangeOf(kv?.key);
12131
12688
  if (!r) throw fail2("The written value could not be found again.");
12132
12689
  return lc.linePos(r[0]).line;
12133
12690
  }
12134
- function blockLines(pair, col, style) {
12135
- const s = stringify2(pair, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
12136
- return indentLines(s.replace(/\n$/, "").split("\n"), col, 0).join(style.eol);
12137
- }
12138
- function afterColon(value, col, style) {
12139
- const s = stringify2({ k: value }, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
12140
- const lines = s.replace(/\n$/, "").slice("k:".length).split("\n");
12141
- return indentLines(lines, col, 1).join(style.eol);
12142
- }
12143
- function indentLines(lines, col, from) {
12144
- const pad2 = " ".repeat(col);
12145
- return lines.map((l, i) => i < from || l === "" ? l : pad2 + l);
12146
- }
12147
- function flowText(value) {
12148
- const s = stringify2(value, { lineWidth: 0, collectionStyle: "flow" }).replace(/\n$/, "");
12149
- return s.includes("\n") ? JSON.stringify(value) : s;
12150
- }
12151
- function detectEol(text2) {
12152
- const i = text2.indexOf("\n");
12153
- return i > 0 && text2.charAt(i - 1) === "\r" ? "\r\n" : "\n";
12154
- }
12155
- function detectStyle(text2, root, eol) {
12156
- let step = 2;
12157
- for (const pair of root.items) {
12158
- const v = pair.value;
12159
- const entryKey = rangeOf(pair.key);
12160
- const ownedKey = isMap5(v) && v.flow !== true ? rangeOf(v.items[0]?.key) : void 0;
12161
- if (entryKey && ownedKey) {
12162
- const d = colOf(text2, ownedKey[0]) - colOf(text2, entryKey[0]);
12163
- if (d > 0) {
12164
- step = d;
12165
- break;
12166
- }
12167
- }
12168
- }
12169
- return { step, indentSeq: seqIndented(text2, root) ?? true, eol };
12170
- }
12171
- function seqIndented(text2, node) {
12172
- if (isMap5(node)) {
12173
- for (const pair of node.items) {
12174
- const v = pair.value;
12175
- if (isSeq3(v) && v.flow !== true && v.items.length > 0) {
12176
- const k = rangeOf(pair.key);
12177
- const s = rangeOf(v);
12178
- if (k && s) return colOf(text2, s[0]) > colOf(text2, k[0]);
12179
- }
12180
- const inner = seqIndented(text2, v);
12181
- if (inner !== void 0) return inner;
12182
- }
12183
- } else if (isSeq3(node)) {
12184
- for (const item of node.items) {
12185
- const inner = seqIndented(text2, item);
12186
- if (inner !== void 0) return inner;
12187
- }
12188
- }
12189
- return void 0;
12190
- }
12191
- function contentEnd(text2, node) {
12192
- if (isMap5(node) && node.flow !== true) {
12193
- const last = node.items.at(-1);
12194
- if (last) return contentEnd(text2, last.value) ?? contentEnd(text2, last.key);
12195
- } else if (isSeq3(node) && node.flow !== true) {
12196
- const last = node.items.at(-1);
12197
- if (last !== void 0) {
12198
- return isPair(last) ? contentEnd(text2, last.value) ?? contentEnd(text2, last.key) : contentEnd(text2, last);
12199
- }
12200
- }
12201
- const r = rangeOf(node);
12202
- if (!r) return void 0;
12203
- let end = r[1];
12204
- while (end > r[0] && /\s/.test(text2.charAt(end - 1))) end--;
12205
- return end;
12206
- }
12207
- function colonEnd(text2, pair) {
12208
- const r = rangeOf(pair.key);
12209
- if (!r) return void 0;
12210
- let i = r[1];
12211
- while (text2.charAt(i) === " " || text2.charAt(i) === " ") i++;
12212
- return text2.charAt(i) === ":" ? i + 1 : void 0;
12213
- }
12214
- function lineEnd(text2, i) {
12215
- const j = text2.indexOf("\n", i);
12216
- if (j < 0) return text2.length;
12217
- return j > 0 && text2.charAt(j - 1) === "\r" ? j - 1 : j;
12218
- }
12219
- function colOf(text2, offset) {
12220
- if (offset === 0) return 0;
12221
- let start = text2.lastIndexOf("\n", offset - 1) + 1;
12222
- if (start === 0 && text2.startsWith("\uFEFF")) start = 1;
12223
- return offset - start;
12224
- }
12225
- function rangeOf(node) {
12226
- return isNode2(node) ? node.range ?? void 0 : void 0;
12227
- }
12228
- function emptySource(text2, node) {
12229
- const r = rangeOf(node);
12230
- return !r || text2.slice(r[0], r[1]).trim() === "";
12231
- }
12232
- function keyString(key) {
12233
- return isScalar3(key) ? String(key.value) : String(key);
12234
- }
12235
12691
  function isRecord6(x) {
12236
12692
  return typeof x === "object" && x !== null && !Array.isArray(x);
12237
12693
  }
@@ -12260,13 +12716,13 @@ function relocationContext(run2, extras) {
12260
12716
  ...extras.onNotice !== void 0 ? { onNotice: extras.onNotice } : {}
12261
12717
  };
12262
12718
  }
12263
- var toPosix3 = (p) => p.split(sep6).join("/");
12719
+ var toPosix3 = (p) => p.split(sep7).join("/");
12264
12720
  function reported2(abs, base) {
12265
- const rel = relative11(base, abs);
12721
+ const rel = relative12(base, abs);
12266
12722
  return rel === "" ? "." : toPosix3(rel);
12267
12723
  }
12268
12724
  function configSource(path, cwd) {
12269
- return toPosix3(relative11(cwd, path)) || basename5(path);
12725
+ return toPosix3(relative12(cwd, path)) || basename5(path);
12270
12726
  }
12271
12727
  function cloneCollection(c) {
12272
12728
  return {
@@ -12284,7 +12740,8 @@ var Model = class {
12284
12740
  for (const c of this.collections) {
12285
12741
  c.externalMetadata.forEach((m, index) => {
12286
12742
  const url = classifyRef(m.file).kind === "url";
12287
- const absPath = url ? void 0 : isAbsolute7(m.file) ? m.file : resolve14(configDir, m.file);
12743
+ const perPage = !url && hasPagePlaceholder(m.file);
12744
+ const absPath = url || perPage ? void 0 : isAbsolute8(m.file) ? m.file : resolve15(configDir, m.file);
12288
12745
  this.manifests.push({
12289
12746
  collection: c.name,
12290
12747
  index,
@@ -12293,6 +12750,7 @@ var Model = class {
12293
12750
  absPath,
12294
12751
  file: absPath === void 0 ? m.file : reported2(absPath, ctx.base),
12295
12752
  join: externalMetadataJoin(m),
12753
+ perPage,
12296
12754
  created: false,
12297
12755
  keysBefore: [...m.keys],
12298
12756
  keysAdded: [],
@@ -12319,6 +12777,22 @@ var Model = class {
12319
12777
  manifestsOf(collection) {
12320
12778
  return this.manifests.filter((m) => m.collection === collection);
12321
12779
  }
12780
+ /**
12781
+ * The one file `m` keeps a page's values in, and how the run reports it:
12782
+ * the page's own for a `{page}` manifest (proposal 0058), the declared file
12783
+ * for any other. `abs` is undefined only for a URL manifest.
12784
+ */
12785
+ fileFor(m, pageAbs) {
12786
+ if (!m.perPage) return { abs: m.absPath, file: m.file };
12787
+ const resolved = pageManifestPath(m.written, this.configDir, pageAbs);
12788
+ if ("outside" in resolved) {
12789
+ const declared = this.ctx.declaredCollections.find((c) => c.name === m.collection)?.externalMetadata[m.index];
12790
+ throw new DocmetaError(
12791
+ outsideRefusal(declared ?? { file: m.written, keys: [] }, m.collection, resolved.pageRel)
12792
+ );
12793
+ }
12794
+ return { abs: resolved.abs, file: reported2(resolved.abs, this.ctx.base) };
12795
+ }
12322
12796
  /** The manifest of one of `members` that owns `key`, in declaration order. */
12323
12797
  ownerOf(key, members) {
12324
12798
  return this.manifests.find(
@@ -12404,7 +12878,8 @@ var Model = class {
12404
12878
  url: false,
12405
12879
  absPath,
12406
12880
  file,
12407
- join: PATH_JOIN,
12881
+ join: PATH_JOIN2,
12882
+ perPage: false,
12408
12883
  created: true,
12409
12884
  keysBefore: [],
12410
12885
  keysAdded: [],
@@ -12441,8 +12916,8 @@ var Model = class {
12441
12916
  /** A label relative to the config directory, posix; undefined outside it. */
12442
12917
  relative(label) {
12443
12918
  if (label === "<stdin>") return void 0;
12444
- const rel = toPosix3(relative11(this.configDir, resolve14(this.ctx.base, label)));
12445
- return isAbsolute7(rel) || rel.startsWith("../") ? void 0 : rel;
12919
+ const rel = toPosix3(relative12(this.configDir, resolve15(this.ctx.base, label)));
12920
+ return isAbsolute8(rel) || rel.startsWith("../") ? void 0 : rel;
12446
12921
  }
12447
12922
  };
12448
12923
  function dedupe2(xs) {
@@ -12453,17 +12928,17 @@ function bare(path) {
12453
12928
  }
12454
12929
  async function collectionPath(target, cwd, configDir) {
12455
12930
  if (target === STDIN_TOKEN) return void 0;
12456
- const posix5 = target.replace(/\\/g, "/");
12457
- const scanned = picomatch6.scan(posix5);
12931
+ const posix7 = target.replace(/\\/g, "/");
12932
+ const scanned = picomatch6.scan(posix7);
12458
12933
  if (scanned.isGlob) {
12459
- const rel2 = toPosix3(relative11(configDir, resolve14(cwd, scanned.base === "" ? "." : scanned.base)));
12460
- if (isAbsolute7(rel2) || rel2 === ".." || rel2.startsWith("../")) return void 0;
12934
+ const rel2 = toPosix3(relative12(configDir, resolve15(cwd, scanned.base === "" ? "." : scanned.base)));
12935
+ if (isAbsolute8(rel2) || rel2 === ".." || rel2.startsWith("../")) return void 0;
12461
12936
  return rel2 === "" ? scanned.glob : `${rel2}/${scanned.glob}`;
12462
12937
  }
12463
- const abs = resolve14(cwd, posix5);
12464
- const rel = toPosix3(relative11(configDir, abs));
12465
- if (isAbsolute7(rel) || rel.startsWith("..")) return void 0;
12466
- let directory = posix5.endsWith("/");
12938
+ const abs = resolve15(cwd, posix7);
12939
+ const rel = toPosix3(relative12(configDir, abs));
12940
+ if (isAbsolute8(rel) || rel.startsWith("..")) return void 0;
12941
+ let directory = posix7.endsWith("/");
12467
12942
  if (!directory) {
12468
12943
  try {
12469
12944
  directory = (await stat6(abs)).isDirectory();
@@ -12484,28 +12959,30 @@ async function exists(absPath, file) {
12484
12959
  }
12485
12960
  }
12486
12961
  function plannedConfigDir(ctx) {
12487
- return ctx.configDir ?? findGitRoot(ctx.cwd) ?? resolve14(ctx.cwd);
12962
+ return ctx.configDir ?? findGitRoot(ctx.cwd) ?? resolve15(ctx.cwd);
12488
12963
  }
12489
12964
  async function keyHome(ctx, label, data, key) {
12490
12965
  const model = new Model(ctx, plannedConfigDir(ctx));
12491
12966
  const owner = model.ownerOf(key, model.members(label));
12492
12967
  if (owner !== void 0) {
12493
- if (owner.url || owner.absPath === void 0) {
12968
+ const at2 = model.fileFor(owner, resolve15(ctx.base, label));
12969
+ if (owner.url || at2.abs === void 0) {
12494
12970
  return { kind: "url", collection: owner.collection, file: owner.file };
12495
12971
  }
12496
12972
  return {
12497
12973
  kind: "manifest",
12498
12974
  collection: owner.collection,
12499
- file: owner.file,
12500
- absPath: owner.absPath,
12975
+ file: at2.file,
12976
+ absPath: at2.abs,
12501
12977
  join: owner.join,
12502
- entry: entryFor(owner, label, data, model.configDir, ctx.base, lazyKey(ctx.configFile, ctx.env))
12978
+ entry: entryFor(owner, label, data, model.configDir, ctx.base, lazyKey(ctx.configFile, ctx.env)),
12979
+ perPage: owner.perPage
12503
12980
  };
12504
12981
  }
12505
12982
  return { kind: "unowned", home: (await model.home(label, false)).proposed };
12506
12983
  }
12507
12984
  function entryFor(m, label, data, configDir, base, key) {
12508
- if (m.join === PATH_JOIN) return toPosix3(relative11(configDir, resolve14(base, label)));
12985
+ if (m.join === PATH_JOIN2) return toPosix3(relative12(configDir, resolve15(base, label)));
12509
12986
  let raw = data[m.join];
12510
12987
  if (isEncryptedValue(raw)) {
12511
12988
  const k = key();
@@ -12522,6 +12999,27 @@ function entryFor(m, label, data, configDir, base, key) {
12522
12999
  if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
12523
13000
  return void 0;
12524
13001
  }
13002
+ async function relocationPages(ctx, request) {
13003
+ const pages2 = new Set(request.files.map((label) => resolve15(ctx.base, label)));
13004
+ const root = ctx.configDir ?? ctx.cwd;
13005
+ for (const collection of ctx.declaredCollections) {
13006
+ const perPage = collection.externalMetadata.some(
13007
+ (m) => classifyRef(m.file).kind !== "url" && hasPagePlaceholder(m.file)
13008
+ );
13009
+ if (!perPage) continue;
13010
+ const walked = await resolveTargetSet({
13011
+ inputs: [...collection.paths],
13012
+ cwd: root,
13013
+ allowEmpty: true,
13014
+ ...gitignoreOptions({
13015
+ ...ctx.respectGitignore !== void 0 ? { flag: ctx.respectGitignore } : {},
13016
+ ...ctx.config?.respectGitignore !== void 0 ? { configured: ctx.config.respectGitignore } : {}
13017
+ })
13018
+ });
13019
+ for (const file of walked.files) pages2.add(resolve15(root, file));
13020
+ }
13021
+ return [...pages2];
13022
+ }
12525
13023
  async function planRelocation(ctx, request) {
12526
13024
  const configDir = plannedConfigDir(ctx);
12527
13025
  const model = new Model(ctx, configDir);
@@ -12529,7 +13027,8 @@ async function planRelocation(ctx, request) {
12529
13027
  const index = await loadExternalMetadata(ctx.declaredCollections, {
12530
13028
  configDir: ctx.configDir ?? ctx.cwd,
12531
13029
  base: ctx.base,
12532
- offline: ctx.config?.offline ?? false
13030
+ offline: ctx.config?.offline ?? false,
13031
+ pages: await relocationPages(ctx, request)
12533
13032
  });
12534
13033
  const trustRoot = schemaTrustRoot(ctx.cwd, ctx.configDir);
12535
13034
  const forced = ctx.as !== void 0 ? extractorByName(ctx.as) : void 0;
@@ -12546,17 +13045,17 @@ async function planRelocation(ctx, request) {
12546
13045
  if (beyond) return void 0;
12547
13046
  throw new DocmetaError(`Unsupported file type "${extname3(label)}" for "${label}". Use --as to override.`);
12548
13047
  }
12549
- const absPath = resolve14(ctx.base, label);
13048
+ const absPath = resolve15(ctx.base, label);
12550
13049
  const elements = resolveElements(label, ctx.config, currentMembers(label));
12551
13050
  let content = "";
12552
13051
  let own2 = {};
12553
- let unreadable;
13052
+ let unreadable2;
12554
13053
  if (beyond) {
12555
13054
  try {
12556
13055
  content = await readFile8(absPath, "utf8");
12557
13056
  own2 = extractor.extract(content, label, { elements }).data;
12558
13057
  } catch (err) {
12559
- unreadable = (errorMessage(err).split("\n")[0] ?? "").replace(/:\s*$/, "");
13058
+ unreadable2 = (errorMessage(err).split("\n")[0] ?? "").replace(/:\s*$/, "");
12560
13059
  }
12561
13060
  } else {
12562
13061
  content = await readFile8(absPath, "utf8");
@@ -12575,7 +13074,7 @@ async function planRelocation(ctx, request) {
12575
13074
  extractor,
12576
13075
  own: own2,
12577
13076
  beyond,
12578
- ...unreadable !== void 0 ? { unreadable } : {},
13077
+ ...unreadable2 !== void 0 ? { unreadable: unreadable2 } : {},
12579
13078
  intents: /* @__PURE__ */ new Map()
12580
13079
  };
12581
13080
  pages2.set(label, page);
@@ -12584,13 +13083,13 @@ async function planRelocation(ctx, request) {
12584
13083
  const supplied = (m, page, key) => {
12585
13084
  if (m.created || index === null) return void 0;
12586
13085
  let sv;
12587
- if (m.join === PATH_JOIN) {
13086
+ if (m.join === PATH_JOIN2) {
12588
13087
  sv = index.byPath.get(page.absPath)?.get(key);
12589
13088
  } else {
12590
13089
  const entry = entryFor(m, page.label, page.own, configDir, ctx.base, encryptionKey);
12591
13090
  sv = entry === void 0 ? void 0 : index.byField.get(m.join)?.get(entry)?.get(key);
12592
13091
  }
12593
- return sv !== void 0 && sv.collection === m.collection && sv.file === m.file ? sv : void 0;
13092
+ return sv !== void 0 && sv.collection === m.collection && sv.file === model.fileFor(m, page.absPath).file ? sv : void 0;
12594
13093
  };
12595
13094
  const stay = (page, key, reason, detail, links = {}) => {
12596
13095
  page.intents.set(key, { kind: "stay", reason, detail, ...links });
@@ -12610,7 +13109,7 @@ async function planRelocation(ctx, request) {
12610
13109
  }
12611
13110
  const held = supplied(m, page, key);
12612
13111
  if (held !== void 0 && !deepEqual(held.value, page.own[key])) {
12613
- stay(page, key, "values-differ", `the page and ${m.file} hold different values`, { toward: m });
13112
+ stay(page, key, "values-differ", `the page and ${held.file} hold different values`, { toward: m });
12614
13113
  return;
12615
13114
  }
12616
13115
  page.intents.set(key, { kind: "out", manifest: m, reason, drop: held !== void 0, entry });
@@ -12623,7 +13122,7 @@ async function planRelocation(ctx, request) {
12623
13122
  if (entry === void 0) return;
12624
13123
  if (Object.hasOwn(page.own, key)) {
12625
13124
  if (!deepEqual(held.value, page.own[key])) {
12626
- stay(page, key, "values-differ", `the page and ${m.file} hold different values`, { blocks: m });
13125
+ stay(page, key, "values-differ", `the page and ${held.file} hold different values`, { blocks: m });
12627
13126
  return;
12628
13127
  }
12629
13128
  page.intents.set(key, { kind: "in", manifest: m, drop: true, entry, value: held.value });
@@ -12669,7 +13168,7 @@ async function planRelocation(ctx, request) {
12669
13168
  preferencesOf.set(label, prefs);
12670
13169
  const planned = model.members(label);
12671
13170
  const joins = new Set(
12672
- model.manifests.filter((m) => planned.includes(m.collection) && m.join !== PATH_JOIN).map((m) => m.join)
13171
+ model.manifests.filter((m) => planned.includes(m.collection) && m.join !== PATH_JOIN2).map((m) => m.join)
12673
13172
  );
12674
13173
  for (const m of model.manifests) {
12675
13174
  if (planned.includes(m.collection)) for (const k of [...m.keysBefore, ...m.keysAdded]) marked.add(k);
@@ -12759,7 +13258,7 @@ async function planRelocation(ctx, request) {
12759
13258
  });
12760
13259
  const labels = dedupe2([
12761
13260
  ...request.files,
12762
- ...walked.files.map((f) => reported2(resolve14(configDir, f), ctx.base))
13261
+ ...walked.files.map((f) => reported2(resolve15(configDir, f), ctx.base))
12763
13262
  ]).filter((label) => model.members(label).includes(name2));
12764
13263
  for (const label of labels) {
12765
13264
  const page = await load(label, !runSet.has(label));
@@ -12776,7 +13275,7 @@ async function planRelocation(ctx, request) {
12776
13275
  const changing = covered ? out : out.filter((k) => m.keysAdded.includes(k));
12777
13276
  for (const key of changing) if (!page.intents.has(key)) stay(page, key, "unreadable", detail);
12778
13277
  for (const key of m.keysRemoved) {
12779
- const held = m.join !== PATH_JOIN || !m.created && index?.byPath.get(page.absPath)?.get(key)?.file === m.file;
13278
+ const held = m.join !== PATH_JOIN2 || !m.created && index?.byPath.get(page.absPath)?.get(key)?.file === model.fileFor(m, page.absPath).file;
12780
13279
  if (held && !page.intents.has(key)) stay(page, key, "unreadable", detail, { blocks: m });
12781
13280
  }
12782
13281
  continue;
@@ -12827,35 +13326,50 @@ async function planRelocation(ctx, request) {
12827
13326
  const writes = [];
12828
13327
  const lines = /* @__PURE__ */ new Map();
12829
13328
  for (const m of model.manifests) {
12830
- if (m.url || m.absPath === void 0) continue;
13329
+ if (m.url) continue;
12831
13330
  const undeclared = model.finalKeys(m).length === 0 && !m.created;
12832
- const outs = [];
12833
- const ins = [];
13331
+ const files = /* @__PURE__ */ new Map();
13332
+ const group = (at2) => {
13333
+ if (at2.abs === void 0) return void 0;
13334
+ const have = files.get(at2.abs);
13335
+ if (have !== void 0) return have;
13336
+ const made = { file: at2.file, outs: [], ins: [] };
13337
+ files.set(at2.abs, made);
13338
+ return made;
13339
+ };
13340
+ if (m.created) group({ abs: m.absPath, file: m.file });
12834
13341
  for (const page of orderedPages()) {
12835
13342
  for (const [key, intent] of orderedIntents(page)) {
12836
- if (intent.kind === "out" && intent.manifest === m) {
12837
- outs.push({ page: page.label, entry: intent.entry, key, value: page.own[key], drop: intent.drop });
12838
- } else if (intent.kind === "in" && intent.manifest === m) {
12839
- ins.push({ entry: intent.entry, key });
13343
+ if ((intent.kind === "out" || intent.kind === "in") && intent.manifest === m) {
13344
+ const into = group(model.fileFor(m, page.absPath));
13345
+ if (intent.kind === "out") {
13346
+ into?.outs.push({ page: page.label, entry: intent.entry, key, value: page.own[key], drop: intent.drop });
13347
+ } else {
13348
+ into?.ins.push({ entry: intent.entry, key });
13349
+ }
12840
13350
  }
12841
13351
  }
12842
13352
  }
12843
- if (!undeclared) assertNoStrandedEntries(m, index, pages2, configDir);
12844
- if (undeclared || !m.created && outs.length === 0 && ins.length === 0) continue;
12845
- let text2 = m.created ? "" : await readManifestText(m);
12846
- const before = text2;
12847
- const where = { join: m.join, file: m.file };
12848
- for (const o of outs) {
12849
- if (!o.drop) text2 = spliceManifestValue(text2, { ...where, entry: o.entry, key: o.key, value: o.value }).text;
12850
- }
12851
- for (const i of ins) text2 = removeManifestKey(text2, { ...where, entry: i.entry, key: i.key }).text;
12852
- const found = keyLines(text2, m.join);
12853
- for (const o of outs) {
12854
- const line = found.get(entryId(o.entry, m.join))?.get(o.key);
12855
- if (line !== void 0) lines.set(lineId(o.page, m.file, o.key), line);
12856
- }
12857
- if (m.created || text2 !== before) {
12858
- writes.push({ path: m.absPath, text: text2, kind: "manifest", before: m.created ? null : before });
13353
+ if (!undeclared) assertNoStrandedEntries(m, index, pages2, configDir, (abs) => model.fileFor(m, abs).file);
13354
+ if (undeclared) continue;
13355
+ for (const [path, { file, outs, ins }] of files) {
13356
+ if (!m.created && outs.length === 0 && ins.length === 0) continue;
13357
+ const disk = m.created ? null : await readManifestText(path, file, m.perPage);
13358
+ let text2 = disk ?? "";
13359
+ const before = text2;
13360
+ const where = { join: m.join, file };
13361
+ for (const o of outs) {
13362
+ if (!o.drop) text2 = spliceManifestValue(text2, { ...where, entry: o.entry, key: o.key, value: o.value }).text;
13363
+ }
13364
+ for (const i of ins) text2 = removeManifestKey(text2, { ...where, entry: i.entry, key: i.key }).text;
13365
+ const found = keyLines2(text2, m.join);
13366
+ for (const o of outs) {
13367
+ const line = found.get(entryId(o.entry, m.join))?.get(o.key);
13368
+ if (line !== void 0) lines.set(lineId(o.page, file, o.key), line);
13369
+ }
13370
+ if (m.created || text2 !== before) {
13371
+ writes.push({ path, text: text2, kind: "manifest", before: disk });
13372
+ }
12859
13373
  }
12860
13374
  }
12861
13375
  const finalCollections = model.final();
@@ -12954,7 +13468,7 @@ function planPageText(page) {
12954
13468
  refuse();
12955
13469
  }
12956
13470
  }
12957
- function assertNoStrandedEntries(m, index, pages2, configDir) {
13471
+ function assertNoStrandedEntries(m, index, pages2, configDir, fileOf) {
12958
13472
  if (index === null || m.keysRemoved.length === 0) return;
12959
13473
  for (const key of m.keysRemoved) {
12960
13474
  const moved = /* @__PURE__ */ new Set();
@@ -12962,10 +13476,10 @@ function assertNoStrandedEntries(m, index, pages2, configDir) {
12962
13476
  const i = page.intents.get(key);
12963
13477
  if (i?.kind === "in" && i.manifest === m) moved.add(entryId(i.entry, m.join));
12964
13478
  }
12965
- const stranded = m.join === PATH_JOIN ? [...index.byPath].filter(([, values]) => {
13479
+ const stranded = m.join === PATH_JOIN2 ? [...index.byPath].filter(([abs, values]) => {
12966
13480
  const sv = values.get(key);
12967
- return sv !== void 0 && sv.file === m.file && sv.collection === m.collection;
12968
- }).map(([abs]) => toPosix3(relative11(configDir, abs))) : [...index.byField.get(m.join) ?? /* @__PURE__ */ new Map()].filter(([, values]) => {
13481
+ return sv !== void 0 && sv.collection === m.collection && sv.file === fileOf(abs);
13482
+ }).map(([abs]) => toPosix3(relative12(configDir, abs))) : [...index.byField.get(m.join) ?? /* @__PURE__ */ new Map()].filter(([, values]) => {
12969
13483
  const sv = values.get(key);
12970
13484
  return sv !== void 0 && sv.file === m.file && sv.collection === m.collection;
12971
13485
  }).map(([value]) => value);
@@ -12977,29 +13491,29 @@ function assertNoStrandedEntries(m, index, pages2, configDir) {
12977
13491
  }
12978
13492
  }
12979
13493
  }
12980
- async function readManifestText(m) {
12981
- if (m.absPath === void 0) return "";
13494
+ async function readManifestText(path, file, perPage) {
12982
13495
  try {
12983
- return await readFile8(m.absPath, "utf8");
13496
+ return await readFile8(path, "utf8");
12984
13497
  } catch (err) {
12985
- throw new DocmetaError(`Manifest ${m.file} could not be read: ${errorMessage(err)}`);
13498
+ if (perPage && isMissing(err)) return null;
13499
+ throw new DocmetaError(`Manifest ${file} could not be read: ${errorMessage(err)}`);
12986
13500
  }
12987
13501
  }
12988
- var entryId = (entry, join11) => join11 === PATH_JOIN ? toPosix3(entry).replace(/^\.\//, "").replace(/\/+/g, "/") : entry;
13502
+ var entryId = (entry, join11) => join11 === PATH_JOIN2 ? toPosix3(entry).replace(/^\.\//, "").replace(/\/+/g, "/") : entry;
12989
13503
  var lineId = (page, manifest, key) => `${page}\0${manifest}\0${key}`;
12990
- function keyLines(text2, join11) {
13504
+ function keyLines2(text2, join11) {
12991
13505
  const out = /* @__PURE__ */ new Map();
12992
- const lc = new LineCounter4();
12993
- const doc = parseDocument6(text2, { lineCounter: lc, uniqueKeys: false });
13506
+ const lc = new LineCounter5();
13507
+ const doc = parseDocument7(text2, { lineCounter: lc, uniqueKeys: false });
12994
13508
  const root = doc.contents;
12995
- if (!isMap6(root)) return out;
13509
+ if (!isMap8(root)) return out;
12996
13510
  for (const pair of root.items) {
12997
- const spelled = isScalar4(pair.key) ? String(pair.key.value) : String(pair.key);
12998
- if (!isMap6(pair.value)) continue;
13511
+ const spelled = isScalar7(pair.key) ? String(pair.key.value) : String(pair.key);
13512
+ if (!isMap8(pair.value)) continue;
12999
13513
  const keys = /* @__PURE__ */ new Map();
13000
13514
  for (const kv of pair.value.items) {
13001
- const name2 = isScalar4(kv.key) ? String(kv.key.value) : String(kv.key);
13002
- const range = isNode3(kv.key) ? kv.key.range : void 0;
13515
+ const name2 = isScalar7(kv.key) ? String(kv.key.value) : String(kv.key);
13516
+ const range = isNode5(kv.key) ? kv.key.range : void 0;
13003
13517
  if (range) keys.set(name2, lc.linePos(range[0]).line);
13004
13518
  }
13005
13519
  out.set(entryId(spelled, join11), keys);
@@ -13031,10 +13545,10 @@ async function configTarget(ctx, configDir) {
13031
13545
  return { path: join8(configDir, FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"), text: null, disk: null };
13032
13546
  }
13033
13547
  function editConfig(text2, model, source) {
13034
- const doc = parseDocument6(text2 ?? "");
13548
+ const doc = parseDocument7(text2 ?? "");
13035
13549
  const [problem] = doc.errors;
13036
13550
  if (problem !== void 0) throw new DocmetaError(`${source}: invalid YAML: ${problem.message}`);
13037
- if (doc.contents !== null && !isMap6(doc.contents)) {
13551
+ if (doc.contents !== null && !isMap8(doc.contents)) {
13038
13552
  throw new DocmetaError(`${source}: top level must be a mapping.`);
13039
13553
  }
13040
13554
  const fail2 = () => {
@@ -13044,7 +13558,7 @@ function editConfig(text2, model, source) {
13044
13558
  if (!isSeq4(node)) return;
13045
13559
  node.flow = true;
13046
13560
  if (quoted) {
13047
- for (const item of node.items) if (isScalar4(item)) item.type = "QUOTE_DOUBLE";
13561
+ for (const item of node.items) if (isScalar7(item)) item.type = "QUOTE_DOUBLE";
13048
13562
  }
13049
13563
  };
13050
13564
  const manifestNode = (d, file, keys) => {
@@ -13058,13 +13572,13 @@ function editConfig(text2, model, source) {
13058
13572
  for (let i = 0; i < declared; i++) {
13059
13573
  const c = model.collections[i];
13060
13574
  const node = isSeq4(existing) ? existing.items[i] : void 0;
13061
- if (c === void 0 || !isMap6(node)) return fail2();
13575
+ if (c === void 0 || !isMap8(node)) return fail2();
13062
13576
  for (const added of model.pathsAdded.filter((p) => p.collection === c.name)) {
13063
13577
  const paths = node.get("paths", true);
13064
13578
  if (!isSeq4(paths)) return fail2();
13065
13579
  const scalar = doc.createNode(added.path);
13066
13580
  const first = paths.items[0];
13067
- if (isScalar4(first) && first.type !== void 0) scalar.type = first.type;
13581
+ if (isScalar7(first) && first.type !== void 0) scalar.type = first.type;
13068
13582
  paths.add(scalar);
13069
13583
  }
13070
13584
  const refs = model.manifestsOf(c.name);
@@ -13081,7 +13595,7 @@ function editConfig(text2, model, source) {
13081
13595
  }
13082
13596
  if (m.keysAdded.length === 0) continue;
13083
13597
  const entry = node.getIn(["externalMetadata", m.index], true);
13084
- const keys = isMap6(entry) ? entry.get("keys", true) : void 0;
13598
+ const keys = isMap8(entry) ? entry.get("keys", true) : void 0;
13085
13599
  if (!isSeq4(keys)) return fail2();
13086
13600
  for (const key of m.keysAdded) keys.add(doc.createNode(key));
13087
13601
  }
@@ -13090,10 +13604,10 @@ function editConfig(text2, model, source) {
13090
13604
  if (m.keysRemoved.length === 0) continue;
13091
13605
  if (!isSeq4(list2)) return fail2();
13092
13606
  const entry = list2.items[m.index];
13093
- const keys = isMap6(entry) ? entry.get("keys", true) : void 0;
13607
+ const keys = isMap8(entry) ? entry.get("keys", true) : void 0;
13094
13608
  if (!isSeq4(keys)) return fail2();
13095
13609
  for (const key of m.keysRemoved) {
13096
- const at2 = keys.items.findIndex((item) => (isScalar4(item) ? String(item.value) : String(item)) === key);
13610
+ const at2 = keys.items.findIndex((item) => (isScalar7(item) ? String(item.value) : String(item)) === key);
13097
13611
  if (at2 >= 0) keys.delete(at2);
13098
13612
  }
13099
13613
  if (keys.items.length === 0) list2.delete(m.index);
@@ -13112,7 +13626,7 @@ function editConfig(text2, model, source) {
13112
13626
  flowSeq(node.get("paths", true), true);
13113
13627
  const made = node.get("externalMetadata", true);
13114
13628
  if (isSeq4(made)) {
13115
- for (const item of made.items) if (isMap6(item)) flowSeq(item.get("keys", true), false);
13629
+ for (const item of made.items) if (isMap8(item)) flowSeq(item.get("keys", true), false);
13116
13630
  }
13117
13631
  const seq = doc.get("collections", true);
13118
13632
  if (isSeq4(seq)) seq.add(node);
@@ -13147,16 +13661,18 @@ function buildResult(input) {
13147
13661
  const moved = [];
13148
13662
  const stayed = [];
13149
13663
  const intents = orderedIntents(page);
13664
+ const fileOf = (m) => model.fileFor(m, page.absPath).file;
13150
13665
  for (const [key, intent] of intents) {
13151
- if (intent.kind === "in") moved.push({ key, to: "page", from: intent.manifest.file, reason: "preferred" });
13666
+ if (intent.kind === "in") moved.push({ key, to: "page", from: fileOf(intent.manifest), reason: "preferred" });
13152
13667
  }
13153
13668
  for (const [key, intent] of intents) {
13154
13669
  if (intent.kind !== "out") continue;
13155
- const line = dryRun ? void 0 : lines.get(lineId(page.label, intent.manifest.file, key));
13670
+ const manifest = fileOf(intent.manifest);
13671
+ const line = dryRun ? void 0 : lines.get(lineId(page.label, manifest, key));
13156
13672
  moved.push({
13157
13673
  key,
13158
13674
  to: "manifest",
13159
- manifest: intent.manifest.file,
13675
+ manifest,
13160
13676
  ...line !== void 0 ? { line } : {},
13161
13677
  reason: intent.reason
13162
13678
  });
@@ -13201,7 +13717,7 @@ function buildResult(input) {
13201
13717
  };
13202
13718
  }
13203
13719
  async function applyRelocation(ctx, plan, io = {}) {
13204
- const write = io.write ?? ((path, text2) => writeFileAtomic(path, text2));
13720
+ const write = io.write ?? ((path, text2) => writeFileAtomic(path, text2, { createParents: true }));
13205
13721
  const remove = io.remove ?? ((path) => unlink(path));
13206
13722
  const label = (w) => w.kind === "config" ? configSource(w.path, ctx.cwd) : reported2(w.path, ctx.base);
13207
13723
  for (const w of plan.writes) {
@@ -13374,7 +13890,7 @@ function relocationOffers(plan) {
13374
13890
  }
13375
13891
 
13376
13892
  // src/meta/commands/validate.ts
13377
- import { resolve as resolve16, extname as extname4 } from "node:path";
13893
+ import { resolve as resolve17, extname as extname4 } from "node:path";
13378
13894
 
13379
13895
  // src/meta/core/checks.ts
13380
13896
  var CHECK_KEYWORD = "check";
@@ -13512,8 +14028,8 @@ async function runChecks(checks, entries, ctx = {}) {
13512
14028
  }
13513
14029
 
13514
14030
  // src/meta/core/derive/provenance-place.ts
13515
- import { isAbsolute as isAbsolute8, relative as relative12, resolve as resolve15, sep as sep7 } from "node:path";
13516
- var toPosix4 = (p) => p.split(sep7).join("/");
14031
+ import { isAbsolute as isAbsolute9, relative as relative13, resolve as resolve16, sep as sep8 } from "node:path";
14032
+ var toPosix4 = (p) => p.split(sep8).join("/");
13517
14033
  function provenanceManifests(collections, configDir, base) {
13518
14034
  const out = [];
13519
14035
  for (const collection of collections) {
@@ -13524,13 +14040,15 @@ function provenanceManifests(collections, configDir, base) {
13524
14040
  `collection ${collection.name}: ${PROVENANCE_FIELD} cannot come from a URL manifest, because manni meta derive writes it.`
13525
14041
  );
13526
14042
  }
13527
- const absPath = isAbsolute8(manifest.file) ? manifest.file : resolve15(configDir, manifest.file);
13528
- const rel = relative12(base, absPath);
14043
+ const absPath = isAbsolute9(manifest.file) ? manifest.file : resolve16(configDir, manifest.file);
14044
+ const perPage = hasPagePlaceholder(manifest.file);
13529
14045
  out.push({
13530
14046
  collection: collection.name,
13531
14047
  absPath,
13532
- file: rel === "" ? "." : toPosix4(rel),
13533
- join: externalMetadataJoin(manifest)
14048
+ file: perPage ? manifest.file : reportedPath(absPath, base),
14049
+ join: externalMetadataJoin(manifest),
14050
+ perPage,
14051
+ written: manifest.file
13534
14052
  });
13535
14053
  }
13536
14054
  }
@@ -13540,15 +14058,16 @@ function provenancePlace(label, data, manifests, collections, configDir, base) {
13540
14058
  if (manifests.length === 0) return void 0;
13541
14059
  const members = new Set(memberOf(collections, configDir, base, label));
13542
14060
  const owning = manifests.filter((m) => members.has(m.collection));
13543
- const [manifest, second] = owning;
13544
- if (manifest === void 0) return void 0;
14061
+ const [declared, second] = owning;
14062
+ if (declared === void 0) return void 0;
13545
14063
  if (second !== void 0) {
13546
14064
  throw new DocmetaError(
13547
- `${label} is in collections ${manifest.collection} and ${second.collection}, and both keep ${PROVENANCE_FIELD} in a manifest.`
14065
+ `${label} is in collections ${declared.collection} and ${second.collection}, and both keep ${PROVENANCE_FIELD} in a manifest.`
13548
14066
  );
13549
14067
  }
13550
- if (manifest.join === PATH_JOIN) {
13551
- const entry = toPosix4(relative12(configDir, resolve15(base, label)));
14068
+ const manifest = declared.perPage ? ownManifest(declared, label, collections, configDir, base) : declared;
14069
+ if (manifest.join === PATH_JOIN2) {
14070
+ const entry = toPosix4(relative13(configDir, resolve16(base, label)));
13552
14071
  return { manifest, absPath: manifest.absPath, entry, join: manifest.join };
13553
14072
  }
13554
14073
  const value = data[manifest.join];
@@ -13559,6 +14078,16 @@ function provenancePlace(label, data, manifests, collections, configDir, base) {
13559
14078
  }
13560
14079
  return { manifest, absPath: manifest.absPath, entry: String(value), join: manifest.join };
13561
14080
  }
14081
+ function ownManifest(manifest, label, collections, configDir, base) {
14082
+ const resolved = pageManifestPath(manifest.written, configDir, resolve16(base, label));
14083
+ if ("outside" in resolved) {
14084
+ const declared = collections.find((c) => c.name === manifest.collection)?.externalMetadata.find((m) => m.file === manifest.written);
14085
+ throw new DocmetaError(
14086
+ outsideRefusal(declared ?? { file: manifest.written, keys: [] }, manifest.collection, resolved.pageRel)
14087
+ );
14088
+ }
14089
+ return { ...manifest, absPath: resolved.abs, file: reportedPath(resolved.abs, base) };
14090
+ }
13562
14091
  function readerManifests(collections, configDir, base) {
13563
14092
  const local = collections.map((c) => ({
13564
14093
  ...c,
@@ -13657,7 +14186,9 @@ async function runValidate(opts) {
13657
14186
  const externalMetadata = await loadExternalMetadata(collections, {
13658
14187
  configDir: configDir ?? cwd,
13659
14188
  base,
13660
- offline: opts.offline ?? config?.offline ?? false
14189
+ offline: opts.offline ?? config?.offline ?? false,
14190
+ // A `{page}` manifest (0058) is read for exactly the pages this run reads.
14191
+ pages: files.map((file) => resolve17(base, file))
13661
14192
  });
13662
14193
  const validator = new Validator(
13663
14194
  schemaLoadOptions({
@@ -13688,7 +14219,7 @@ async function runValidate(opts) {
13688
14219
  const owned = new Set(merged.collisions.map((c) => c.key));
13689
14220
  const declaredMembers = declaredCollections === collections ? members : memberOf(declaredCollections, configDir ?? cwd, base, label);
13690
14221
  const joins = new Set(
13691
- declaredCollections.filter((c) => declaredMembers.includes(c.name)).flatMap((c) => c.externalMetadata.map(externalMetadataJoin)).filter((join11) => join11 !== PATH_JOIN)
14222
+ declaredCollections.filter((c) => declaredMembers.includes(c.name)).flatMap((c) => c.externalMetadata.map(externalMetadataJoin)).filter((join11) => join11 !== PATH_JOIN2)
13692
14223
  );
13693
14224
  const findings = [];
13694
14225
  for (const key of Object.keys(data)) {
@@ -13812,7 +14343,7 @@ async function runValidate(opts) {
13812
14343
  const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, extracted.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base);
13813
14344
  deriveInputs.push({
13814
14345
  label,
13815
- absPath: resolve16(base, label),
14346
+ absPath: resolve17(base, label),
13816
14347
  content,
13817
14348
  extracted,
13818
14349
  ...place !== void 0 ? { provenanceManifest: place } : {}
@@ -13943,7 +14474,7 @@ async function runValidate(opts) {
13943
14474
  await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
13944
14475
  }
13945
14476
  for (const file of files) {
13946
- const content = await readFile9(resolve16(base, file), "utf8");
14477
+ const content = await readFile9(resolve17(base, file), "utf8");
13947
14478
  await processOne(file, content, extname4(file));
13948
14479
  }
13949
14480
  if (unverified > 0) opts.onNotice?.(unverifiedWarning(unverified));
@@ -14089,7 +14620,7 @@ async function runValidate(opts) {
14089
14620
 
14090
14621
  // src/meta/commands/get.ts
14091
14622
  import { readFile as readFile10 } from "node:fs/promises";
14092
- import { resolve as resolve17, extname as extname5 } from "node:path";
14623
+ import { resolve as resolve18, extname as extname5 } from "node:path";
14093
14624
  async function runGet(opts) {
14094
14625
  const cwd = opts.cwd ?? process.cwd();
14095
14626
  if (opts.fields.length === 0) {
@@ -14148,7 +14679,9 @@ async function runGet(opts) {
14148
14679
  const externalMetadata = await loadExternalMetadata(collections, {
14149
14680
  configDir: configDir ?? cwd,
14150
14681
  base,
14151
- offline: opts.offline ?? config?.offline ?? false
14682
+ offline: opts.offline ?? config?.offline ?? false,
14683
+ // A `{page}` manifest (0058) is read for exactly the pages this run reads.
14684
+ pages: files.map((file) => resolve18(base, file))
14152
14685
  });
14153
14686
  const commands = commandsOf(config?.derive) ?? {};
14154
14687
  const deriving = opts.derived !== false;
@@ -14175,7 +14708,7 @@ async function runGet(opts) {
14175
14708
  const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, own2.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base);
14176
14709
  deriveInputs.push({
14177
14710
  label,
14178
- absPath: resolve17(base, label),
14711
+ absPath: resolve18(base, label),
14179
14712
  content,
14180
14713
  extracted: own2,
14181
14714
  ...place !== void 0 ? { provenanceManifest: place } : {}
@@ -14217,7 +14750,7 @@ async function runGet(opts) {
14217
14750
  readOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
14218
14751
  }
14219
14752
  for (const file of files) {
14220
- const content = await readFile10(resolve17(base, file), "utf8");
14753
+ const content = await readFile10(resolve18(base, file), "utf8");
14221
14754
  readOne(file, content, extname5(file));
14222
14755
  }
14223
14756
  if (deriving) {
@@ -14343,16 +14876,16 @@ function parseJsonPointer(pointer) {
14343
14876
 
14344
14877
  // src/meta/commands/schemas.ts
14345
14878
  import { existsSync as existsSync4 } from "node:fs";
14346
- import { mkdir as mkdir2, readFile as readFile11 } from "node:fs/promises";
14347
- import { dirname as dirname10, extname as extname6, isAbsolute as isAbsolute9, join as join9, relative as relative13, resolve as resolve18, sep as sep8 } from "node:path";
14348
- import { parseDocument as parseDocument7, parse as parseYaml6, stringify as stringify3 } from "yaml";
14879
+ import { mkdir as mkdir3, readFile as readFile11 } from "node:fs/promises";
14880
+ import { dirname as dirname10, extname as extname6, isAbsolute as isAbsolute10, join as join9, relative as relative14, resolve as resolve19, sep as sep9 } from "node:path";
14881
+ import { parseDocument as parseDocument8, parse as parseYaml6, stringify as stringify4 } from "yaml";
14349
14882
  function getSchemasInfo() {
14350
14883
  return { builtins: listBuiltins(), formats: listFormats() };
14351
14884
  }
14352
14885
  var DEFAULT_VENDOR_DIR = "./schema";
14353
14886
  var DEFAULT_CONFIG_NAME = "manni.config.yaml";
14354
14887
  function posixRelative(from, to) {
14355
- return relative13(from, to).split(sep8).join("/");
14888
+ return relative14(from, to).split(sep9).join("/");
14356
14889
  }
14357
14890
  function vendorFileName(url) {
14358
14891
  let parsed2;
@@ -14415,9 +14948,9 @@ function foldEntry(entries, next) {
14415
14948
  }
14416
14949
  function renderConfig(existing, entries, section) {
14417
14950
  if (existing === null) {
14418
- return stringify3({ [META_SECTION]: { schemas: entries } });
14951
+ return stringify4({ [META_SECTION]: { schemas: entries } });
14419
14952
  }
14420
- const doc = parseDocument7(existing);
14953
+ const doc = parseDocument8(existing);
14421
14954
  doc.setIn(section === void 0 ? ["schemas"] : [section, "schemas"], entries);
14422
14955
  return doc.toString();
14423
14956
  }
@@ -14438,13 +14971,13 @@ async function runVendorSchema(opts) {
14438
14971
  );
14439
14972
  }
14440
14973
  const loaded = await loadConfig(opts.configPath, cwd);
14441
- const configPath = loaded?.path ?? resolve18(cwd, DEFAULT_CONFIG_NAME);
14974
+ const configPath = loaded?.path ?? resolve19(cwd, DEFAULT_CONFIG_NAME);
14442
14975
  const configDir = loaded?.dir ?? cwd;
14443
- const absDir = resolve18(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
14976
+ const absDir = resolve19(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
14444
14977
  const absFile = join9(absDir, vendorFileName(url));
14445
14978
  await assertNotIgnored(absFile, absDir, cwd, opts.onNotice);
14446
14979
  const fromConfig = posixRelative(configDir, absFile);
14447
- const ref = isAbsolute9(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
14980
+ const ref = isAbsolute10(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
14448
14981
  const { bytes } = await fetchSchemaBytes(url, {
14449
14982
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
14450
14983
  ...opts.maxBytes !== void 0 ? { maxBytes: opts.maxBytes } : {}
@@ -14467,7 +15000,7 @@ async function runVendorSchema(opts) {
14467
15000
  );
14468
15001
  }
14469
15002
  }
14470
- await mkdir2(absDir, { recursive: true });
15003
+ await mkdir3(absDir, { recursive: true });
14471
15004
  await writeFileAtomic(absFile, bytes);
14472
15005
  const existing = loaded ? await readFile11(configPath, "utf8") : null;
14473
15006
  const section = loaded ? loaded.section : META_SECTION;
@@ -14598,11 +15131,6 @@ async function runInferSchema(opts) {
14598
15131
  });
14599
15132
  const joinKey = lazyKey(configFile, void 0);
14600
15133
  const usingStdin = inputs.includes(STDIN_TOKEN);
14601
- const externalMetadata = await loadExternalMetadata(collections, {
14602
- configDir: configDir ?? cwd,
14603
- base,
14604
- offline: opts.offline ?? config?.offline ?? false
14605
- });
14606
15134
  if (inputs.length === 0) {
14607
15135
  throw new DocmetaError(
14608
15136
  "No files to scan. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
@@ -14621,7 +15149,7 @@ async function runInferSchema(opts) {
14621
15149
  }
14622
15150
  let absOut;
14623
15151
  if (opts.out !== void 0) {
14624
- absOut = resolve18(cwd, opts.out);
15152
+ absOut = resolve19(cwd, opts.out);
14625
15153
  await assertNotIgnored(
14626
15154
  absOut,
14627
15155
  dirname10(absOut),
@@ -14663,8 +15191,14 @@ async function runInferSchema(opts) {
14663
15191
  gitignoreSkipped,
14664
15192
  action: "scanned"
14665
15193
  });
15194
+ const externalMetadata = await loadExternalMetadata(collections, {
15195
+ configDir: configDir ?? cwd,
15196
+ base,
15197
+ offline: opts.offline ?? config?.offline ?? false,
15198
+ pages: files.map((file) => resolve19(base, file))
15199
+ });
14666
15200
  const stats = /* @__PURE__ */ new Map();
14667
- const unreadable = [];
15201
+ const unreadable2 = [];
14668
15202
  let filesScanned = 0;
14669
15203
  let filesWithoutMetadata = 0;
14670
15204
  const scanOne = (label, content, extension) => {
@@ -14689,7 +15223,7 @@ async function runInferSchema(opts) {
14689
15223
  { encryptionKey: joinKey }
14690
15224
  ).extracted;
14691
15225
  } catch (err) {
14692
- unreadable.push({
15226
+ unreadable2.push({
14693
15227
  file: label,
14694
15228
  message: err instanceof Error ? err.message : String(err)
14695
15229
  });
@@ -14709,10 +15243,10 @@ async function runInferSchema(opts) {
14709
15243
  scanOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
14710
15244
  }
14711
15245
  for (const file of files) {
14712
- const content = await readFile11(resolve18(base, file), "utf8");
15246
+ const content = await readFile11(resolve19(base, file), "utf8");
14713
15247
  scanOne(file, content, extname6(file));
14714
15248
  }
14715
- for (const u of unreadable) {
15249
+ for (const u of unreadable2) {
14716
15250
  opts.onNotice?.(`could not read metadata from "${u.file}": ${u.message}`);
14717
15251
  }
14718
15252
  const all = [...stats.entries()].map(([key, stat7]) => {
@@ -14751,14 +15285,14 @@ async function runInferSchema(opts) {
14751
15285
  };
14752
15286
  const keys = kept.map(({ sawEmptyString: _drop, ...rest }) => rest);
14753
15287
  if (absOut !== void 0) {
14754
- await mkdir2(dirname10(absOut), { recursive: true });
15288
+ await mkdir3(dirname10(absOut), { recursive: true });
14755
15289
  await writeFileAtomic(absOut, `${JSON.stringify(draft, null, 2)}
14756
15290
  `);
14757
15291
  }
14758
15292
  return {
14759
15293
  filesScanned,
14760
15294
  filesWithoutMetadata,
14761
- unreadable,
15295
+ unreadable: unreadable2,
14762
15296
  keys,
14763
15297
  hiddenByMinCoverage: all.length - kept.length,
14764
15298
  draft,
@@ -14770,8 +15304,8 @@ async function runInferSchema(opts) {
14770
15304
  // src/meta/commands/query.ts
14771
15305
  import { randomBytes as randomBytes2 } from "node:crypto";
14772
15306
  import { existsSync as existsSync5 } from "node:fs";
14773
- import { mkdir as mkdir3, open as open2, readFile as readFile12, rename as rename3, rm as rm3 } from "node:fs/promises";
14774
- import { dirname as dirname11, isAbsolute as isAbsolute10, resolve as resolve19, extname as extname7, sep as sep9 } from "node:path";
15307
+ import { mkdir as mkdir4, open as open2, readFile as readFile12, rename as rename3, rm as rm3 } from "node:fs/promises";
15308
+ import { dirname as dirname11, isAbsolute as isAbsolute11, resolve as resolve20, extname as extname7, sep as sep10 } from "node:path";
14775
15309
 
14776
15310
  // src/shared/prompt.ts
14777
15311
  import { createInterface } from "node:readline";
@@ -14829,10 +15363,10 @@ async function ensureEncryptionKey(opts) {
14829
15363
  }
14830
15364
 
14831
15365
  // src/meta/commands/query.ts
14832
- import { parseDocument as parseDocument9, isMap as isMap8, isSeq as isSeq5, isScalar as isScalar6 } from "yaml";
15366
+ import { parseDocument as parseDocument10, isMap as isMap10, isSeq as isSeq5, isScalar as isScalar9 } from "yaml";
14833
15367
 
14834
15368
  // src/meta/core/location-writes.ts
14835
- import { LineCounter as LineCounter5, isMap as isMap7, isNode as isNode4, isScalar as isScalar5, parseDocument as parseDocument8 } from "yaml";
15369
+ import { LineCounter as LineCounter6, isMap as isMap9, isNode as isNode6, isScalar as isScalar8, parseDocument as parseDocument9 } from "yaml";
14836
15370
  var dedupe3 = (xs) => [...new Set(xs)];
14837
15371
  var pages = (n) => `${String(n)} page${n === 1 ? "" : "s"}`;
14838
15372
  async function offerExternalHomes(ctx, writes, hooks) {
@@ -14915,17 +15449,17 @@ function externalWriteWarning(line) {
14915
15449
  return home.reason === "no-config" ? `${head}; ${prefer}, and --no-config leaves ${them} no manifest.` : `${head} that ${line.pages === 1 ? "is" : "are"} in none of the ${String(home.collections)} collections; ${prefer}, and only a collection has a manifest.`;
14916
15450
  }
14917
15451
  function manifestKeyLine(text2, entry, key, join11) {
14918
- const norm = (e) => join11 === PATH_JOIN ? e.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/") : e;
14919
- const lc = new LineCounter5();
14920
- const doc = parseDocument8(text2, { lineCounter: lc, uniqueKeys: false });
15452
+ const norm = (e) => join11 === PATH_JOIN2 ? e.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/") : e;
15453
+ const lc = new LineCounter6();
15454
+ const doc = parseDocument9(text2, { lineCounter: lc, uniqueKeys: false });
14921
15455
  const root = doc.contents;
14922
- if (!isMap7(root)) return void 0;
15456
+ if (!isMap9(root)) return void 0;
14923
15457
  for (const pair of root.items) {
14924
- const spelled = isScalar5(pair.key) ? String(pair.key.value) : String(pair.key);
14925
- if (norm(spelled) !== norm(entry) || !isMap7(pair.value)) continue;
15458
+ const spelled = isScalar8(pair.key) ? String(pair.key.value) : String(pair.key);
15459
+ if (norm(spelled) !== norm(entry) || !isMap9(pair.value)) continue;
14926
15460
  for (const kv of pair.value.items) {
14927
- const name2 = isScalar5(kv.key) ? String(kv.key.value) : String(kv.key);
14928
- const range = isNode4(kv.key) ? kv.key.range : void 0;
15461
+ const name2 = isScalar8(kv.key) ? String(kv.key.value) : String(kv.key);
15462
+ const range = isNode6(kv.key) ? kv.key.range : void 0;
14929
15463
  if (name2 === key && range) return lc.linePos(range[0]).line;
14930
15464
  }
14931
15465
  }
@@ -15028,7 +15562,9 @@ async function runQuery(opts) {
15028
15562
  const externalMetadata = await loadExternalMetadata(collections, {
15029
15563
  configDir: configDir ?? cwd,
15030
15564
  base,
15031
- offline: opts.offline ?? config?.offline ?? false
15565
+ offline: opts.offline ?? config?.offline ?? false,
15566
+ // A `{page}` manifest (0058) is read for exactly the pages this run reads.
15567
+ pages: files.map((file) => resolve20(base, file))
15032
15568
  });
15033
15569
  const configuredKey = lazyKey(configFile, opts.env);
15034
15570
  let ensuredKey;
@@ -15110,10 +15646,10 @@ async function runQuery(opts) {
15110
15646
  readOne(STDIN_LABEL, opts.stdinContent ?? "", ext);
15111
15647
  }
15112
15648
  for (const file of files) {
15113
- const content = await readFile12(resolve19(base, file), "utf8");
15649
+ const content = await readFile12(resolve20(base, file), "utf8");
15114
15650
  readOne(file, content, extname7(file));
15115
15651
  }
15116
- const db = opts.db === void 0 ? void 0 : { resolved: resolve19(cwd, opts.db), display: opts.db };
15652
+ const db = opts.db === void 0 ? void 0 : { resolved: resolve20(cwd, opts.db), display: opts.db };
15117
15653
  const run2 = await runSql(sql, entries, {
15118
15654
  target: db,
15119
15655
  params: boundParams,
@@ -15210,7 +15746,7 @@ async function runSql(sql, entries, ctx) {
15210
15746
  );
15211
15747
  const { DatabaseSync } = await loadSqlite();
15212
15748
  if (target) {
15213
- await mkdir3(dirname11(target.resolved), { recursive: true });
15749
+ await mkdir4(dirname11(target.resolved), { recursive: true });
15214
15750
  await prepareDbTarget(target.resolved, target.display);
15215
15751
  }
15216
15752
  let db;
@@ -15254,7 +15790,7 @@ async function runSql(sql, entries, ctx) {
15254
15790
  const place = manifests.length === 0 ? void 0 : readerPlace(e.label, e.own.data, manifests, ctx.declaredCollections, manifestRoot, ctx.base);
15255
15791
  return {
15256
15792
  label: e.label,
15257
- absPath: resolve19(ctx.base, e.label),
15793
+ absPath: resolve20(ctx.base, e.label),
15258
15794
  content: e.content,
15259
15795
  extracted: e.own,
15260
15796
  ...place !== void 0 ? { provenanceManifest: place } : {}
@@ -15719,20 +16255,20 @@ function displayPath(ctx, abs) {
15719
16255
  }
15720
16256
  function assertSchemaWriteWithin(ctx, abs) {
15721
16257
  const within = posixRelative(ctx.trustRoot.dir, abs);
15722
- if (within !== "" && !within.startsWith("..") && !isAbsolute10(within)) return;
16258
+ if (within !== "" && !within.startsWith("..") && !isAbsolute11(within)) return;
15723
16259
  throw new DocmetaError(
15724
16260
  `The resolved schema "${abs}" lives outside ${ctx.trustRoot.dir}; DDL edits schemas inside the repository. Evolve it where it lives, or vendor a copy in.`
15725
16261
  );
15726
16262
  }
15727
16263
  function foldPath(p) {
15728
- const abs = resolve19(p);
16264
+ const abs = resolve20(p);
15729
16265
  return process.platform === "win32" ? abs.toLowerCase() : abs;
15730
16266
  }
15731
16267
  function filePinsByPath(ctx) {
15732
16268
  const byPath = /* @__PURE__ */ new Map();
15733
16269
  for (const [ref, pin] of collectSchemaPins(ctx.config)) {
15734
16270
  if (classifyRef(ref).kind !== "file") continue;
15735
- byPath.set(foldPath(resolve19(ctx.cwd, ref)), pin);
16271
+ byPath.set(foldPath(resolve20(ctx.cwd, ref)), pin);
15736
16272
  }
15737
16273
  return byPath;
15738
16274
  }
@@ -15760,7 +16296,7 @@ async function loadSetMembers(refs, ctx) {
15760
16296
  });
15761
16297
  continue;
15762
16298
  }
15763
- const abs = resolve19(ctx.cwd, ref);
16299
+ const abs = resolve20(ctx.cwd, ref);
15764
16300
  const canon = foldPath(abs);
15765
16301
  if (seenFiles.has(canon)) continue;
15766
16302
  seenFiles.add(canon);
@@ -15965,7 +16501,7 @@ async function planSchemaMutation(ops, entries, ctx) {
15965
16501
  const [, name2, ver] = target.builtinId.split(":");
15966
16502
  const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "-");
15967
16503
  const forkName = `${safe(name2 ?? "schema")}-${safe(ver ?? "0")}.local.json`;
15968
- schemaAbs = resolve19(ctx.configDir ?? ctx.cwd, "schemas", forkName);
16504
+ schemaAbs = resolve20(ctx.configDir ?? ctx.cwd, "schemas", forkName);
15969
16505
  assertSchemaWriteWithin(ctx, schemaAbs);
15970
16506
  if (existsSync5(schemaAbs)) {
15971
16507
  throw new DocmetaError(
@@ -16122,17 +16658,17 @@ async function planConfigEdit(ctx, edit, runRefs) {
16122
16658
  return { changes: [], repointed: false };
16123
16659
  }
16124
16660
  const text2 = await readFile12(configPath, "utf8");
16125
- const doc = parseDocument9(stripBom2(text2));
16661
+ const doc = parseDocument10(stripBom2(text2));
16126
16662
  const display = displayPath(ctx, configPath);
16127
16663
  const rawRefOf = (item) => {
16128
- if (isScalar6(item) && typeof item.value === "string") return item.value;
16129
- if (isMap8(item)) {
16664
+ if (isScalar9(item) && typeof item.value === "string") return item.value;
16665
+ if (isMap10(item)) {
16130
16666
  const ref = item.get("ref");
16131
16667
  if (typeof ref === "string") return ref;
16132
16668
  }
16133
16669
  return void 0;
16134
16670
  };
16135
- const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute10(raw) ? resolve19(configDir, raw) : raw;
16671
+ const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute11(raw) ? resolve20(configDir, raw) : raw;
16136
16672
  const wantSet = runRefs.map(rebaseRaw).sort().join("\n");
16137
16673
  const seqResolvesToRunSet = (node) => {
16138
16674
  if (!isSeq5(node)) return false;
@@ -16148,10 +16684,10 @@ async function planConfigEdit(ctx, edit, runRefs) {
16148
16684
  for (const item of node.items) {
16149
16685
  const raw = rawRefOf(item);
16150
16686
  if (raw === void 0 || !wants(raw)) continue;
16151
- if (isScalar6(item)) {
16687
+ if (isScalar9(item)) {
16152
16688
  item.value = spec.newRef;
16153
16689
  n += 1;
16154
- } else if (isMap8(item)) {
16690
+ } else if (isMap10(item)) {
16155
16691
  item.set("ref", spec.newRef);
16156
16692
  n += 1;
16157
16693
  }
@@ -16159,13 +16695,13 @@ async function planConfigEdit(ctx, edit, runRefs) {
16159
16695
  return n;
16160
16696
  };
16161
16697
  const root = ctx.configSection === void 0 ? doc : doc.get(ctx.configSection, true);
16162
- const keyOf2 = (key) => root === doc ? doc.get(key, true) : isMap8(root) ? root.get(key, true) : void 0;
16698
+ const keyOf2 = (key) => root === doc ? doc.get(key, true) : isMap10(root) ? root.get(key, true) : void 0;
16163
16699
  const topSeq = keyOf2("schemas");
16164
16700
  let repointCount = repoint(topSeq);
16165
16701
  const overrides = keyOf2("overrides");
16166
16702
  if (isSeq5(overrides)) {
16167
16703
  for (const entry of overrides.items) {
16168
- if (isMap8(entry)) repointCount += repoint(entry.get("schemas", true));
16704
+ if (isMap10(entry)) repointCount += repoint(entry.get("schemas", true));
16169
16705
  }
16170
16706
  }
16171
16707
  const repointed = repointCount > 0;
@@ -16174,10 +16710,10 @@ async function planConfigEdit(ctx, edit, runRefs) {
16174
16710
  if (edit.integrity && isSeq5(topSeq)) {
16175
16711
  const wantPath = foldPath(edit.integrity.path);
16176
16712
  for (const item of topSeq.items) {
16177
- if (!isMap8(item)) continue;
16713
+ if (!isMap10(item)) continue;
16178
16714
  const raw = rawRefOf(item);
16179
16715
  if (raw === void 0 || classifyRef(raw).kind !== "file") continue;
16180
- const abs = isAbsolute10(raw) ? raw : resolve19(configDir, raw);
16716
+ const abs = isAbsolute11(raw) ? raw : resolve20(configDir, raw);
16181
16717
  if (foldPath(abs) !== wantPath) continue;
16182
16718
  pinFrom = item.get("integrity");
16183
16719
  item.set("integrity", edit.integrity.value);
@@ -16406,7 +16942,7 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
16406
16942
  `"${file}": no writable format for that extension.`
16407
16943
  );
16408
16944
  }
16409
- if (existsSync5(resolve19(ctx.base, file))) {
16945
+ if (existsSync5(resolve20(ctx.base, file))) {
16410
16946
  throw new DocmetaError(
16411
16947
  `"${file}" already exists; INSERT creates new files only.`
16412
16948
  );
@@ -16425,7 +16961,7 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
16425
16961
  `"${from}" -> "${to}": a rename may not change the extension.`
16426
16962
  );
16427
16963
  }
16428
- if (existsSync5(resolve19(ctx.base, to))) {
16964
+ if (existsSync5(resolve20(ctx.base, to))) {
16429
16965
  throw new DocmetaError(
16430
16966
  `"${to}" already exists; the rename would overwrite it.`
16431
16967
  );
@@ -16454,13 +16990,13 @@ function validateNewPath(p, base) {
16454
16990
  const refuse = (why) => {
16455
16991
  throw new DocmetaError(`"${p}" is not a usable path: ${why}.`);
16456
16992
  };
16457
- if (isAbsolute10(p)) refuse("it is absolute");
16993
+ if (isAbsolute11(p)) refuse("it is absolute");
16458
16994
  if (p.split(/[\\/]/).some((segment) => segment === "..")) {
16459
16995
  refuse("it traverses upward");
16460
16996
  }
16461
- const abs = resolve19(base, p);
16462
- if (abs === resolve19(base)) refuse("it points at the corpus root, not a file");
16463
- if (!abs.startsWith(resolve19(base) + sep9)) refuse("it escapes the corpus");
16997
+ const abs = resolve20(base, p);
16998
+ if (abs === resolve20(base)) refuse("it points at the corpus root, not a file");
16999
+ if (!abs.startsWith(resolve20(base) + sep10)) refuse("it escapes the corpus");
16464
17000
  }
16465
17001
  function refuseJoinFieldChanges(changes, entries, ctx) {
16466
17002
  const index = ctx.externalMetadata;
@@ -16531,6 +17067,12 @@ function postChangeData(changes, entries) {
16531
17067
  }
16532
17068
  return out;
16533
17069
  }
17070
+ function runPages(changes, entries, base) {
17071
+ const labels = new Set(entries.map((e) => e.label));
17072
+ for (const c of changes) if (!("schema" in c) && !("config" in c)) labels.add(c.file);
17073
+ labels.delete(STDIN_LABEL);
17074
+ return [...labels].map((label) => resolve20(base, label));
17075
+ }
16534
17076
  async function planManifestEdits(changes, entries, ctx, renameHints) {
16535
17077
  for (const c of changes) {
16536
17078
  delete c.manifest;
@@ -16579,12 +17121,18 @@ async function planManifestEdits(changes, entries, ctx, renameHints) {
16579
17121
  let write = texts.get(home.absPath);
16580
17122
  if (write === void 0) {
16581
17123
  let text2;
17124
+ let expected;
16582
17125
  try {
16583
17126
  text2 = await readFile12(home.absPath, "utf8");
17127
+ expected = text2;
16584
17128
  } catch (err) {
16585
- throw new DocmetaError(`Manifest ${home.file} could not be read: ${errorMessage(err)}`);
17129
+ if (!home.perPage || !isMissing(err)) {
17130
+ throw new DocmetaError(`Manifest ${home.file} could not be read: ${errorMessage(err)}`);
17131
+ }
17132
+ text2 = "";
17133
+ expected = null;
16586
17134
  }
16587
- write = { path: home.absPath, display: home.file, text: text2, expected: text2 };
17135
+ write = { path: home.absPath, display: home.file, text: text2, expected };
16588
17136
  texts.set(home.absPath, write);
16589
17137
  }
16590
17138
  write.text = change(write.text);
@@ -16607,13 +17155,13 @@ async function planManifestEdits(changes, entries, ctx, renameHints) {
16607
17155
  ...col,
16608
17156
  externalMetadata: col.externalMetadata.filter((m) => classifyRef(m.file).kind !== "url")
16609
17157
  })),
16610
- { configDir: ctx.configDir ?? ctx.cwd, base: ctx.base }
17158
+ { configDir: ctx.configDir ?? ctx.cwd, base: ctx.base, pages: runPages(changes, entries, ctx.base) }
16611
17159
  );
16612
17160
  const index = await leftOut;
16613
17161
  if (index === null) return void 0;
16614
- const abs = resolve19(ctx.base, file);
17162
+ const abs = resolve20(ctx.base, file);
16615
17163
  const named = index.entries.find(
16616
- (e) => e.join === PATH_JOIN && e.abs === abs && excluded.includes(e.collection)
17164
+ (e) => e.join === PATH_JOIN2 && e.abs === abs && excluded.includes(e.collection)
16617
17165
  );
16618
17166
  if (named !== void 0) return named;
16619
17167
  if (!joins || own2 === void 0) return void 0;
@@ -16658,7 +17206,7 @@ async function planManifestEdits(changes, entries, ctx, renameHints) {
16658
17206
  continue;
16659
17207
  }
16660
17208
  if ("renamed" in c) {
16661
- const supplied = ctx.externalMetadata?.byPath.get(resolve19(ctx.base, c.file));
17209
+ const supplied = ctx.externalMetadata?.byPath.get(resolve20(ctx.base, c.file));
16662
17210
  const seen = /* @__PURE__ */ new Set();
16663
17211
  for (const [key, sv] of supplied ?? []) {
16664
17212
  if (seen.has(sv.file) || !ctx.memberships(c.file).includes(sv.collection)) continue;
@@ -16669,7 +17217,12 @@ async function planManifestEdits(changes, entries, ctx, renameHints) {
16669
17217
  `"${c.file}": manifest ${home.file} names it, which is fetched and cannot be written; rename the entry in that repository.`
16670
17218
  );
16671
17219
  }
16672
- if (home.kind !== "manifest" || home.join !== PATH_JOIN || home.entry === void 0) continue;
17220
+ if (home.kind === "manifest" && home.perPage) {
17221
+ throw new DocmetaError(
17222
+ `${c.file} cannot move to ${c.renamed}, because its "${key}" lives in ${home.file}, a {page} manifest named after the page's path. Move the page and its manifest together, then rename the entry's key.`
17223
+ );
17224
+ }
17225
+ if (home.kind !== "manifest" || home.join !== PATH_JOIN2 || home.entry === void 0) continue;
16673
17226
  const moved = await keyHome(loc, c.renamed, own2, key);
16674
17227
  if (moved.kind !== "manifest" || moved.absPath !== home.absPath || moved.entry === void 0) {
16675
17228
  throw new DocmetaError(
@@ -16733,7 +17286,7 @@ async function planManifestEdits(changes, entries, ctx, renameHints) {
16733
17286
  }
16734
17287
  pageRoutes.set(c, ops);
16735
17288
  }
16736
- return [...texts.values()].filter((w) => w.text !== w.expected);
17289
+ return [...texts.values()].filter((w) => w.text !== (w.expected ?? ""));
16737
17290
  }
16738
17291
  async function unhomedWrites(changes, entries, ctx) {
16739
17292
  const written = /* @__PURE__ */ new Map();
@@ -16765,7 +17318,7 @@ async function unhomedWrites(changes, entries, ctx) {
16765
17318
  const prefs = await validator.locationPreferences(data, refs);
16766
17319
  const joins = new Set(
16767
17320
  memberOf(loc.declaredCollections, loc.configDir ?? ctx.cwd, ctx.base, file).flatMap(
16768
- (name2) => (loc.declaredCollections.find((col) => col.name === name2)?.externalMetadata ?? []).map((m) => externalMetadataJoin(m)).filter((j) => j !== PATH_JOIN)
17321
+ (name2) => (loc.declaredCollections.find((col) => col.name === name2)?.externalMetadata ?? []).map((m) => externalMetadataJoin(m)).filter((j) => j !== PATH_JOIN2)
16769
17322
  )
16770
17323
  );
16771
17324
  for (const key of new Set(w.keys)) {
@@ -16816,7 +17369,8 @@ async function offerRelocation(changes, entries, ctx) {
16816
17369
  ctx.externalMetadata = await loadExternalMetadata(loc.collections, {
16817
17370
  configDir: loc.configDir ?? ctx.cwd,
16818
17371
  base: ctx.base,
16819
- offline: ctx.location.offline
17372
+ offline: ctx.location.offline,
17373
+ pages: runPages(changes, entries, ctx.base)
16820
17374
  });
16821
17375
  ctx.memberships = (label) => memberOf(loc.collections, loc.configDir ?? ctx.cwd, ctx.base, label);
16822
17376
  unhomed = await unhomedWrites(changes, entries, ctx);
@@ -16960,14 +17514,14 @@ async function applyChanges(changes, entries, ctx, schemaPlan, manifestWrites =
16960
17514
  const pendingWrites = [];
16961
17515
  const pendingRenames = [];
16962
17516
  for (const [label, ops] of grouped) {
16963
- const path = resolve19(ctx.base, label);
17517
+ const path = resolve20(ctx.base, label);
16964
17518
  if (ops.renamedTo !== void 0) {
16965
17519
  if (!existsSync5(path)) {
16966
17520
  throw new DocmetaError(
16967
17521
  `"${label}" is gone from disk since it was read; re-run the query.`
16968
17522
  );
16969
17523
  }
16970
- const toDisk = resolve19(ctx.base, ops.renamedTo);
17524
+ const toDisk = resolve20(ctx.base, ops.renamedTo);
16971
17525
  if (existsSync5(toDisk)) {
16972
17526
  throw new DocmetaError(
16973
17527
  `"${ops.renamedTo}" appeared on disk since the plan; re-run the query.`
@@ -17092,7 +17646,7 @@ async function applyChanges(changes, entries, ctx, schemaPlan, manifestWrites =
17092
17646
  try {
17093
17647
  now = await readFile12(m.path, "utf8");
17094
17648
  } catch {
17095
- now = void 0;
17649
+ now = null;
17096
17650
  }
17097
17651
  if (now !== m.expected) {
17098
17652
  throw new DocmetaError(
@@ -17106,7 +17660,10 @@ async function applyChanges(changes, entries, ctx, schemaPlan, manifestWrites =
17106
17660
  for (const p of pendingWrites) {
17107
17661
  if (p.ensureDir) dirs.add(dirname11(p.path));
17108
17662
  }
17109
- for (const d of dirs) await mkdir3(d, { recursive: true });
17663
+ for (const m of manifestWrites) {
17664
+ if (m.expected === null) dirs.add(dirname11(m.path));
17665
+ }
17666
+ for (const d of dirs) await mkdir4(d, { recursive: true });
17110
17667
  const writeSchema = async () => {
17111
17668
  for (const w of schemaWrites) await writeFileAtomic(w.path, w.content);
17112
17669
  };
@@ -17221,7 +17778,7 @@ function skipExpression(sql, from) {
17221
17778
 
17222
17779
  // src/meta/commands/fill.ts
17223
17780
  import { readFile as readFile14 } from "node:fs/promises";
17224
- import { resolve as resolve20, extname as extname8, join as join10 } from "node:path";
17781
+ import { resolve as resolve21, extname as extname8, join as join10 } from "node:path";
17225
17782
  import {
17226
17783
  completeValidatedJSON,
17227
17784
  makeProvider,
@@ -17237,68 +17794,6 @@ import {
17237
17794
 
17238
17795
  // src/meta/core/manifest-writes.ts
17239
17796
  import { readFile as readFile13 } from "node:fs/promises";
17240
-
17241
- // src/shared/manifest-cas.ts
17242
- import { posix as posix4 } from "node:path";
17243
- var COMMIT_ATTEMPTS = 5;
17244
- function conflictRefusal(file) {
17245
- return `${file} changed under the command while it was being written. Nothing was written to it. Re-run the command.`;
17246
- }
17247
- function heldManifest(path, file, before) {
17248
- return { path, file, before, text: before, ops: [], bases: /* @__PURE__ */ new Map() };
17249
- }
17250
- function encodeValue(value) {
17251
- return value === void 0 ? "absent" : JSON.stringify(value);
17252
- }
17253
- function baseKey(at2) {
17254
- const entry = at2.join === "path" ? posix4.normalize(at2.entry) : at2.entry;
17255
- return `${entry}\0${at2.key}`;
17256
- }
17257
- function rebaseable(held, op, codec) {
17258
- const at2 = baseKey(op);
17259
- if (held.bases.has(at2)) return;
17260
- held.bases.set(at2, {
17261
- entry: op.entry,
17262
- key: op.key,
17263
- join: op.join,
17264
- value: encodeValue(codec.read(held.before, op))
17265
- });
17266
- }
17267
- function rebase(held, current, codec) {
17268
- for (const base of held.bases.values()) {
17269
- if (encodeValue(codec.read(current, base)) !== base.value) return void 0;
17270
- }
17271
- let text2 = current;
17272
- for (const op of held.ops) text2 = codec.apply(text2, op);
17273
- return text2;
17274
- }
17275
- function rebaseline(held) {
17276
- held.before = held.text;
17277
- held.ops = [];
17278
- held.bases.clear();
17279
- }
17280
- async function settle(held, io) {
17281
- for (let attempt = 1; attempt <= COMMIT_ATTEMPTS; attempt++) {
17282
- let current;
17283
- try {
17284
- current = await io.read(held.path);
17285
- } catch (error) {
17286
- const reason = error instanceof Error ? error.message : String(error);
17287
- throw io.toError(`Manifest ${held.file} could not be read: ${reason}`);
17288
- }
17289
- if (current === held.before) {
17290
- await io.write(held.path, held.text);
17291
- return;
17292
- }
17293
- const replayed = rebase(held, current, io.codec);
17294
- if (replayed === void 0) throw io.toError(conflictRefusal(held.file));
17295
- held.before = current;
17296
- held.text = replayed;
17297
- }
17298
- throw io.toError(conflictRefusal(held.file));
17299
- }
17300
-
17301
- // src/meta/core/manifest-writes.ts
17302
17797
  var manifestCodec = {
17303
17798
  apply(text2, op) {
17304
17799
  const where = { entry: op.entry, key: op.key, join: op.join, file: op.file };
@@ -17311,11 +17806,12 @@ var manifestCodec = {
17311
17806
  function applyManifestOp(text2, op) {
17312
17807
  return manifestCodec.apply(text2, op);
17313
17808
  }
17314
- async function holdManifestFile(path, file) {
17809
+ async function holdManifestFile(path, file, perPage = false) {
17315
17810
  let before;
17316
17811
  try {
17317
17812
  before = await readFile13(path, "utf8");
17318
17813
  } catch (err) {
17814
+ if (perPage && isMissing(err)) return heldManifest(path, file, "", true);
17319
17815
  throw new DocmetaError(`Manifest ${file} could not be read: ${errorMessage(err)}`);
17320
17816
  }
17321
17817
  return heldManifest(path, file, before);
@@ -17328,7 +17824,8 @@ async function settleManifest(held, io = {}) {
17328
17824
  if (held.text === held.before) return false;
17329
17825
  await settle(held, {
17330
17826
  codec: manifestCodec,
17331
- write: io.write ?? writeFileAtomic,
17827
+ // A per-page manifest (proposal 0058) may be the first file in its directory.
17828
+ write: io.write ?? ((at2, text2) => writeFileAtomic(at2, text2, { createParents: true })),
17332
17829
  read: io.read ?? ((at2) => readFile13(at2, "utf8")),
17333
17830
  toError: (message) => new DocmetaError(message)
17334
17831
  });
@@ -17612,7 +18109,9 @@ async function runFill(opts) {
17612
18109
  const loadManifests = () => loadExternalMetadata(mergedCollections(), {
17613
18110
  configDir: configDir ?? cwd,
17614
18111
  base,
17615
- offline: opts.offline ?? config?.offline ?? false
18112
+ offline: opts.offline ?? config?.offline ?? false,
18113
+ // A `{page}` manifest (0058) is read for exactly the pages this run fills.
18114
+ pages: files.map((file) => resolve21(base, file))
17616
18115
  });
17617
18116
  const mergeMembersFor = (label) => memberOf(runConfig.declaredCollections, configDir ?? cwd, base, label);
17618
18117
  let externalMetadata = await loadManifests();
@@ -17649,7 +18148,7 @@ async function runFill(opts) {
17649
18148
  };
17650
18149
  const restorePage = async (label, before) => {
17651
18150
  try {
17652
- await writeFileAtomic(resolve20(base, label), before);
18151
+ await writeFileAtomic(resolve21(base, label), before);
17653
18152
  return `${label} was restored.`;
17654
18153
  } catch (err) {
17655
18154
  return `${label} could not be restored: ${errorMessage(err)}. Restore it from version control.`;
@@ -17658,7 +18157,7 @@ async function runFill(opts) {
17658
18157
  const holdManifest = (home) => {
17659
18158
  let held = holding.get(home.absPath);
17660
18159
  if (held === void 0) {
17661
- held = holdManifestFile(home.absPath, home.file).then((m) => {
18160
+ held = holdManifestFile(home.absPath, home.file, home.perPage).then((m) => {
17662
18161
  const h = { held: m, saving: Promise.resolve() };
17663
18162
  heldManifests.set(home.absPath, h);
17664
18163
  return h;
@@ -18194,7 +18693,7 @@ async function runFill(opts) {
18194
18693
  const changed = next !== content || manifestChanged;
18195
18694
  const pageWritten = next !== content && !dryRun && label !== "<stdin>";
18196
18695
  if (pageWritten) {
18197
- await writeFileAtomic(resolve20(base, label), next);
18696
+ await writeFileAtomic(resolve21(base, label), next);
18198
18697
  }
18199
18698
  if (manifestChanged) {
18200
18699
  const perManifest = /* @__PURE__ */ new Map();
@@ -18226,7 +18725,7 @@ async function runFill(opts) {
18226
18725
  const prepareAll = async () => ({
18227
18726
  stdin: usingStdin ? await prepareOne("<stdin>", opts.stdinContent ?? "", forcedExtractor?.extensions[0] ?? "") : void 0,
18228
18727
  files: await mapConcurrent(files, concurrency, async (file) => {
18229
- const content = await readFile14(resolve20(base, file), "utf8");
18728
+ const content = await readFile14(resolve21(base, file), "utf8");
18230
18729
  return prepareOne(file, content, extname8(file));
18231
18730
  })
18232
18731
  });
@@ -18656,7 +19155,7 @@ async function mapConcurrent(items2, limit, fn) {
18656
19155
 
18657
19156
  // src/meta/commands/derive.ts
18658
19157
  import { readFile as readFile15 } from "node:fs/promises";
18659
- import { extname as extname9, relative as relative14, resolve as resolve21, sep as sep10 } from "node:path";
19158
+ import { extname as extname9, relative as relative15, resolve as resolve22, sep as sep11 } from "node:path";
18660
19159
  var GENERATED_BY_ENV = "MANNI_GENERATED_BY";
18661
19160
  var SOURCE_HINT = "narrow --sources or --fields";
18662
19161
  async function runDerive(opts) {
@@ -18767,7 +19266,7 @@ async function runDerive(opts) {
18767
19266
  const files = fromCollections ? retainMembers(walked, membersFor) : walked;
18768
19267
  const rangesByPage = /* @__PURE__ */ new Map();
18769
19268
  for (const t of targets) {
18770
- const label = relative14(base, resolve21(base, t.page)).split(sep10).join("/");
19269
+ const label = relative15(base, resolve22(base, t.page)).split(sep11).join("/");
18771
19270
  if (!files.includes(label)) {
18772
19271
  throw new DocmetaError(
18773
19272
  `${t.target} does not name one file that derive reads; a range names lines of one file.`
@@ -18792,7 +19291,7 @@ async function runDerive(opts) {
18792
19291
  const loaded2 = /* @__PURE__ */ new Map();
18793
19292
  const errors2 = /* @__PURE__ */ new Map();
18794
19293
  for (const label of files) {
18795
- const absPath = resolve21(base, label);
19294
+ const absPath = resolve22(base, label);
18796
19295
  const extractor = forcedExtractor ?? extractorForExtension(extname9(label));
18797
19296
  if (!extractor) {
18798
19297
  errors2.set(label, {
@@ -18818,12 +19317,13 @@ async function runDerive(opts) {
18818
19317
  }
18819
19318
  }
18820
19319
  const provenanceRoot = configDir ?? cwd;
19320
+ const pages2 = files.map((label) => resolve22(base, label));
18821
19321
  const manifests = wantsProvenance ? provenanceManifests(declaredCollections, provenanceRoot, base) : [];
18822
19322
  const provenanceCollections = declaredCollections.map((c) => ({
18823
19323
  ...c,
18824
19324
  externalMetadata: c.externalMetadata.filter((m) => m.keys.includes(PROVENANCE_FIELD))
18825
19325
  })).filter((c) => c.externalMetadata.length > 0);
18826
- const provenanceIndex = manifests.length === 0 ? null : await loadExternalMetadata(provenanceCollections, { configDir: provenanceRoot, base });
19326
+ const provenanceIndex = manifests.length === 0 ? null : await loadExternalMetadata(provenanceCollections, { configDir: provenanceRoot, base, pages: pages2 });
18827
19327
  const places2 = /* @__PURE__ */ new Map();
18828
19328
  for (const doc of loaded2.values()) {
18829
19329
  if (!wantsProvenance) break;
@@ -18850,7 +19350,7 @@ async function runDerive(opts) {
18850
19350
  (m) => classifyRef(m.file).kind !== "url" && m.keys.some((k) => k !== PROVENANCE_FIELD && fields.includes(k))
18851
19351
  )
18852
19352
  })).filter((c) => c.externalMetadata.length > 0);
18853
- const ownedIndex = ownedCollections.length === 0 ? null : await loadExternalMetadata(ownedCollections, { configDir: provenanceRoot, base });
19353
+ const ownedIndex = ownedCollections.length === 0 ? null : await loadExternalMetadata(ownedCollections, { configDir: provenanceRoot, base, pages: pages2 });
18854
19354
  const derived2 = await deriveMetadata([...loaded2.values()], {
18855
19355
  cwd,
18856
19356
  base,
@@ -18945,8 +19445,8 @@ async function runDerive(opts) {
18945
19445
  error: `${at2} is encrypted, and no encryption key is available to compare it. Set ${ENCRYPTION_KEY_ENV}, or run \`manni key set\`.`
18946
19446
  };
18947
19447
  }
18948
- const unreadable = view.unreadable.find((p) => isAtOrUnder(p, at2));
18949
- if (unreadable !== void 0) return { error: unreadableMessage(unreadable) };
19448
+ const unreadable2 = view.unreadable.find((p) => isAtOrUnder(p, at2));
19449
+ if (unreadable2 !== void 0) return { error: unreadableMessage(unreadable2) };
18950
19450
  }
18951
19451
  const stamped = { ...view.data };
18952
19452
  for (const field of fields) {
@@ -19022,7 +19522,7 @@ async function runDerive(opts) {
19022
19522
  const holdManifest = async (manifest) => {
19023
19523
  const already = heldManifests.get(manifest.absPath);
19024
19524
  if (already !== void 0) return already;
19025
- const held = await holdManifestFile(manifest.absPath, manifest.file);
19525
+ const held = await holdManifestFile(manifest.absPath, manifest.file, manifest.perPage);
19026
19526
  heldManifests.set(manifest.absPath, held);
19027
19527
  return held;
19028
19528
  };
@@ -19953,6 +20453,9 @@ export {
19953
20453
  palette,
19954
20454
  shouldColor,
19955
20455
  matchesFileGlob,
20456
+ hasPagePlaceholder,
20457
+ pageManifestPath,
20458
+ findPageManifests,
19956
20459
  externalMetadataUrlProblem,
19957
20460
  parseCollections,
19958
20461
  selectCollections,
@@ -19965,6 +20468,7 @@ export {
19965
20468
  findGitRoot,
19966
20469
  valeConfigPath,
19967
20470
  programName,
20471
+ warn,
19968
20472
  notice,
19969
20473
  ToolError,
19970
20474
  errorMessage,
@@ -20005,6 +20509,7 @@ export {
20005
20509
  committedKeyWarning,
20006
20510
  terminalConfirm,
20007
20511
  ensureEncryptionKey,
20512
+ gitIgnored,
20008
20513
  escapePointerSegment,
20009
20514
  hasFrontmatterFence,
20010
20515
  locateFrontmatter,
@@ -20068,6 +20573,10 @@ export {
20068
20573
  isMachineFormat,
20069
20574
  OMITTED_WHEN_CLEAN,
20070
20575
  render,
20576
+ heldManifest,
20577
+ rebaseable,
20578
+ settle,
20579
+ isMissing,
20071
20580
  EXTERNAL_METADATA_FETCH_TIMEOUT_MS,
20072
20581
  EXTERNAL_METADATA_FETCH_MAX_BYTES,
20073
20582
  fetchExternalMetadata,
@@ -20076,7 +20585,7 @@ export {
20076
20585
  EXTERNAL_OWNED_SCHEMA,
20077
20586
  EXTERNAL_DUPLICATE_SCHEMA,
20078
20587
  EXTERNAL_KEYWORD,
20079
- PATH_JOIN,
20588
+ PATH_JOIN2 as PATH_JOIN,
20080
20589
  externalMetadataJoin,
20081
20590
  loadExternalMetadata,
20082
20591
  mergeExternalMetadata,
@@ -20134,9 +20643,6 @@ export {
20134
20643
  runVendorSchema,
20135
20644
  runInferSchema,
20136
20645
  runQuery,
20137
- heldManifest,
20138
- rebaseable,
20139
- settle,
20140
20646
  runFill,
20141
20647
  runDerive,
20142
20648
  deriveFailed,
@@ -20155,4 +20661,4 @@ export {
20155
20661
  renderQuery,
20156
20662
  renderQueryCsv
20157
20663
  };
20158
- //# sourceMappingURL=chunk-KJLPOY2I.js.map
20664
+ //# sourceMappingURL=chunk-7ODP2AB3.js.map