@hawkeyexl/manni 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.0.1",
11
+ version: "2.1.0",
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: {
@@ -124,6 +124,85 @@ var package_default = {
124
124
  }
125
125
  };
126
126
 
127
+ // src/shared/run.ts
128
+ import { realpathSync } from "node:fs";
129
+ import { fileURLToPath } from "node:url";
130
+ import { CommanderError } from "commander";
131
+
132
+ // src/shared/errors.ts
133
+ var ToolError = class extends Error {
134
+ /** clig.dev: 2 is operational/usage; 1 is reserved for findings. */
135
+ exitCode = 2;
136
+ constructor(message) {
137
+ super(message);
138
+ this.name = "ToolError";
139
+ }
140
+ };
141
+ function errorMessage(err) {
142
+ if (err instanceof Error) return err.message;
143
+ if (typeof err === "string") return err;
144
+ if (typeof err === "number" || typeof err === "bigint") return String(err);
145
+ if (typeof err === "object" && err !== null && "message" in err && typeof err.message === "string") {
146
+ return err.message;
147
+ }
148
+ try {
149
+ const json = JSON.stringify(err);
150
+ if (json !== void 0) return json;
151
+ } catch {
152
+ }
153
+ return String(err);
154
+ }
155
+
156
+ // src/shared/program-name.ts
157
+ var name = "manni";
158
+ function programName() {
159
+ return name;
160
+ }
161
+ function setProgramName(value) {
162
+ name = value;
163
+ }
164
+
165
+ // src/shared/run.ts
166
+ function fail(err) {
167
+ const msg = err instanceof ToolError ? err.message : `Unexpected error: ${errorMessage(err)}`;
168
+ process.stderr.write(`${programName()}: ${msg}
169
+ `);
170
+ process.exit(2);
171
+ }
172
+ var STDIN_LINES_MARKER = "\0";
173
+ var STDIN_WITH_LINES = /^-(:[1-9][0-9]*(?:-[1-9][0-9]*)?)$/;
174
+ function normalizeStdinArgv(argv) {
175
+ return argv.map((arg) => {
176
+ const lines = STDIN_WITH_LINES.exec(arg)?.[1];
177
+ return lines === void 0 ? arg : `${STDIN_LINES_MARKER}${lines}`;
178
+ });
179
+ }
180
+ async function runProgram(program, argv = process.argv) {
181
+ setProgramName(program.name());
182
+ try {
183
+ await program.parseAsync(normalizeStdinArgv(argv));
184
+ } catch (err) {
185
+ if (err instanceof CommanderError) {
186
+ process.exitCode = err.exitCode === 0 ? 0 : 2;
187
+ return;
188
+ }
189
+ throw err;
190
+ }
191
+ }
192
+ function isMainModule(moduleUrl) {
193
+ const entry = process.argv[1];
194
+ if (!entry) return false;
195
+ try {
196
+ return realpathSync(entry) === fileURLToPath(moduleUrl);
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+ function runIfMain(moduleUrl, build) {
202
+ if (!isMainModule(moduleUrl)) return;
203
+ runProgram(build()).catch(fail);
204
+ }
205
+
127
206
  // src/shared/collections.ts
128
207
  import picomatch2 from "picomatch";
129
208
 
@@ -392,13 +471,53 @@ function isMember(collection, relPath) {
392
471
  return !collection.exclude.some((glob) => matchesFileGlob(relPath, glob));
393
472
  }
394
473
  function normalizeEntry(entry) {
395
- const posix2 = entry.replace(/\\/g, "/").replace(/^\.\//, "");
396
- return posix2.endsWith("/") ? posix2.slice(0, -1) : posix2;
474
+ const posix4 = entry.replace(/\\/g, "/").replace(/^\.\//, "");
475
+ return posix4.endsWith("/") ? posix4.slice(0, -1) : posix4;
397
476
  }
398
477
  function isGlob(entry) {
399
478
  return picomatch2.scan(entry).isGlob;
400
479
  }
401
480
 
481
+ // src/meta/core/integrity.ts
482
+ import { createHash } from "node:crypto";
483
+ var INTEGRITY_ALGORITHM = "sha256";
484
+ var INTEGRITY_SHAPE = `${INTEGRITY_ALGORITHM}-<64 hex characters>`;
485
+ var INTEGRITY_RE = new RegExp(`^${INTEGRITY_ALGORITHM}-([0-9a-f]{64})$`);
486
+ function integrityOf(bytes) {
487
+ return `${INTEGRITY_ALGORITHM}-${createHash(INTEGRITY_ALGORITHM).update(bytes).digest("hex")}`;
488
+ }
489
+ function isIntegrity(value) {
490
+ return INTEGRITY_RE.test(value);
491
+ }
492
+ function toLf(bytes) {
493
+ const out = new Uint8Array(bytes.length);
494
+ let n = 0;
495
+ for (let i = 0; i < bytes.length; i++) {
496
+ const byte = bytes[i];
497
+ if (byte === void 0) continue;
498
+ if (byte === 13 && bytes[i + 1] === 10) continue;
499
+ out[n++] = byte;
500
+ }
501
+ return out.subarray(0, n);
502
+ }
503
+ function toCrlf(bytes) {
504
+ const lf = toLf(bytes);
505
+ const out = new Uint8Array(lf.length * 2);
506
+ let n = 0;
507
+ for (let i = 0; i < lf.length; i++) {
508
+ const byte = lf[i];
509
+ if (byte === void 0) continue;
510
+ if (byte === 10) out[n++] = 13;
511
+ out[n++] = byte;
512
+ }
513
+ return out.subarray(0, n);
514
+ }
515
+ function diagnoseIntegrity(bytes, expected) {
516
+ if (integrityOf(toLf(bytes)) === expected) return "line-endings";
517
+ if (integrityOf(toCrlf(bytes)) === expected) return "line-endings";
518
+ return "content";
519
+ }
520
+
402
521
  // src/meta/extractors/frontmatter.ts
403
522
  import { parseDocument, LineCounter, isMap, isSeq, isScalar } from "yaml";
404
523
  import { parse as parseToml } from "smol-toml";
@@ -453,22 +572,22 @@ function locateFrontmatter(content) {
453
572
  while (cursor <= body.length) {
454
573
  const nl = body.indexOf("\n", cursor);
455
574
  const hasNl = nl !== -1;
456
- const lineEnd = hasNl ? nl : body.length;
457
- const hadCr = lineEnd > cursor && body.charCodeAt(lineEnd - 1) === 13;
458
- const line = body.slice(cursor, hadCr ? lineEnd - 1 : lineEnd);
575
+ const lineEnd2 = hasNl ? nl : body.length;
576
+ const hadCr = lineEnd2 > cursor && body.charCodeAt(lineEnd2 - 1) === 13;
577
+ const line = body.slice(cursor, hadCr ? lineEnd2 - 1 : lineEnd2);
459
578
  if (fence.isClose(line)) {
460
579
  return {
461
580
  flavor: fence.flavor,
462
581
  openStart: bom,
463
582
  innerStart: innerStart + bom,
464
583
  innerEnd: cursor + bom,
465
- closeEnd: (hasNl ? lineEnd + 1 : lineEnd) + bom,
584
+ closeEnd: (hasNl ? lineEnd2 + 1 : lineEnd2) + bom,
466
585
  eol,
467
586
  firstContentLine: 2
468
587
  };
469
588
  }
470
589
  if (!hasNl) break;
471
- cursor = lineEnd + 1;
590
+ cursor = lineEnd2 + 1;
472
591
  }
473
592
  return null;
474
593
  }
@@ -607,30 +726,6 @@ function extractFrontmatter(content, format) {
607
726
  }
608
727
  }
609
728
 
610
- // src/shared/errors.ts
611
- var ToolError = class extends Error {
612
- /** clig.dev: 2 is operational/usage; 1 is reserved for findings. */
613
- exitCode = 2;
614
- constructor(message) {
615
- super(message);
616
- this.name = "ToolError";
617
- }
618
- };
619
- function errorMessage(err) {
620
- if (err instanceof Error) return err.message;
621
- if (typeof err === "string") return err;
622
- if (typeof err === "number" || typeof err === "bigint") return String(err);
623
- if (typeof err === "object" && err !== null && "message" in err && typeof err.message === "string") {
624
- return err.message;
625
- }
626
- try {
627
- const json = JSON.stringify(err);
628
- if (json !== void 0) return json;
629
- } catch {
630
- }
631
- return String(err);
632
- }
633
-
634
729
  // src/meta/types.ts
635
730
  function isErrorSeverity(e) {
636
731
  return e.severity === void 0 || e.severity === "error";
@@ -1204,8 +1299,8 @@ function ditaShape(content, root, filePath) {
1204
1299
  const lower = filePath?.toLowerCase() ?? "";
1205
1300
  const byExtension2 = (lower.endsWith(".dita") || lower.endsWith(".ditamap")) && DITA_ROOTS.has(name2);
1206
1301
  if (!byDoctype && !byClass && !byArch && !byExtension2) return void 0;
1207
- const isMap6 = /^\s*[-+]\s+map\//.test(cls) || MAP_ROOTS.has(name2) || lower.endsWith(".ditamap");
1208
- return isMap6 ? { kind: "map", container: "topicmeta", preamble: /* @__PURE__ */ new Set(["title"]) } : {
1302
+ const isMap7 = /^\s*[-+]\s+map\//.test(cls) || MAP_ROOTS.has(name2) || lower.endsWith(".ditamap");
1303
+ return isMap7 ? { kind: "map", container: "topicmeta", preamble: /* @__PURE__ */ new Set(["title"]) } : {
1209
1304
  kind: "topic",
1210
1305
  container: "metadata",
1211
1306
  preamble: /* @__PURE__ */ new Set([
@@ -2633,24 +2728,13 @@ function listFormats() {
2633
2728
  // src/meta/core/write-file.ts
2634
2729
  import { writeFile, rename, rm, stat, chmod } from "node:fs/promises";
2635
2730
  import { dirname, join, basename } from "node:path";
2636
-
2637
- // src/shared/program-name.ts
2638
- var name = "manni";
2639
- function programName() {
2640
- return name;
2641
- }
2642
- function setProgramName(value) {
2643
- name = value;
2644
- }
2645
-
2646
- // src/meta/core/write-file.ts
2647
2731
  var LOCKED = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
2648
2732
  var RENAME_ATTEMPTS = 3;
2649
2733
  function errorCode(err) {
2650
2734
  return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
2651
2735
  }
2652
- var wait = (ms) => new Promise((resolve18) => {
2653
- setTimeout(resolve18, ms);
2736
+ var wait = (ms) => new Promise((resolve19) => {
2737
+ setTimeout(resolve19, ms);
2654
2738
  });
2655
2739
  async function writeFileAtomic(path, contents) {
2656
2740
  const tmp = join(
@@ -2691,7 +2775,7 @@ async function writeFileAtomic(path, contents) {
2691
2775
  }
2692
2776
 
2693
2777
  // src/meta/core/schema-cache.ts
2694
- import { createHash } from "node:crypto";
2778
+ import { createHash as createHash2 } from "node:crypto";
2695
2779
  import { mkdir, readFile, stat as stat2 } from "node:fs/promises";
2696
2780
  import { join as join2 } from "node:path";
2697
2781
  var SCHEMA_CACHE_DIR = ".manni/meta/schema-cache";
@@ -2721,7 +2805,7 @@ var SchemaCache = class {
2721
2805
  * directory.
2722
2806
  */
2723
2807
  entryPath(url) {
2724
- const key = createHash("sha256").update(url).digest("hex");
2808
+ const key = createHash2("sha256").update(url).digest("hex");
2725
2809
  return join2(this.dir, `${key}.json`);
2726
2810
  }
2727
2811
  /**
@@ -2798,46 +2882,6 @@ var SchemaCache = class {
2798
2882
  }
2799
2883
  };
2800
2884
 
2801
- // src/meta/core/integrity.ts
2802
- import { createHash as createHash2 } from "node:crypto";
2803
- var INTEGRITY_ALGORITHM = "sha256";
2804
- var INTEGRITY_SHAPE = `${INTEGRITY_ALGORITHM}-<64 hex characters>`;
2805
- var INTEGRITY_RE = new RegExp(`^${INTEGRITY_ALGORITHM}-([0-9a-f]{64})$`);
2806
- function integrityOf(bytes) {
2807
- return `${INTEGRITY_ALGORITHM}-${createHash2(INTEGRITY_ALGORITHM).update(bytes).digest("hex")}`;
2808
- }
2809
- function isIntegrity(value) {
2810
- return INTEGRITY_RE.test(value);
2811
- }
2812
- function toLf(bytes) {
2813
- const out = new Uint8Array(bytes.length);
2814
- let n = 0;
2815
- for (let i = 0; i < bytes.length; i++) {
2816
- const byte = bytes[i];
2817
- if (byte === void 0) continue;
2818
- if (byte === 13 && bytes[i + 1] === 10) continue;
2819
- out[n++] = byte;
2820
- }
2821
- return out.subarray(0, n);
2822
- }
2823
- function toCrlf(bytes) {
2824
- const lf = toLf(bytes);
2825
- const out = new Uint8Array(lf.length * 2);
2826
- let n = 0;
2827
- for (let i = 0; i < lf.length; i++) {
2828
- const byte = lf[i];
2829
- if (byte === void 0) continue;
2830
- if (byte === 10) out[n++] = 13;
2831
- out[n++] = byte;
2832
- }
2833
- return out.subarray(0, n);
2834
- }
2835
- function diagnoseIntegrity(bytes, expected) {
2836
- if (integrityOf(toLf(bytes)) === expected) return "line-endings";
2837
- if (integrityOf(toCrlf(bytes)) === expected) return "line-endings";
2838
- return "content";
2839
- }
2840
-
2841
2885
  // src/meta/core/schema-registry.ts
2842
2886
  import { readFile as readFile2 } from "node:fs/promises";
2843
2887
  import { resolve as resolvePath } from "node:path";
@@ -5243,8 +5287,8 @@ var SCHEMA_KEYS = [
5243
5287
  "multipleOf"
5244
5288
  ];
5245
5289
  var RETRY_DELAY_MS = 500;
5246
- var wait2 = (ms) => new Promise((resolve18) => {
5247
- setTimeout(resolve18, ms);
5290
+ var wait2 = (ms) => new Promise((resolve19) => {
5291
+ setTimeout(resolve19, ms);
5248
5292
  });
5249
5293
  var RetryableFetchError = class extends DocmetaError {
5250
5294
  };
@@ -5659,8 +5703,10 @@ var DERIVABLE_FIELDS = [
5659
5703
  "authors",
5660
5704
  "owner",
5661
5705
  "reviewed-by",
5662
- "last-reviewed"
5706
+ "last-reviewed",
5707
+ "provenance"
5663
5708
  ];
5709
+ var PROVENANCE_FIELD = "provenance";
5664
5710
  function isBuiltinField(x) {
5665
5711
  return DERIVABLE_FIELDS.includes(x);
5666
5712
  }
@@ -6145,7 +6191,7 @@ function asElementPaths(value, where, source) {
6145
6191
  }
6146
6192
  var OVERRIDE_KEYS = ["collection", "files", "schemas", "elements"];
6147
6193
  var CHECK_KEYS = ["name", "query"];
6148
- var DERIVE_KEYS = ["fields", "sources", "codeowners", "commands"];
6194
+ var DERIVE_KEYS = ["fields", "sources", "codeowners", "commands", "machines"];
6149
6195
  var DERIVE_COMMAND_KEYS = ["run", "timeout"];
6150
6196
  var BUILTIN_CLAIMED_BY = {
6151
6197
  created: "git",
@@ -6153,7 +6199,8 @@ var BUILTIN_CLAIMED_BY = {
6153
6199
  authors: "git",
6154
6200
  owner: "codeowners",
6155
6201
  "reviewed-by": "GitHub or GitLab",
6156
- "last-reviewed": "GitHub or GitLab"
6202
+ "last-reviewed": "GitHub or GitLab",
6203
+ provenance: "git"
6157
6204
  };
6158
6205
  var DERIVED_TABLE_RESERVED = /* @__PURE__ */ new Set(["_path", "_sources"]);
6159
6206
  var DERIVABLE_LIST = `Derivable fields: ${DERIVABLE_FIELDS.join(", ")}, or any key with an entry in derive.commands.`;
@@ -6398,6 +6445,7 @@ function assertManagedFieldsUnowned(config, collections, source, section) {
6398
6445
  if (owner !== void 0) refuse(`derive.commands.${key}`, owner);
6399
6446
  }
6400
6447
  for (const [i, field] of (config.derive?.fields ?? []).entries()) {
6448
+ if (field === PROVENANCE_FIELD) continue;
6401
6449
  const owner = manifestOwning(field, collections);
6402
6450
  if (owner !== void 0) refuse(`derive.fields[${i}] "${field}"`, owner);
6403
6451
  }
@@ -6410,9 +6458,9 @@ function parseDerive(raw, source) {
6410
6458
  }
6411
6459
  const e = raw;
6412
6460
  rejectUnknownKeys2(e, DERIVE_KEYS, '"derive"', source);
6413
- if (e.fields === void 0 && e.sources === void 0 && e.codeowners === void 0 && e.commands === void 0) {
6461
+ if (e.fields === void 0 && e.sources === void 0 && e.codeowners === void 0 && e.commands === void 0 && e.machines === void 0) {
6414
6462
  throw new DocmetaError(
6415
- `${source}: "derive" sets nothing. Give it \`fields:\` to manage, \`commands:\` to derive from a command, or \`sources:\` or \`codeowners:\` to shape the reads.`
6463
+ `${source}: "derive" sets nothing. Give it \`fields:\` to manage, \`commands:\` to derive from a command, or \`sources:\`, \`codeowners:\` or \`machines:\` to shape the reads.`
6416
6464
  );
6417
6465
  }
6418
6466
  const commands = e.commands === void 0 ? void 0 : parseDeriveCommands(e.commands, source);
@@ -6463,6 +6511,24 @@ function parseDerive(raw, source) {
6463
6511
  derive.codeowners = e.codeowners;
6464
6512
  }
6465
6513
  if (commands !== void 0) derive.commands = commands;
6514
+ if (e.machines !== void 0) {
6515
+ if (!Array.isArray(e.machines) || e.machines.length === 0 || e.machines.some((m) => typeof m !== "string")) {
6516
+ throw new DocmetaError(
6517
+ `${source}: derive.machines must be a non-empty list of globs, matched against a trailer's name and its email. Default: ["*[bot]"].`
6518
+ );
6519
+ }
6520
+ const machines = [];
6521
+ e.machines.forEach((m, i) => {
6522
+ if (m.trim() === "") {
6523
+ throw new DocmetaError(`${source}: derive.machines[${i}] is blank; a glob must name something.`);
6524
+ }
6525
+ if (machines.includes(m)) {
6526
+ throw new DocmetaError(`${source}: derive.machines lists "${m}" twice.`);
6527
+ }
6528
+ machines.push(m);
6529
+ });
6530
+ derive.machines = machines;
6531
+ }
6466
6532
  return derive;
6467
6533
  }
6468
6534
  function parseDeriveCommands(raw, source) {
@@ -7743,8 +7809,8 @@ var RENAME_ATTEMPTS2 = 3;
7743
7809
  function errorCode2(err) {
7744
7810
  return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
7745
7811
  }
7746
- var wait3 = (ms) => new Promise((resolve18) => {
7747
- setTimeout(resolve18, ms);
7812
+ var wait3 = (ms) => new Promise((resolve19) => {
7813
+ setTimeout(resolve19, ms);
7748
7814
  });
7749
7815
  async function writeTextAtomic(path, text2) {
7750
7816
  const tmp = join5(
@@ -8970,8 +9036,8 @@ var ReviewCache = class {
8970
9036
  }
8971
9037
  if (!isRecord2(raw)) return void 0;
8972
9038
  if (raw.version !== REVIEW_CACHE_VERSION || raw.key !== key) return void 0;
8973
- const change2 = readChange(raw.change);
8974
- return change2 ?? void 0;
9039
+ const change = readChange(raw.change);
9040
+ return change ?? void 0;
8975
9041
  }
8976
9042
  set(key, value) {
8977
9043
  if (!this.enabled) return;
@@ -9000,9 +9066,9 @@ function cachedClient(client, cache, identity) {
9000
9066
  const key = `${identity.host}/${identity.project}/${sha}`;
9001
9067
  const hit = cache.get(key);
9002
9068
  if (hit !== void 0) return hit;
9003
- const change2 = await client.mergedChangeFor(sha);
9004
- if (change2 !== null) cache.set(key, change2);
9005
- return change2;
9069
+ const change = await client.mergedChangeFor(sha);
9070
+ if (change !== null) cache.set(key, change);
9071
+ return change;
9006
9072
  }
9007
9073
  };
9008
9074
  }
@@ -9037,7 +9103,7 @@ function run(bin, args, opts) {
9037
9103
  done = true;
9038
9104
  settle(r);
9039
9105
  };
9040
- const fail = (err) => {
9106
+ const fail2 = (err) => {
9041
9107
  if (done) return;
9042
9108
  done = true;
9043
9109
  reject(err);
@@ -9049,7 +9115,7 @@ function run(bin, args, opts) {
9049
9115
  windowsHide: true
9050
9116
  });
9051
9117
  } catch (err) {
9052
- fail(err instanceof Error ? err : new Error(String(err)));
9118
+ fail2(err instanceof Error ? err : new Error(String(err)));
9053
9119
  return;
9054
9120
  }
9055
9121
  let stdout = "";
@@ -9085,7 +9151,7 @@ function run(bin, args, opts) {
9085
9151
  }, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2);
9086
9152
  child.on("error", (err) => {
9087
9153
  clearTimeout(timer);
9088
- fail(err.code === "ENOENT" ? new BinMissing(err.message) : err);
9154
+ fail2(err.code === "ENOENT" ? new BinMissing(err.message) : err);
9089
9155
  });
9090
9156
  child.on("close", (code) => {
9091
9157
  clearTimeout(timer);
@@ -9496,17 +9562,17 @@ async function deriveFromReviews(inputs, client) {
9496
9562
  records.set(input.label, NOTHING);
9497
9563
  continue;
9498
9564
  }
9499
- const change2 = await changeFor(input.sha);
9500
- records.set(input.label, factsFrom(change2, kind));
9565
+ const change = await changeFor(input.sha);
9566
+ records.set(input.label, factsFrom(change, kind));
9501
9567
  }
9502
9568
  return { status, records };
9503
9569
  }
9504
- function factsFrom(change2, kind) {
9505
- if (change2 === null || change2.approvals.length === 0) return NOTHING;
9506
- const ordered = byTime(change2.approvals);
9570
+ function factsFrom(change, kind) {
9571
+ if (change === null || change.approvals.length === 0) return NOTHING;
9572
+ const ordered = byTime(change.approvals);
9507
9573
  const latest = ordered.at(-1);
9508
9574
  if (latest === void 0) return NOTHING;
9509
- const evidence = kind === "gitlab" ? `gitlab MR !${change2.id}` : `github PR #${change2.id}`;
9575
+ const evidence = kind === "gitlab" ? `gitlab MR !${change.id}` : `github PR #${change.id}`;
9510
9576
  const dateEvidence = kind === "gitlab" ? `${evidence} (merge date; GitLab records no approval time)` : evidence;
9511
9577
  return {
9512
9578
  "reviewed-by": {
@@ -9744,87 +9810,631 @@ function toPosix2(p) {
9744
9810
  // src/meta/core/derive/git.ts
9745
9811
  import { constants as bufferConstants } from "node:buffer";
9746
9812
  import { spawn as spawn3 } from "node:child_process";
9747
- import { dirname as dirname8, extname as extname2, relative as relative10, sep as sep5 } from "node:path";
9748
- var DEFAULT_BULK_THRESHOLD = 32;
9749
- var NULL_SHA = "0".repeat(40);
9750
- var MAX_GIT_OUTPUT_BYTES = Math.min(
9751
- 512 * 1024 * 1024,
9752
- bufferConstants.MAX_STRING_LENGTH
9753
- );
9754
- var HistoryTooLarge = class extends Error {
9755
- };
9756
- var BlobsUnreadable = class extends Error {
9757
- constructor(detail) {
9758
- super(detail);
9759
- this.detail = detail;
9813
+ import { dirname as dirname8, extname as extname2, isAbsolute as isAbsolute5, posix as posix2, relative as relative10, sep as sep5 } from "node:path";
9814
+ import { parse as parseYaml4 } from "yaml";
9815
+
9816
+ // src/shared/pin.ts
9817
+ var LineRangeError = class extends ToolError {
9818
+ constructor(message) {
9819
+ super(message);
9820
+ this.name = "LineRangeError";
9760
9821
  }
9761
- detail;
9762
9822
  };
9763
- function bodyOf(content, fenced) {
9764
- if (fenced !== true) return content;
9765
- const loc = locateFrontmatter(content);
9766
- if (!loc) return content;
9767
- return content.slice(0, loc.openStart) + content.slice(loc.closeEnd);
9823
+ function normalizeText(text2) {
9824
+ const body = text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
9825
+ return body.replace(/\r\n/g, "\n");
9826
+ }
9827
+ function splitLines(text2) {
9828
+ const parts = normalizeText(text2).split("\n");
9829
+ if (parts.length > 0 && parts[parts.length - 1] === "") parts.pop();
9830
+ return parts;
9831
+ }
9832
+ function sliceLines(lines, range, label) {
9833
+ const start = range?.start;
9834
+ if (start === void 0) return lines.join("\n");
9835
+ const end = range?.end ?? start;
9836
+ if (end < start) {
9837
+ throw new LineRangeError(`${label ?? "the source"} range ${String(start)}-${String(end)} ends before it starts.`);
9838
+ }
9839
+ if (start > lines.length || end > lines.length) {
9840
+ const first = start > lines.length ? start : end;
9841
+ throw new LineRangeError(`${label ?? "the source"} has ${lines.length} lines; line ${first} is out of range.`);
9842
+ }
9843
+ return lines.slice(start - 1, end).join("\n");
9844
+ }
9845
+ function hashRange(text2, range, key) {
9846
+ return hashLines(sliceLines(splitLines(text2), range), key);
9847
+ }
9848
+ function hashLines(joined, key) {
9849
+ return key === void 0 ? integrityOf(Buffer.from(joined, "utf8")) : keyedPin(joined, key);
9850
+ }
9851
+ function isKeyedPin(pin) {
9852
+ return pin.startsWith(KEYED_PIN_PREFIX);
9853
+ }
9854
+ function pinOfLines(lines, range) {
9855
+ if (range.start < 1 || range.end > lines.length || range.end < range.start) return void 0;
9856
+ return hashLines(lines.slice(range.start - 1, range.end).join("\n"));
9857
+ }
9858
+ var MOVE_WINDOW_LINES = 2e3;
9859
+ var MOVE_BUDGET_BYTES = 64 * 1024 * 1024;
9860
+ var MAX_RANGE_LINES = 5e3;
9861
+ function findWindows(lines, length, pin, key, opts) {
9862
+ const starts = [];
9863
+ const lastStart = lines.length - length + 1;
9864
+ if (length < 1 || lastStart < 1) return { starts, truncated: false };
9865
+ const joined = (start) => lines.slice(start - 1, start - 1 + length).join("\n");
9866
+ const original = opts?.original;
9867
+ if (original !== void 0) {
9868
+ const first = original[0];
9869
+ for (let start = 1; start <= lastStart; start++) {
9870
+ if (lines[start - 1] !== first) continue;
9871
+ let same = true;
9872
+ for (let i = 1; i < length; i++) {
9873
+ if (lines[start - 1 + i] !== original[i]) {
9874
+ same = false;
9875
+ break;
9876
+ }
9877
+ }
9878
+ if (same && hashLines(joined(start), key) === pin) starts.push(start);
9879
+ }
9880
+ return { starts, truncated: false };
9881
+ }
9882
+ const budget = opts?.budget ?? MOVE_BUDGET_BYTES;
9883
+ const around = opts?.around ?? 1;
9884
+ const bandStart = Math.max(1, around - MOVE_WINDOW_LINES);
9885
+ const bandEnd = Math.min(lastStart, around + MOVE_WINDOW_LINES);
9886
+ const order = [];
9887
+ for (let start = bandStart; start <= bandEnd; start++) order.push(start);
9888
+ for (let start = 1; start < bandStart; start++) order.push(start);
9889
+ for (let start = bandEnd + 1; start <= lastStart; start++) order.push(start);
9890
+ let spent = 0;
9891
+ for (const start of order) {
9892
+ const text2 = joined(start);
9893
+ const bytes = Buffer.byteLength(text2, "utf8");
9894
+ if (spent + bytes > budget) return { starts, truncated: true };
9895
+ spent += bytes;
9896
+ if (hashLines(text2, key) === pin) starts.push(start);
9897
+ }
9898
+ return { starts, truncated: false };
9899
+ }
9900
+ var RANGE_SPEC = /^([1-9][0-9]*)(?:-([1-9][0-9]*))?$/;
9901
+ function parseLines(spec) {
9902
+ if (typeof spec === "number") {
9903
+ return Number.isInteger(spec) && spec >= 1 ? { start: spec, end: spec } : void 0;
9904
+ }
9905
+ const m = RANGE_SPEC.exec(spec);
9906
+ const startText = m?.[1];
9907
+ if (startText === void 0) return void 0;
9908
+ const start = Number(startText);
9909
+ const endText = m?.[2];
9910
+ const end = endText === void 0 ? start : Number(endText);
9911
+ return end < start ? void 0 : { start, end };
9912
+ }
9913
+ function lineSpec(lines) {
9914
+ return lines.start === lines.end ? lines.start : `${String(lines.start)}-${String(lines.end)}`;
9915
+ }
9916
+ function spellLines(lines) {
9917
+ return String(lineSpec(lines));
9918
+ }
9919
+ function toFileLines(lines, bodyLine) {
9920
+ return { start: bodyLine + lines.start - 1, end: bodyLine + lines.end - 1 };
9921
+ }
9922
+ function toBodyLines(lines, bodyLine) {
9923
+ return { start: lines.start - bodyLine + 1, end: lines.end - bodyLine + 1 };
9924
+ }
9925
+ function splitPageArgument(arg) {
9926
+ const text2 = arg.startsWith(STDIN_LINES_MARKER) ? `-${arg.slice(STDIN_LINES_MARKER.length)}` : arg;
9927
+ const m = /:([1-9][0-9]*)(?:-([1-9][0-9]*))?$/.exec(text2);
9928
+ const head = m === null ? text2 : text2.slice(0, m.index);
9929
+ if (m === null || head === "") return { page: arg };
9930
+ const start = Number(m[1]);
9931
+ const end = m[2] === void 0 ? start : Number(m[2]);
9932
+ if (end < start) {
9933
+ throw new LineRangeError(`Invalid range "${arg}": end line ${end} is before start line ${start}.`);
9934
+ }
9935
+ return { page: head, lines: { start, end } };
9768
9936
  }
9769
- async function deriveFromGit(inputs, opts) {
9770
- const records = /* @__PURE__ */ new Map();
9771
- const byRoot = groupByRoot(inputs);
9772
- if (byRoot.size === 0) {
9773
- return {
9774
- status: {
9775
- available: false,
9776
- reason: "no git repository contains the documents"
9777
- },
9778
- records
9779
- };
9937
+
9938
+ // src/meta/core/derive/provenance.ts
9939
+ import picomatch5 from "picomatch";
9940
+ var ZERO_SHA = "0000000000000000000000000000000000000000";
9941
+ var DEFAULT_MACHINES = ["*[bot]"];
9942
+ var PROVENANCE_POINTER = "/provenance";
9943
+ var PLAIN_PIN = /^sha256-[0-9a-f]{64}$/;
9944
+ function provenanceEntries(value) {
9945
+ if (!Array.isArray(value)) return [];
9946
+ const out = [];
9947
+ for (const item of value) {
9948
+ if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
9949
+ const record = item;
9950
+ const machine = record["generated-by"];
9951
+ const lines = record.lines;
9952
+ const integrity = record.integrity;
9953
+ if (typeof machine !== "string" || machine === "") continue;
9954
+ if (typeof lines !== "string" && typeof lines !== "number") continue;
9955
+ if (parseLines(lines) === void 0) continue;
9956
+ if (typeof integrity !== "string" || !PLAIN_PIN.test(integrity)) continue;
9957
+ out.push({ ...record, "generated-by": machine, lines, integrity });
9780
9958
  }
9781
- const threshold = opts.bulkThreshold ?? DEFAULT_BULK_THRESHOLD;
9782
- const maxBytes = opts.maxOutputBytes ?? MAX_GIT_OUTPUT_BYTES;
9783
- const reasons = [];
9784
- for (const [root, bucket] of byRoot) {
9785
- const run2 = (args, stdin) => runGit(args, root, stdin, maxBytes);
9786
- const shallow = await run2(["rev-parse", "--is-shallow-repository"]);
9787
- if (shallow.missing) {
9788
- return {
9789
- status: { available: false, reason: "git is not on PATH" },
9790
- records: /* @__PURE__ */ new Map()
9791
- };
9959
+ return out;
9960
+ }
9961
+ function readProvenancePage(content, opts) {
9962
+ const lines = splitLines(content);
9963
+ const loc = opts?.fenced === false ? null : locateFrontmatter(content);
9964
+ if (loc === null) return { lines, bodyLine: 1, body: lines, stamp: [] };
9965
+ const head = content.slice(0, loc.closeEnd);
9966
+ const newlines = head.split("\n").length - 1;
9967
+ const bodyLine = newlines + (head.endsWith("\n") ? 1 : 2);
9968
+ let stamp = [];
9969
+ try {
9970
+ stamp = provenanceEntries(extractFrontmatter(content, "markdown").data.provenance);
9971
+ } catch {
9972
+ stamp = [];
9973
+ }
9974
+ return { lines, bodyLine, body: lines.slice(bodyLine - 1), stamp };
9975
+ }
9976
+ var HEADER = /^([0-9a-f]{40}(?:[0-9a-f]{24})?) ([1-9][0-9]*) ([1-9][0-9]*)(?: ([1-9][0-9]*))?$/;
9977
+ var ALL_ZERO = /^0+$/;
9978
+ function parseLinePorcelain(text2) {
9979
+ const rows = text2.split("\n");
9980
+ if (rows.length > 0 && rows[rows.length - 1] === "") rows.pop();
9981
+ const out = [];
9982
+ let i = 0;
9983
+ while (i < rows.length) {
9984
+ const headerRow = rows[i] ?? "";
9985
+ const m = HEADER.exec(headerRow);
9986
+ const sha = m?.[1];
9987
+ const orig = m?.[2];
9988
+ const final = m?.[3];
9989
+ if (sha === void 0 || orig === void 0 || final === void 0) {
9990
+ throw new Error(`porcelain: expected a header at row ${i + 1}, got ${JSON.stringify(headerRow)}`);
9991
+ }
9992
+ const finalLine = Number(final);
9993
+ const count = m?.[4];
9994
+ const fields = /* @__PURE__ */ new Map();
9995
+ i++;
9996
+ for (let row = rows[i]; row !== void 0 && !row.startsWith(" "); row = rows[i]) {
9997
+ const space2 = row.indexOf(" ");
9998
+ if (space2 === -1) fields.set(row, true);
9999
+ else fields.set(row.slice(0, space2), row.slice(space2 + 1));
10000
+ i++;
9792
10001
  }
9793
- if (shallow.code !== 0) {
9794
- reasons.push(`git could not read the repository at ${root}`);
9795
- continue;
10002
+ const contentRow = rows[i];
10003
+ if (contentRow === void 0) throw new Error(`porcelain: line ${finalLine} has no content row`);
10004
+ const author = fields.get("author");
10005
+ const filename = fields.get("filename");
10006
+ if (typeof author !== "string" || typeof filename !== "string") {
10007
+ throw new Error(`porcelain: line ${finalLine} has no full header; run blame with --line-porcelain`);
9796
10008
  }
9797
- if (text(shallow).trim() === "true") {
9798
- reasons.push(
9799
- `this checkout is shallow; use actions/checkout with fetch-depth: 0 (${root})`
9800
- );
10009
+ const mail = fields.get("author-mail");
10010
+ const previous = fields.get("previous");
10011
+ const space = typeof previous === "string" ? previous.indexOf(" ") : -1;
10012
+ out.push({
10013
+ sha,
10014
+ origLine: Number(orig),
10015
+ finalLine,
10016
+ ...count !== void 0 ? { groupCount: Number(count) } : {},
10017
+ author,
10018
+ authorMail: typeof mail === "string" ? mail.replace(/^<(.*)>$/, "$1") : "",
10019
+ filename,
10020
+ ...typeof previous === "string" && space !== -1 ? { previous: { sha: previous.slice(0, space), filename: previous.slice(space + 1) } } : {},
10021
+ boundary: fields.get("boundary") === true,
10022
+ uncommitted: ALL_ZERO.test(sha),
10023
+ content: contentRow.slice(1)
10024
+ });
10025
+ i++;
10026
+ }
10027
+ return out;
10028
+ }
10029
+ var IDENTITY_GLOB = { literalBrackets: true, dot: true, bash: true };
10030
+ function machineIdentity(value, machines) {
10031
+ const m = /^(.*?)\s*<([^>]*)>\s*$/.exec(value);
10032
+ const name2 = m === null ? value.trim() : m[1] ?? "";
10033
+ const email = m === null ? "" : m[2] ?? "";
10034
+ const match = (s) => s !== "" && machines.some((glob) => picomatch5.isMatch(s, glob, IDENTITY_GLOB));
10035
+ if (!match(name2) && !match(email)) return void 0;
10036
+ return name2 !== "" ? name2 : email;
10037
+ }
10038
+ function stampEvidence(commit, origLine, fenced) {
10039
+ if (commit.blob === void 0) return void 0;
10040
+ const page = readProvenancePage(commit.blob, fenced === void 0 ? void 0 : { fenced });
10041
+ const body = origLine - page.bodyLine + 1;
10042
+ if (body < 1) return void 0;
10043
+ for (const entry of commit.stamp === void 0 ? page.stamp : provenanceEntries(commit.stamp)) {
10044
+ const span = parseLines(entry.lines);
10045
+ if (span === void 0 || body < span.start || body > span.end) continue;
10046
+ if (pinOfLines(page.body, span) === entry.integrity) return entry["generated-by"];
10047
+ }
10048
+ return void 0;
10049
+ }
10050
+ function recordedEvidence(line, ctx) {
10051
+ if (line.uncommitted) return { rule: 5, sha: line.sha };
10052
+ const commit = ctx.commits.get(line.sha);
10053
+ if (commit === void 0) throw new Error(`no commit ${line.sha}`);
10054
+ const stamped = stampEvidence(commit, line.origLine, ctx.fenced);
10055
+ if (stamped !== void 0) return { machine: stamped, rule: 2, sha: line.sha };
10056
+ const generated = commit.trailers.generatedBy.map((v) => v.trim()).find((v) => v !== "");
10057
+ if (generated !== void 0) return { machine: generated, rule: 3, sha: line.sha };
10058
+ for (const value of commit.trailers.coAuthoredBy) {
10059
+ const machine = machineIdentity(value.trim(), ctx.machines);
10060
+ if (machine !== void 0) return { machine, rule: 4, sha: line.sha };
10061
+ }
10062
+ return { rule: 5, sha: line.sha };
10063
+ }
10064
+ function resolveLineEvidence(line, ctx) {
10065
+ if (line.uncommitted && ctx.generatedBy !== void 0 && ctx.generatedBy !== "") {
10066
+ return { machine: ctx.generatedBy, rule: 1, sha: line.sha };
10067
+ }
10068
+ return recordedEvidence(line, ctx);
10069
+ }
10070
+ function pinOf(lines, span) {
10071
+ const pin = pinOfLines(lines, span);
10072
+ if (pin === void 0) throw new Error(`provenance: lines ${spellLines(span)} fall outside the body`);
10073
+ return pin;
10074
+ }
10075
+ function contextOf(input) {
10076
+ return {
10077
+ commits: input.commits,
10078
+ machines: input.machines ?? DEFAULT_MACHINES,
10079
+ ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
10080
+ ...input.fenced !== void 0 ? { fenced: input.fenced } : {}
10081
+ };
10082
+ }
10083
+ function readOptions(fenced) {
10084
+ return fenced === void 0 ? void 0 : { fenced };
10085
+ }
10086
+ function deriveProvenance(input) {
10087
+ const page = readProvenancePage(input.content, readOptions(input.fenced));
10088
+ const ctx = contextOf(input);
10089
+ const evidenceByLine = /* @__PURE__ */ new Map();
10090
+ for (const line of input.blame) {
10091
+ const body = line.finalLine - page.bodyLine + 1;
10092
+ if (body < 1) continue;
10093
+ evidenceByLine.set(body, resolveLineEvidence(line, ctx));
10094
+ }
10095
+ const groups = [];
10096
+ let open3;
10097
+ for (let n = 1; n <= page.body.length; n++) {
10098
+ const evidence = evidenceByLine.get(n);
10099
+ const machine = evidence?.machine;
10100
+ if (open3 !== void 0 && machine === open3.machine && n === open3.end + 1) {
10101
+ open3.end = n;
10102
+ continue;
10103
+ }
10104
+ if (open3 !== void 0) groups.push(open3);
10105
+ open3 = machine === void 0 || evidence === void 0 ? void 0 : { machine, start: n, end: n, first: evidence };
10106
+ }
10107
+ if (open3 !== void 0) groups.push(open3);
10108
+ return {
10109
+ page,
10110
+ evidenceByLine,
10111
+ derived: groups.map((g) => {
10112
+ const span = { start: g.start, end: g.end };
10113
+ return {
10114
+ entry: { "generated-by": g.machine, lines: lineSpec(span), integrity: pinOf(page.body, span) },
10115
+ span,
10116
+ evidence: g.first
10117
+ };
10118
+ })
10119
+ };
10120
+ }
10121
+ function nearest(candidates, recorded) {
10122
+ let best;
10123
+ for (const c of candidates) {
10124
+ const d = Math.abs(c.span.start - recorded.start);
10125
+ if (best === void 0 || d < best.d || d === best.d && c.span.start < best.c.span.start) best = { c, d };
10126
+ }
10127
+ return best?.c;
10128
+ }
10129
+ var sameSpan = (a, b) => a.start === b.start && a.end === b.end;
10130
+ function scanEvidence(evidenceByLine, span, machine) {
10131
+ let anyMachine = false;
10132
+ for (let n = span.start; n <= span.end; n++) {
10133
+ const evidence = evidenceByLine.get(n);
10134
+ if (evidence?.machine === void 0) continue;
10135
+ anyMachine = true;
10136
+ if (evidence.machine !== machine) return { contradiction: evidence, anyMachine };
10137
+ }
10138
+ return { anyMachine };
10139
+ }
10140
+ function compareProvenance(stamped, derivation) {
10141
+ const { page, evidenceByLine, derived } = derivation;
10142
+ const claimed = /* @__PURE__ */ new Set();
10143
+ const results = [];
10144
+ for (const entry of provenanceEntries(stamped)) {
10145
+ const recorded = parseLines(entry.lines);
10146
+ if (recorded === void 0) continue;
10147
+ const machine = entry["generated-by"];
10148
+ const takenHere = [...claimed].some((d) => sameSpan(d.span, recorded));
10149
+ if (!takenHere && pinOfLines(page.body, recorded) === entry.integrity) {
10150
+ const here = scanEvidence(evidenceByLine, recorded, machine);
10151
+ if (here.contradiction === void 0) {
10152
+ const exact = derived.find(
10153
+ (d) => !claimed.has(d) && sameSpan(d.span, recorded) && d.entry.integrity === entry.integrity
10154
+ );
10155
+ if (exact !== void 0) claimed.add(exact);
10156
+ results.push({ status: "current", entry, span: recorded, ...here.anyMachine ? {} : { noEvidence: true } });
10157
+ continue;
10158
+ }
10159
+ }
10160
+ const pool = derived.filter((d) => !claimed.has(d) && d.entry.integrity === entry.integrity);
10161
+ const hit = nearest(pool, recorded);
10162
+ if (hit !== void 0) {
10163
+ claimed.add(hit);
10164
+ if (hit.entry["generated-by"] !== machine) {
10165
+ results.push({ status: "stale", entry, span: hit.span, evidence: hit.evidence });
10166
+ } else {
10167
+ results.push({ status: sameSpan(hit.span, recorded) ? "current" : "moved", entry, span: hit.span });
10168
+ }
10169
+ continue;
10170
+ }
10171
+ const width = recorded.end - recorded.start + 1;
10172
+ const claimedSpans = [...claimed].map((d) => d.span);
10173
+ const windows = findWindows(page.body, width, entry.integrity, void 0, { around: recorded.start, budget: Infinity }).starts.map((start) => ({ span: { start, end: start + width - 1 } })).filter((w) => !claimedSpans.some((s) => sameSpan(s, w.span)));
10174
+ const found = nearest(windows, recorded);
10175
+ if (found === void 0) {
10176
+ results.push({ status: "changed", entry, span: recorded });
10177
+ continue;
10178
+ }
10179
+ const { contradiction, anyMachine } = scanEvidence(evidenceByLine, found.span, machine);
10180
+ if (contradiction !== void 0) {
10181
+ results.push({ status: "stale", entry, span: found.span, evidence: contradiction });
10182
+ continue;
10183
+ }
10184
+ results.push({
10185
+ status: sameSpan(found.span, recorded) ? "current" : "moved",
10186
+ entry,
10187
+ span: found.span,
10188
+ ...anyMachine ? {} : { noEvidence: true }
10189
+ });
10190
+ }
10191
+ const covering = results.map((r) => r.span);
10192
+ for (const d of derived) {
10193
+ if (claimed.has(d)) continue;
10194
+ let uncovered = false;
10195
+ for (let n = d.span.start; n <= d.span.end && !uncovered; n++) {
10196
+ uncovered = !covering.some((s) => n >= s.start && n <= s.end);
10197
+ }
10198
+ if (uncovered) results.push({ status: "unset", entry: d.entry, span: d.span, evidence: d.evidence });
10199
+ }
10200
+ return results;
10201
+ }
10202
+ function planProvenanceWrite(comparisons, derivation) {
10203
+ const kept = [];
10204
+ for (const r of comparisons) {
10205
+ if (r.status === "current") kept.push({ entry: r.entry, start: r.span.start });
10206
+ else if (r.status === "moved") kept.push({ entry: { ...r.entry, lines: lineSpec(r.span) }, start: r.span.start });
10207
+ }
10208
+ const same = (a, b) => a["generated-by"] === b["generated-by"] && String(a.lines) === String(b.lines) && a.integrity === b.integrity;
10209
+ const out = [...kept];
10210
+ for (const d of derivation.derived) {
10211
+ if (!kept.some((k) => same(k.entry, d.entry))) out.push({ entry: d.entry, start: d.span.start });
10212
+ }
10213
+ return out.sort((a, b) => a.start - b.start).map((k) => k.entry);
10214
+ }
10215
+ var short = (sha) => sha.slice(0, 7);
10216
+ function fileSpelling(span, bodyLine) {
10217
+ return spellLines(toFileLines(span, bodyLine));
10218
+ }
10219
+ function evidenceText(evidence) {
10220
+ if (evidence?.machine === void 0) return "pin";
10221
+ return evidence.rule === 1 ? "uncommitted" : `blame ${short(evidence.sha)}`;
10222
+ }
10223
+ function evidenceRef(evidence) {
10224
+ if (evidence === void 0) return "no evidence";
10225
+ return evidence.rule === 1 ? "uncommitted" : short(evidence.sha);
10226
+ }
10227
+ function rangeResults(comparisons, bodyLine) {
10228
+ return comparisons.map((r) => {
10229
+ const lines = fileSpelling(r.span, bodyLine);
10230
+ const recorded = parseLines(r.entry.lines);
10231
+ const from = recorded === void 0 ? void 0 : fileSpelling(recorded, bodyLine);
10232
+ const machine = r.entry["generated-by"];
10233
+ switch (r.status) {
10234
+ case "stale":
10235
+ return {
10236
+ lines,
10237
+ "generated-by": r.evidence?.machine ?? machine,
10238
+ integrity: r.entry.integrity,
10239
+ status: r.status,
10240
+ evidence: evidenceText(r.evidence),
10241
+ ...from !== void 0 ? { from: { lines: from, "generated-by": machine } } : {},
10242
+ written: false
10243
+ };
10244
+ case "moved":
10245
+ return {
10246
+ lines,
10247
+ "generated-by": machine,
10248
+ integrity: r.entry.integrity,
10249
+ status: r.status,
10250
+ evidence: "pin",
10251
+ ...from !== void 0 ? { from: { lines: from, "generated-by": machine } } : {},
10252
+ written: false
10253
+ };
10254
+ case "unset":
10255
+ return { lines, "generated-by": machine, integrity: r.entry.integrity, status: r.status, evidence: evidenceText(r.evidence), written: false };
10256
+ case "current":
10257
+ case "changed":
10258
+ return { lines, "generated-by": machine, integrity: r.entry.integrity, status: r.status, evidence: "pin", written: false };
10259
+ }
10260
+ });
10261
+ }
10262
+ function provenanceFindings(comparisons, bodyLine) {
10263
+ const findings = [];
10264
+ for (const r of comparisons) {
10265
+ const machine = r.entry["generated-by"];
10266
+ const lines = fileSpelling(r.span, bodyLine);
10267
+ let message;
10268
+ switch (r.status) {
10269
+ case "changed":
10270
+ message = `provenance lines ${lines} changed since ${machine} wrote them`;
10271
+ break;
10272
+ case "stale":
10273
+ message = `provenance lines ${lines} say ${machine}; blame says ${r.evidence?.machine ?? "another machine"} (${evidenceRef(r.evidence)})`;
10274
+ break;
10275
+ case "unset":
10276
+ message = `provenance is unset for lines ${lines}; blame says ${machine} (${evidenceRef(r.evidence)})`;
10277
+ break;
10278
+ case "current":
10279
+ case "moved":
10280
+ continue;
10281
+ }
10282
+ findings.push({
10283
+ schema: DERIVED_STALE_SCHEMA,
10284
+ keyword: DERIVED_KEYWORD,
10285
+ subject: `provenance ${r.entry.integrity}`,
10286
+ instancePath: PROVENANCE_POINTER,
10287
+ message: `${message} \u2014 run manni meta derive`,
10288
+ line: toFileLines(r.span, bodyLine).start
10289
+ });
10290
+ }
10291
+ return findings;
10292
+ }
10293
+ function renderProvenanceValue(entries, bodyLine) {
10294
+ return entries.map((e) => {
10295
+ const span = parseLines(e.lines);
10296
+ const lines = span === void 0 ? String(e.lines) : fileSpelling(span, bodyLine);
10297
+ return `lines ${lines} ${e["generated-by"]}`;
10298
+ }).join("; ");
10299
+ }
10300
+ function parseProvenanceTarget(arg) {
10301
+ try {
10302
+ return splitPageArgument(arg);
10303
+ } catch (err) {
10304
+ if (err instanceof LineRangeError) throw new DocmetaError(`${arg} ends before it starts.`);
10305
+ throw err;
10306
+ }
10307
+ }
10308
+ function attributeRange(input) {
10309
+ const { target } = input;
10310
+ const { page: path, lines: range } = parseProvenanceTarget(target);
10311
+ if (range === void 0) throw new Error(`provenance: ${target} names no lines`);
10312
+ const page = readProvenancePage(input.content, readOptions(input.fenced));
10313
+ if (range.end > page.lines.length) {
10314
+ throw new DocmetaError(
10315
+ `${path} has no lines ${spellLines(range)}: the file ends at line ${page.lines.length}.`
10316
+ );
10317
+ }
10318
+ if (range.start < page.bodyLine) {
10319
+ throw new DocmetaError(
10320
+ `${target} reaches into the frontmatter; provenance pins body lines, which start at line ${page.bodyLine}.`
10321
+ );
10322
+ }
10323
+ const ctx = contextOf(input);
10324
+ for (const line of input.blame) {
10325
+ if (line.finalLine < range.start || line.finalLine > range.end) continue;
10326
+ const evidence = recordedEvidence(line, ctx);
10327
+ if (evidence.machine !== void 0 && evidence.machine !== input.generatedBy) {
10328
+ throw new DocmetaError(
10329
+ `${target}: blame attributes these lines to ${evidence.machine} (${short(evidence.sha)}); --generated-by cannot overrule a recorded machine.`
10330
+ );
10331
+ }
10332
+ }
10333
+ const span = toBodyLines(range, page.bodyLine);
10334
+ return { "generated-by": input.generatedBy, lines: lineSpec(span), integrity: pinOf(page.body, span) };
10335
+ }
10336
+
10337
+ // src/meta/core/derive/git.ts
10338
+ var DEFAULT_BULK_THRESHOLD = 32;
10339
+ var NULL_SHA = "0".repeat(40);
10340
+ var MAX_GIT_OUTPUT_BYTES = Math.min(
10341
+ 512 * 1024 * 1024,
10342
+ bufferConstants.MAX_STRING_LENGTH
10343
+ );
10344
+ var HistoryTooLarge = class extends Error {
10345
+ };
10346
+ var BlobsUnreadable = class extends Error {
10347
+ constructor(detail) {
10348
+ super(detail);
10349
+ this.detail = detail;
10350
+ }
10351
+ detail;
10352
+ };
10353
+ var BlameUnreadable = class extends Error {
10354
+ constructor(rel, detail) {
10355
+ super(rel);
10356
+ this.rel = rel;
10357
+ this.detail = detail;
10358
+ }
10359
+ rel;
10360
+ detail;
10361
+ };
10362
+ function bodyOf(content, fenced) {
10363
+ if (fenced !== true) return content;
10364
+ const loc = locateFrontmatter(content);
10365
+ if (!loc) return content;
10366
+ return content.slice(0, loc.openStart) + content.slice(loc.closeEnd);
10367
+ }
10368
+ async function deriveFromGit(inputs, opts) {
10369
+ const records = /* @__PURE__ */ new Map();
10370
+ const byRoot = groupByRoot(inputs);
10371
+ if (byRoot.size === 0) {
10372
+ return {
10373
+ status: {
10374
+ available: false,
10375
+ reason: "no git repository contains the documents"
10376
+ },
10377
+ records
10378
+ };
10379
+ }
10380
+ const threshold = opts.bulkThreshold ?? DEFAULT_BULK_THRESHOLD;
10381
+ const machines = opts.machines ?? DEFAULT_MACHINES;
10382
+ const wantsProvenance = opts.fields?.includes(PROVENANCE_FIELD) ?? false;
10383
+ const maxBytes = opts.maxOutputBytes ?? MAX_GIT_OUTPUT_BYTES;
10384
+ const reasons = [];
10385
+ for (const [root, bucket] of byRoot) {
10386
+ const run2 = (args, stdin) => runGit(args, root, stdin, maxBytes);
10387
+ const shallow = await run2(["rev-parse", "--is-shallow-repository"]);
10388
+ if (shallow.missing) {
10389
+ return {
10390
+ status: { available: false, reason: "git is not on PATH" },
10391
+ records: /* @__PURE__ */ new Map()
10392
+ };
10393
+ }
10394
+ if (shallow.code !== 0) {
10395
+ reasons.push(`git could not read the repository at ${root}`);
10396
+ continue;
10397
+ }
10398
+ if (text(shallow).trim() === "true") {
10399
+ reasons.push(
10400
+ `this checkout is shallow; use actions/checkout with fetch-depth: 0 (${root})`
10401
+ );
9801
10402
  continue;
9802
10403
  }
9803
- let histories;
9804
- let blobs;
9805
10404
  try {
9806
- histories = bucket.length <= threshold ? await perFileHistories(run2, bucket) : await bulkHistories(run2, bucket);
10405
+ const histories = bucket.length <= threshold ? await perFileHistories(run2, bucket) : await bulkHistories(run2, bucket);
9807
10406
  if (histories === null) {
9808
10407
  reasons.push(`git could not read the history at ${root}`);
9809
10408
  continue;
9810
10409
  }
9811
- blobs = await fetchBlobs(run2, neededBlobs(histories));
10410
+ const blobs = await fetchBlobs(run2, neededBlobs(histories));
10411
+ for (const entry of bucket) {
10412
+ const history = histories.get(entry.rel) ?? [];
10413
+ const facts = judge(entry.input, root, history, blobs, opts.now, machines);
10414
+ if (wantsProvenance) {
10415
+ const derived = await provenanceFor(run2, root, entry, history, blobs, {
10416
+ machines,
10417
+ ...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
10418
+ });
10419
+ facts.provenance = derived.value;
10420
+ facts.provenanceDerivation = derived.derivation;
10421
+ }
10422
+ records.set(entry.input.label, facts);
10423
+ }
9812
10424
  } catch (err) {
9813
10425
  if (err instanceof BlobsUnreadable) {
9814
10426
  reasons.push(`git cat-file could not read the history (${root}): ${err.detail}`);
9815
10427
  continue;
9816
10428
  }
10429
+ if (err instanceof BlameUnreadable) {
10430
+ const why = err.detail === "" ? "" : `: ${err.detail}`;
10431
+ reasons.push(`git blame could not read ${err.rel} (${root})${why}`);
10432
+ continue;
10433
+ }
9817
10434
  if (!(err instanceof HistoryTooLarge)) throw err;
9818
10435
  reasons.push(`git history is too large to read in one pass (${root})`);
9819
10436
  continue;
9820
10437
  }
9821
- for (const entry of bucket) {
9822
- const history = histories.get(entry.rel) ?? [];
9823
- records.set(
9824
- entry.input.label,
9825
- judge(entry.input, root, history, blobs, opts.now)
9826
- );
9827
- }
9828
10438
  }
9829
10439
  if (reasons.length > 0) {
9830
10440
  return {
@@ -9852,7 +10462,7 @@ function groupByRoot(inputs) {
9852
10462
  }
9853
10463
  return byRoot;
9854
10464
  }
9855
- var RECORD_FORMAT = "--format=%x1e%H%x00%aI%x00%an%x00%ae%x00%(trailers:key=Co-authored-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Reviewed-by,valueonly,unfold,separator=%x1f)";
10465
+ var RECORD_FORMAT = "--format=%x1e%H%x00%aI%x00%an%x00%ae%x00%(trailers:key=Co-authored-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Reviewed-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Generated-by,valueonly,unfold,separator=%x1f)";
9856
10466
  var LOG_ARGS = [
9857
10467
  "-c",
9858
10468
  "core.quotePath=false",
@@ -9918,6 +10528,7 @@ function parseLog(text2) {
9918
10528
  authorEmail: fields[3] ?? "",
9919
10529
  coAuthors: splitTrailers(fields[4]),
9920
10530
  reviewedBy: splitTrailers(fields[5]),
10531
+ generatedBy: splitTrailers(fields[6]),
9921
10532
  raw
9922
10533
  });
9923
10534
  }
@@ -10008,6 +10619,7 @@ function attribute(records, wanted) {
10008
10619
  authorEmail: record.authorEmail,
10009
10620
  coAuthors: record.coAuthors,
10010
10621
  reviewedBy: record.reviewedBy,
10622
+ generatedBy: record.generatedBy,
10011
10623
  oldBlob: entry.oldBlob === NULL_SHA ? null : entry.oldBlob,
10012
10624
  newBlob: entry.newBlob === NULL_SHA ? null : entry.newBlob,
10013
10625
  pathAtCommit: entry.newPath
@@ -10057,7 +10669,7 @@ var git = (value, evidence) => ({
10057
10669
  source: "git",
10058
10670
  evidence
10059
10671
  });
10060
- function judge(input, root, history, blobs, now) {
10672
+ function judge(input, root, history, blobs, now, machines) {
10061
10673
  const fenced = input.extracted.fenced;
10062
10674
  const extractor = extractorFor(input);
10063
10675
  const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
@@ -10094,11 +10706,11 @@ function judge(input, root, history, blobs, now) {
10094
10706
  stampsOf(newestBody.oldBlob)
10095
10707
  );
10096
10708
  }
10097
- const authors = bodyChanging.length === 0 ? null : authorsOf(bodyChanging);
10709
+ const authors = bodyChanging.length === 0 ? null : authorsOf(bodyChanging, machines);
10098
10710
  let reviewedBy = null;
10099
10711
  let lastReviewed = null;
10100
10712
  if (!workingChanged && newestBody !== void 0 && newestBody.reviewedBy.length > 0) {
10101
- const evidence = `Reviewed-by trailer in ${short(newestBody.sha)}`;
10713
+ const evidence = `Reviewed-by trailer in ${short2(newestBody.sha)}`;
10102
10714
  reviewedBy = git(newestBody.reviewedBy.map(nameOf), evidence);
10103
10715
  lastReviewed = git(datePart(newestBody.authorDate), evidence);
10104
10716
  }
@@ -10108,6 +10720,7 @@ function judge(input, root, history, blobs, now) {
10108
10720
  authors,
10109
10721
  "reviewed-by": reviewedBy,
10110
10722
  "last-reviewed": lastReviewed,
10723
+ provenance: null,
10111
10724
  lastBodyCommit,
10112
10725
  root
10113
10726
  };
@@ -10115,24 +10728,26 @@ function judge(input, root, history, blobs, now) {
10115
10728
  function createdOf(oldest, stamps) {
10116
10729
  const stamp = stamps["created"];
10117
10730
  if (stamp !== void 0 && stamp !== null) {
10118
- return git(stamp, `stamped in ${short(oldest.sha)}`);
10731
+ return git(stamp, `stamped in ${short2(oldest.sha)}`);
10119
10732
  }
10120
10733
  const date = datePart(oldest.authorDate);
10121
- return git(date, `added in ${short(oldest.sha)} (${date})`);
10734
+ return git(date, `added in ${short2(oldest.sha)} (${date})`);
10122
10735
  }
10123
10736
  function lastUpdatedOf(commit, after, before) {
10124
10737
  const stamp = after["last-updated"];
10125
10738
  if (stamp !== void 0 && stamp !== null && !sameValue(stamp, before["last-updated"])) {
10126
- return git(stamp, `stamped in ${short(commit.sha)}`);
10739
+ return git(stamp, `stamped in ${short2(commit.sha)}`);
10127
10740
  }
10128
10741
  const date = datePart(commit.authorDate);
10129
- return git(date, `body changed in ${short(commit.sha)} (${date})`);
10742
+ return git(date, `body changed in ${short2(commit.sha)} (${date})`);
10130
10743
  }
10131
- function authorsOf(bodyChanging) {
10744
+ function authorsOf(bodyChanging, machines) {
10132
10745
  const seen = /* @__PURE__ */ new Set();
10133
10746
  const names = [];
10134
10747
  const add = (name2, email) => {
10135
- if (name2 === "" || name2.endsWith("[bot]")) return;
10748
+ if (name2 === "") return;
10749
+ const identity = email === null || email === "" ? name2 : `${name2} <${email}>`;
10750
+ if (machineIdentity(identity, machines) !== void 0) return;
10136
10751
  const key = email === null || email === "" ? `name:${name2.toLowerCase()}` : email.toLowerCase();
10137
10752
  if (seen.has(key)) return;
10138
10753
  seen.add(key);
@@ -10145,6 +10760,204 @@ function authorsOf(bodyChanging) {
10145
10760
  const n = bodyChanging.length;
10146
10761
  return git(names, `${n} body-changing ${n === 1 ? "commit" : "commits"}`);
10147
10762
  }
10763
+ function provenanceFenced(extracted) {
10764
+ return extracted.fenced === true || extracted.format === "markdown" || extracted.format === "mdx";
10765
+ }
10766
+ var OUTSIDE_RANGE = "outside-the-attributed-range";
10767
+ var ATTRIBUTED = ":attributed";
10768
+ async function provenanceFor(run2, root, entry, history, blobs, opts) {
10769
+ const { input, rel } = entry;
10770
+ const fenced = provenanceFenced(input.extracted);
10771
+ const blame = await blameOf(run2, rel, input.content);
10772
+ const commits = await commitEvidence(run2, root, entry, blame, history, blobs, input.provenanceManifest);
10773
+ const base = { content: input.content, commits, machines: opts.machines, fenced };
10774
+ let derivation;
10775
+ const attribution = input.attribution;
10776
+ if (attribution !== void 0) {
10777
+ const ranges = attribution.targets.flatMap((target) => {
10778
+ attributeRange({ ...base, blame, target, generatedBy: attribution.generatedBy });
10779
+ const range = parseProvenanceTarget(target).lines;
10780
+ return range === void 0 ? [] : [range];
10781
+ });
10782
+ const inRange = (line) => ranges.some((range) => line.finalLine >= range.start && line.finalLine <= range.end);
10783
+ const attributed = new Map(commits);
10784
+ const trailers = { generatedBy: [attribution.generatedBy], coAuthoredBy: [] };
10785
+ attributed.set(OUTSIDE_RANGE, { sha: OUTSIDE_RANGE, trailers: { generatedBy: [], coAuthoredBy: [] } });
10786
+ const rekeyed = blame.map((line) => {
10787
+ if (inRange(line)) {
10788
+ if (line.uncommitted) return line;
10789
+ const sha = `${line.sha}${ATTRIBUTED}`;
10790
+ attributed.set(sha, { sha, trailers });
10791
+ return { ...line, sha };
10792
+ }
10793
+ return line.uncommitted ? { ...line, sha: OUTSIDE_RANGE, uncommitted: false } : line;
10794
+ });
10795
+ derivation = deriveProvenance({
10796
+ ...base,
10797
+ commits: attributed,
10798
+ generatedBy: attribution.generatedBy,
10799
+ blame: rekeyed
10800
+ });
10801
+ } else {
10802
+ derivation = deriveProvenance({
10803
+ ...base,
10804
+ blame,
10805
+ ...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
10806
+ });
10807
+ }
10808
+ const entries = derivation.derived.map((d) => d.entry);
10809
+ return {
10810
+ value: entries.length === 0 ? null : git(entries, provenanceEvidence(derivation)),
10811
+ derivation
10812
+ };
10813
+ }
10814
+ function provenanceEvidence(derivation) {
10815
+ const shas = [];
10816
+ let uncommitted = false;
10817
+ for (const range of derivation.derived) {
10818
+ for (let n = range.span.start; n <= range.span.end; n++) {
10819
+ const evidence = derivation.evidenceByLine.get(n);
10820
+ if (evidence?.machine === void 0) continue;
10821
+ if (evidence.rule === 1) uncommitted = true;
10822
+ else if (!shas.includes(evidence.sha)) shas.push(evidence.sha);
10823
+ }
10824
+ }
10825
+ const first = shas[0];
10826
+ if (first === void 0) return "uncommitted";
10827
+ const parts = [`blame ${short2(first)}`];
10828
+ if (shas.length > 1) parts.push(`${String(shas.length)} commits`);
10829
+ if (uncommitted) parts.push("uncommitted");
10830
+ return parts.join(", ");
10831
+ }
10832
+ async function blameOf(run2, rel, content) {
10833
+ const out = await run2([
10834
+ "-c",
10835
+ "core.quotePath=false",
10836
+ "blame",
10837
+ "--no-ignore-revs-file",
10838
+ "--line-porcelain",
10839
+ "--",
10840
+ rel
10841
+ ]);
10842
+ if (out.tooLarge) throw new HistoryTooLarge();
10843
+ if (out.code !== 0) {
10844
+ const inHead = await run2(["cat-file", "-e", `HEAD:${rel}`]);
10845
+ if (inHead.code !== 0) {
10846
+ return splitLines(content).map((line, i) => ({
10847
+ sha: ZERO_SHA,
10848
+ origLine: i + 1,
10849
+ finalLine: i + 1,
10850
+ author: "Not Committed Yet",
10851
+ authorMail: "not.committed.yet",
10852
+ filename: rel,
10853
+ boundary: false,
10854
+ uncommitted: true,
10855
+ content: line
10856
+ }));
10857
+ }
10858
+ throw new BlameUnreadable(rel, out.stderr.trim());
10859
+ }
10860
+ return parseLinePorcelain(text(out));
10861
+ }
10862
+ async function commitEvidence(run2, root, entry, blame, history, blobs, manifest) {
10863
+ const pathAt = /* @__PURE__ */ new Map();
10864
+ for (const line of blame) {
10865
+ if (!line.uncommitted && !pathAt.has(line.sha)) pathAt.set(line.sha, unquotePath(line.filename));
10866
+ }
10867
+ const shas = [...pathAt.keys()];
10868
+ const commits = /* @__PURE__ */ new Map();
10869
+ if (shas.length === 0) return commits;
10870
+ const trailers = /* @__PURE__ */ new Map();
10871
+ const pageBlob = /* @__PURE__ */ new Map();
10872
+ for (const h of history) {
10873
+ trailers.set(h.sha, { generatedBy: h.generatedBy, coAuthoredBy: h.coAuthors });
10874
+ const blob = h.newBlob === null ? void 0 : blobs.get(h.newBlob);
10875
+ if (blob !== void 0) pageBlob.set(h.sha, blob);
10876
+ }
10877
+ const unlogged = shas.filter((sha) => !trailers.has(sha));
10878
+ if (unlogged.length > 0) {
10879
+ const out = await run2(["log", "--no-walk=unsorted", RECORD_FORMAT, ...unlogged]);
10880
+ if (out.tooLarge) throw new HistoryTooLarge();
10881
+ if (out.code !== 0) throw new BlobsUnreadable(`git log exit ${String(out.code)}`);
10882
+ for (const record of parseLog(text(out))) {
10883
+ trailers.set(record.sha, { generatedBy: record.generatedBy, coAuthoredBy: record.coAuthors });
10884
+ }
10885
+ }
10886
+ const manifestRel = manifest === void 0 ? void 0 : insideRoot(root, manifest.absPath);
10887
+ const specs = [];
10888
+ for (const sha of shas) {
10889
+ if (!pageBlob.has(sha)) specs.push(`${sha}:${pathAt.get(sha) ?? ""}`);
10890
+ if (manifestRel !== void 0) specs.push(`${sha}:${manifestRel}`);
10891
+ }
10892
+ const fetched = await fetchSpecs(run2, specs);
10893
+ for (const sha of shas) {
10894
+ const blob = pageBlob.get(sha) ?? fetched.get(`${sha}:${pathAt.get(sha) ?? ""}`);
10895
+ commits.set(sha, {
10896
+ sha,
10897
+ trailers: trailers.get(sha) ?? { generatedBy: [], coAuthoredBy: [] },
10898
+ ...blob !== void 0 ? { blob } : {},
10899
+ ...manifest !== void 0 ? {
10900
+ stamp: manifestRel === void 0 ? [] : manifestStamp(
10901
+ fetched.get(`${sha}:${manifestRel}`),
10902
+ manifest.join === "path" ? entryAt(manifest.entry, entry.rel, pathAt.get(sha) ?? entry.rel) : manifest.entry,
10903
+ manifest.join
10904
+ )
10905
+ } : {}
10906
+ });
10907
+ }
10908
+ return commits;
10909
+ }
10910
+ function insideRoot(root, abs) {
10911
+ const rel = relative10(root, abs);
10912
+ if (rel === "" || rel.startsWith("..") || isAbsolute5(rel)) return void 0;
10913
+ return rel.split(sep5).join("/");
10914
+ }
10915
+ function entryAt(entry, rel, relAtCommit) {
10916
+ if (relAtCommit === rel) return entry;
10917
+ const fromHere = posix2.relative(posix2.dirname(rel), relAtCommit);
10918
+ return posix2.join(posix2.dirname(entry), fromHere);
10919
+ }
10920
+ function manifestStamp(text2, key, join10) {
10921
+ if (text2 === void 0) return [];
10922
+ let data;
10923
+ try {
10924
+ data = parseYaml4(text2);
10925
+ } catch {
10926
+ return [];
10927
+ }
10928
+ if (!isRecord4(data)) return [];
10929
+ for (const [candidate, value] of Object.entries(data)) {
10930
+ const same = join10 === "path" ? posix2.normalize(candidate) === posix2.normalize(key) : candidate === key;
10931
+ if (same && isRecord4(value)) return provenanceEntries(value[PROVENANCE_FIELD]);
10932
+ }
10933
+ return [];
10934
+ }
10935
+ function isRecord4(x) {
10936
+ return typeof x === "object" && x !== null && !Array.isArray(x);
10937
+ }
10938
+ async function fetchSpecs(run2, specs) {
10939
+ const found = /* @__PURE__ */ new Map();
10940
+ if (specs.length === 0) return found;
10941
+ const out = await run2(["cat-file", "--batch"], `${specs.join("\n")}
10942
+ `);
10943
+ if (out.tooLarge) throw new HistoryTooLarge();
10944
+ if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
10945
+ const buf = out.raw;
10946
+ let cursor = 0;
10947
+ for (const spec of specs) {
10948
+ const nl = buf.indexOf(10, cursor);
10949
+ if (nl === -1) break;
10950
+ const header = buf.toString("utf8", cursor, nl);
10951
+ cursor = nl + 1;
10952
+ if (header.endsWith(" missing") || header.endsWith(" ambiguous")) continue;
10953
+ const parts = header.split(" ");
10954
+ const size = Number(parts[2]);
10955
+ if (!Number.isFinite(size)) break;
10956
+ if (parts[1] === "blob") found.set(spec, buf.toString("utf8", cursor, cursor + size));
10957
+ cursor += size + 1;
10958
+ }
10959
+ return found;
10960
+ }
10148
10961
  function extractorFor(input) {
10149
10962
  return extractorByName(input.extracted.format) ?? extractorForExtension(extname2(input.absPath));
10150
10963
  }
@@ -10163,7 +10976,7 @@ function sameBody(a, b) {
10163
10976
  function sameValue(a, b) {
10164
10977
  return toJsonText(a) === toJsonText(b);
10165
10978
  }
10166
- var short = (sha) => sha.slice(0, 7);
10979
+ var short2 = (sha) => sha.slice(0, 7);
10167
10980
  var datePart = (iso) => iso.slice(0, 10);
10168
10981
  function localDate(d) {
10169
10982
  const pad2 = (n) => String(n).padStart(2, "0");
@@ -10177,6 +10990,7 @@ function emailOf(trailer) {
10177
10990
  const m = /<([^>]*)>\s*$/.exec(trailer);
10178
10991
  return m?.[1]?.trim() ?? null;
10179
10992
  }
10993
+ var MAX_STDERR_BYTES = 16 * 1024;
10180
10994
  function text(out) {
10181
10995
  try {
10182
10996
  return out.raw.toString("utf8");
@@ -10192,7 +11006,7 @@ function runGit(args, cwd, stdin, maxBytes) {
10192
11006
  done = true;
10193
11007
  settle(out);
10194
11008
  };
10195
- const fail = (err) => {
11009
+ const fail2 = (err) => {
10196
11010
  if (done) return;
10197
11011
  done = true;
10198
11012
  reject(err instanceof Error ? err : new Error(String(err)));
@@ -10201,7 +11015,8 @@ function runGit(args, cwd, stdin, maxBytes) {
10201
11015
  code: null,
10202
11016
  raw: Buffer.alloc(0),
10203
11017
  missing,
10204
- tooLarge: false
11018
+ tooLarge: false,
11019
+ stderr: ""
10205
11020
  });
10206
11021
  let child;
10207
11022
  try {
@@ -10224,7 +11039,14 @@ function runGit(args, cwd, stdin, maxBytes) {
10224
11039
  }
10225
11040
  chunks.push(chunk);
10226
11041
  });
10227
- child.stderr.resume();
11042
+ const errChunks = [];
11043
+ let errTotal = 0;
11044
+ child.stderr.on("data", (chunk) => {
11045
+ if (errTotal >= MAX_STDERR_BYTES) return;
11046
+ const kept = chunk.subarray(0, MAX_STDERR_BYTES - errTotal);
11047
+ errTotal += kept.length;
11048
+ errChunks.push(kept);
11049
+ });
10228
11050
  child.on("error", (err) => {
10229
11051
  finish(failed(err.code === "ENOENT"));
10230
11052
  });
@@ -10233,9 +11055,10 @@ function runGit(args, cwd, stdin, maxBytes) {
10233
11055
  child.on("close", (code) => {
10234
11056
  try {
10235
11057
  const raw = tooLarge ? Buffer.alloc(0) : Buffer.concat(chunks);
10236
- finish({ code, raw, missing: false, tooLarge });
11058
+ const stderr = Buffer.concat(errChunks).toString("utf8");
11059
+ finish({ code, raw, missing: false, tooLarge, stderr });
10237
11060
  } catch (err) {
10238
- fail(err);
11061
+ fail2(err);
10239
11062
  }
10240
11063
  });
10241
11064
  if (stdin === void 0) child.stdin.end();
@@ -10250,7 +11073,8 @@ var FIELD_SOURCES = {
10250
11073
  authors: ["git"],
10251
11074
  owner: ["codeowners"],
10252
11075
  "reviewed-by": ["github", "gitlab", "git"],
10253
- "last-reviewed": ["github", "gitlab", "git"]
11076
+ "last-reviewed": ["github", "gitlab", "git"],
11077
+ provenance: ["git"]
10254
11078
  };
10255
11079
  var REVIEW_SOURCES = ["github", "gitlab"];
10256
11080
  var HOST_NAME = {
@@ -10284,10 +11108,20 @@ async function deriveMetadata(inputs, ctx) {
10284
11108
  const sources = {};
10285
11109
  let git2 = /* @__PURE__ */ new Map();
10286
11110
  if (consulted.has("git")) {
10287
- const result = await deriveFromGit(inputs, { cwd: ctx.cwd, now: ctx.now });
11111
+ const result = await deriveFromGit(inputs, {
11112
+ cwd: ctx.cwd,
11113
+ now: ctx.now,
11114
+ fields: ctx.fields,
11115
+ ...ctx.generatedBy !== void 0 ? { generatedBy: ctx.generatedBy } : {},
11116
+ ...ctx.machines !== void 0 ? { machines: ctx.machines } : {}
11117
+ });
10288
11118
  sources.git = result.status;
10289
11119
  git2 = result.records;
10290
11120
  }
11121
+ const provenance = /* @__PURE__ */ new Map();
11122
+ for (const [label, facts] of git2) {
11123
+ if (facts.provenanceDerivation !== void 0) provenance.set(label, facts.provenanceDerivation);
11124
+ }
10291
11125
  const rootCache = /* @__PURE__ */ new Map();
10292
11126
  const rootOf = (absPath) => {
10293
11127
  const dir = dirname9(absPath);
@@ -10365,7 +11199,7 @@ async function deriveMetadata(inputs, ctx) {
10365
11199
  }
10366
11200
  records.set(input.label, { file: input.label, fields });
10367
11201
  }
10368
- return { records, sources };
11202
+ return { records, sources, ...provenance.size > 0 ? { provenance } : {} };
10369
11203
  }
10370
11204
  function valueFrom(source, field, git2, reviews, owner, commanded) {
10371
11205
  switch (source) {
@@ -10448,6 +11282,9 @@ function commandsOf(config) {
10448
11282
  }
10449
11283
  return out;
10450
11284
  }
11285
+ function machinesOf(config) {
11286
+ return config?.machines ?? DEFAULT_MACHINES;
11287
+ }
10451
11288
 
10452
11289
  // src/meta/core/derive/table.ts
10453
11290
  var DERIVED_VIEW = "derived";
@@ -10462,10 +11299,11 @@ function mentionsDerived(sql) {
10462
11299
  return /\b(?:from|join|update|into|table|view)\s+(?:"derived"|derived\b)/i.test(sql);
10463
11300
  }
10464
11301
  function fieldsForSql(sql, fields) {
10465
- if (sql.includes("*") || /\b_(?:sources|origin)\b/i.test(sql)) return [...fields];
10466
- return fields.filter(
10467
- (field) => new RegExp(`(?<![\\w-])${escapeRegExp(field)}(?![\\w-])`, "i").test(sql)
10468
- );
11302
+ const named = (field) => new RegExp(`(?<![\\w-])${escapeRegExp(field)}(?![\\w-])`, "i").test(sql);
11303
+ if (sql.includes("*") || /\b_(?:sources|origin)\b/i.test(sql)) {
11304
+ return fields.filter((field) => field !== PROVENANCE_FIELD || named(field));
11305
+ }
11306
+ return fields.filter(named);
10469
11307
  }
10470
11308
  function escapeRegExp(s) {
10471
11309
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -10559,16 +11397,18 @@ function createResolvedView(db, docsColumns, commands) {
10559
11397
  async function deriveForTable(inputs, ctx, hint, fields) {
10560
11398
  const derive = ctx.config?.derive;
10561
11399
  const commands = commandsOf(derive);
11400
+ const managesProvenance = derive?.fields.includes(PROVENANCE_FIELD) === true && !fields.includes(PROVENANCE_FIELD);
10562
11401
  const result = await deriveMetadata(inputs, {
10563
11402
  cwd: ctx.cwd,
10564
11403
  base: ctx.base,
10565
11404
  ...ctx.configDir !== void 0 ? { configDir: ctx.configDir } : {},
10566
11405
  sources: derive?.sources ?? [...DERIVE_SOURCES],
10567
- fields,
11406
+ fields: managesProvenance ? [...fields, PROVENANCE_FIELD] : fields,
10568
11407
  ...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
10569
11408
  ...commands !== void 0 ? { commands } : {},
10570
11409
  cache: ctx.cache,
10571
- now: ctx.now ?? (() => /* @__PURE__ */ new Date())
11410
+ now: ctx.now ?? (() => /* @__PURE__ */ new Date()),
11411
+ machines: machinesOf(derive)
10572
11412
  });
10573
11413
  assertSourcesAvailable(result.sources, hint);
10574
11414
  return result.records;
@@ -10576,7 +11416,7 @@ async function deriveForTable(inputs, ctx, hint, fields) {
10576
11416
 
10577
11417
  // src/meta/commands/validate.ts
10578
11418
  import { readFile as readFile8 } from "node:fs/promises";
10579
- import { resolve as resolve12, extname as extname3 } from "node:path";
11419
+ import { resolve as resolve13, extname as extname3 } from "node:path";
10580
11420
 
10581
11421
  // src/meta/core/checks.ts
10582
11422
  var CHECK_KEYWORD = "check";
@@ -10713,6 +11553,71 @@ async function runChecks(checks, entries, ctx = {}) {
10713
11553
  return merged;
10714
11554
  }
10715
11555
 
11556
+ // src/meta/core/derive/provenance-place.ts
11557
+ import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve12, sep as sep6 } from "node:path";
11558
+ var toPosix3 = (p) => p.split(sep6).join("/");
11559
+ function provenanceManifests(collections, configDir, base) {
11560
+ const out = [];
11561
+ for (const collection of collections) {
11562
+ for (const manifest of collection.externalMetadata) {
11563
+ if (!manifest.keys.includes(PROVENANCE_FIELD)) continue;
11564
+ if (classifyRef(manifest.file).kind === "url") {
11565
+ throw new DocmetaError(
11566
+ `collection ${collection.name}: ${PROVENANCE_FIELD} cannot come from a URL manifest, because manni meta derive writes it.`
11567
+ );
11568
+ }
11569
+ const absPath = isAbsolute6(manifest.file) ? manifest.file : resolve12(configDir, manifest.file);
11570
+ const rel = relative11(base, absPath);
11571
+ out.push({
11572
+ collection: collection.name,
11573
+ absPath,
11574
+ file: rel === "" ? "." : toPosix3(rel),
11575
+ join: externalMetadataJoin(manifest)
11576
+ });
11577
+ }
11578
+ }
11579
+ return out;
11580
+ }
11581
+ function provenancePlace(label, data, manifests, collections, configDir, base) {
11582
+ if (manifests.length === 0) return void 0;
11583
+ const members = new Set(memberOf(collections, configDir, base, label));
11584
+ const owning = manifests.filter((m) => members.has(m.collection));
11585
+ const [manifest, second] = owning;
11586
+ if (manifest === void 0) return void 0;
11587
+ if (second !== void 0) {
11588
+ throw new DocmetaError(
11589
+ `${label} is in collections ${manifest.collection} and ${second.collection}, and both keep ${PROVENANCE_FIELD} in a manifest.`
11590
+ );
11591
+ }
11592
+ if (manifest.join === PATH_JOIN) {
11593
+ const entry = toPosix3(relative11(configDir, resolve12(base, label)));
11594
+ return { manifest, absPath: manifest.absPath, entry, join: manifest.join };
11595
+ }
11596
+ const value = data[manifest.join];
11597
+ if (typeof value !== "string" && typeof value !== "number") {
11598
+ throw new DocmetaError(
11599
+ `${label} carries no ${manifest.join}, which ${manifest.file} joins on, so its ${PROVENANCE_FIELD} has no entry there.`
11600
+ );
11601
+ }
11602
+ return { manifest, absPath: manifest.absPath, entry: String(value), join: manifest.join };
11603
+ }
11604
+ function readerManifests(collections, configDir, base) {
11605
+ const local = collections.map((c) => ({
11606
+ ...c,
11607
+ externalMetadata: c.externalMetadata.filter((m) => classifyRef(m.file).kind !== "url")
11608
+ }));
11609
+ return provenanceManifests(local, configDir, base);
11610
+ }
11611
+ function readerPlace(label, data, manifests, collections, configDir, base) {
11612
+ try {
11613
+ const place = provenancePlace(label, data, manifests, collections, configDir, base);
11614
+ return place === void 0 ? void 0 : { absPath: place.absPath, entry: place.entry, join: place.join };
11615
+ } catch (err) {
11616
+ if (err instanceof DocmetaError) return void 0;
11617
+ throw err;
11618
+ }
11619
+ }
11620
+
10716
11621
  // src/meta/commands/validate.ts
10717
11622
  function parseErrorResult(file, format, message, keyword) {
10718
11623
  const err = {
@@ -10836,6 +11741,7 @@ async function runValidate(opts) {
10836
11741
  const deriveFields = new Set(deriveWillRun ? deriveConfig.fields : []);
10837
11742
  const readable = derivableFields(deriveCommands);
10838
11743
  for (const c of derivedChecks) for (const f of fieldsForSql(c.query, readable)) deriveFields.add(f);
11744
+ const provenanceManifests2 = keepDeriveInputs && deriveFields.has(PROVENANCE_FIELD) ? readerManifests(declaredCollections, configDir ?? cwd, base) : [];
10839
11745
  const derivedOnce = once(async () => {
10840
11746
  const derived = await deriveMetadata(deriveInputs, {
10841
11747
  cwd,
@@ -10846,7 +11752,8 @@ async function runValidate(opts) {
10846
11752
  ...deriveConfig?.codeowners !== void 0 ? { codeowners: deriveConfig.codeowners } : {},
10847
11753
  ...deriveCommands !== void 0 ? { commands: deriveCommands } : {},
10848
11754
  cache: opts.cache ?? true,
10849
- now: opts.now ?? (() => /* @__PURE__ */ new Date())
11755
+ now: opts.now ?? (() => /* @__PURE__ */ new Date()),
11756
+ machines: machinesOf(deriveConfig)
10850
11757
  });
10851
11758
  assertSourcesAvailable(
10852
11759
  derived.sources,
@@ -10880,7 +11787,14 @@ async function runValidate(opts) {
10880
11787
  return;
10881
11788
  }
10882
11789
  if (keepDeriveInputs && label !== STDIN_LABEL) {
10883
- deriveInputs.push({ label, absPath: resolve12(base, label), content, extracted });
11790
+ const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, extracted.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base);
11791
+ deriveInputs.push({
11792
+ label,
11793
+ absPath: resolve13(base, label),
11794
+ content,
11795
+ extracted,
11796
+ ...place !== void 0 ? { provenanceManifest: place } : {}
11797
+ });
10884
11798
  }
10885
11799
  const merged = mergeExternalMetadata(label, extracted, externalMetadata, members, base, {
10886
11800
  encryptionKey
@@ -10993,7 +11907,7 @@ async function runValidate(opts) {
10993
11907
  await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
10994
11908
  }
10995
11909
  for (const file of files) {
10996
- const content = await readFile8(resolve12(base, file), "utf8");
11910
+ const content = await readFile8(resolve13(base, file), "utf8");
10997
11911
  await processOne(file, content, extname3(file));
10998
11912
  }
10999
11913
  if (unverified > 0) opts.onNotice?.(unverifiedWarning(unverified));
@@ -11083,18 +11997,23 @@ async function runValidate(opts) {
11083
11997
  }
11084
11998
  marked = [...managed.marked, ...await validator.markedPointers(stamped, managed.refs)];
11085
11999
  }
11086
- const findings = staleFindings(
11087
- fields.map((field) => {
11088
- const compared = compareDerived(
11089
- field,
11090
- managed !== void 0 ? managed.data[field] : input.extracted.data[field],
11091
- record.fields[field]
12000
+ const asserted = (field) => managed !== void 0 ? managed.data[field] : input.extracted.data[field];
12001
+ const findings = fields.flatMap((field) => {
12002
+ if (field === PROVENANCE_FIELD) {
12003
+ const derivation = derived.provenance?.get(input.label);
12004
+ if (derivation === void 0) return [];
12005
+ return provenanceFindings(
12006
+ compareProvenance(provenanceEntries(asserted(field)), derivation),
12007
+ derivation.page.bodyLine
11092
12008
  );
11093
- const at2 = pointerOf(field);
11094
- return marked.some((p) => isAtOrUnder(p, at2)) ? redactedDerived(compared) : compared;
11095
- }),
11096
- input.extracted.lineFor
11097
- );
12009
+ }
12010
+ const compared = compareDerived(field, asserted(field), record.fields[field]);
12011
+ const at2 = pointerOf(field);
12012
+ return staleFindings(
12013
+ [marked.some((p) => isAtOrUnder(p, at2)) ? redactedDerived(compared) : compared],
12014
+ input.extracted.lineFor
12015
+ );
12016
+ });
11098
12017
  if (findings.length === 0) continue;
11099
12018
  result.errors.push(...findings);
11100
12019
  result.ok = false;
@@ -11132,13 +12051,13 @@ async function runValidate(opts) {
11132
12051
 
11133
12052
  // src/meta/commands/get.ts
11134
12053
  import { readFile as readFile9 } from "node:fs/promises";
11135
- import { resolve as resolve13, extname as extname4 } from "node:path";
12054
+ import { resolve as resolve14, extname as extname4 } from "node:path";
11136
12055
  async function runGet(opts) {
11137
12056
  const cwd = opts.cwd ?? process.cwd();
11138
12057
  if (opts.fields.length === 0) {
11139
12058
  throw new DocmetaError("Specify at least one field to get.");
11140
12059
  }
11141
- const { config, inputs, base, configDir, collections, fromCollections, configFile } = await resolveRunConfig({
12060
+ const { config, inputs, base, configDir, collections, declaredCollections, fromCollections, configFile } = await resolveRunConfig({
11142
12061
  cwd,
11143
12062
  configPath: opts.configPath,
11144
12063
  noConfig: opts.noConfig,
@@ -11197,7 +12116,10 @@ async function runGet(opts) {
11197
12116
  const deriving = opts.derived !== false;
11198
12117
  const derivableFields2 = deriving ? opts.fields.filter((f) => isBuiltinField(f) || Object.hasOwn(commands, f)) : [];
11199
12118
  const deriveInputs = [];
12119
+ const wantsProvenance = opts.fields.includes(PROVENANCE_FIELD);
12120
+ const provenanceManifests2 = deriving && derivableFields2.includes(PROVENANCE_FIELD) ? readerManifests(declaredCollections, configDir ?? cwd, base) : [];
11200
12121
  const carried = /* @__PURE__ */ new Map();
12122
+ const bodyLines = /* @__PURE__ */ new Map();
11201
12123
  const readOne = (label, content, extension) => {
11202
12124
  const extractor = forced ?? extractorForExtension(extension);
11203
12125
  if (!extractor) {
@@ -11212,13 +12134,18 @@ async function runGet(opts) {
11212
12134
  elements: resolveElements(label, config, members)
11213
12135
  });
11214
12136
  if (deriving && label !== STDIN_LABEL) {
12137
+ const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, own2.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base);
11215
12138
  deriveInputs.push({
11216
12139
  label,
11217
- absPath: resolve13(base, label),
12140
+ absPath: resolve14(base, label),
11218
12141
  content,
11219
- extracted: own2
12142
+ extracted: own2,
12143
+ ...place !== void 0 ? { provenanceManifest: place } : {}
11220
12144
  });
11221
12145
  }
12146
+ if (wantsProvenance) {
12147
+ bodyLines.set(label, readProvenancePage(content, { fenced: provenanceFenced(own2) }).bodyLine);
12148
+ }
11222
12149
  extracted = mergeExternalMetadata(label, own2, externalMetadata, members, base, {
11223
12150
  encryptionKey: joinKey
11224
12151
  }).extracted;
@@ -11235,7 +12162,13 @@ async function runGet(opts) {
11235
12162
  carries[f] = carriesField(extracted.data, f);
11236
12163
  }
11237
12164
  carried.set(label, carries);
11238
- out.push({ file: label, present: extracted.present, values });
12165
+ const bodyLine = bodyLines.get(label);
12166
+ out.push({
12167
+ file: label,
12168
+ present: extracted.present,
12169
+ values,
12170
+ ...bodyLine !== void 0 ? { provenance: { bodyLine } } : {}
12171
+ });
11239
12172
  };
11240
12173
  if (usingStdin) {
11241
12174
  if (!forced) {
@@ -11246,7 +12179,7 @@ async function runGet(opts) {
11246
12179
  readOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
11247
12180
  }
11248
12181
  for (const file of files) {
11249
- const content = await readFile9(resolve13(base, file), "utf8");
12182
+ const content = await readFile9(resolve14(base, file), "utf8");
11250
12183
  readOne(file, content, extname4(file));
11251
12184
  }
11252
12185
  if (deriving) {
@@ -11269,6 +12202,7 @@ async function runGet(opts) {
11269
12202
  async function attachResolved(out, inputs, opts) {
11270
12203
  const { run: run2 } = opts;
11271
12204
  const derive = run2.config?.derive;
12205
+ let provenance = /* @__PURE__ */ new Map();
11272
12206
  const commands = commandsOf(derive);
11273
12207
  const records = opts.derivable.length === 0 || inputs.length === 0 ? /* @__PURE__ */ new Map() : await (async () => {
11274
12208
  const result = await deriveMetadata(inputs, {
@@ -11280,12 +12214,14 @@ async function attachResolved(out, inputs, opts) {
11280
12214
  ...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
11281
12215
  ...commands !== void 0 ? { commands } : {},
11282
12216
  cache: run2.cache,
11283
- now: run2.now
12217
+ now: run2.now,
12218
+ machines: machinesOf(derive)
11284
12219
  });
11285
12220
  assertSourcesAvailable(
11286
12221
  result.sources,
11287
12222
  "pass --no-derived, or narrow derive.sources"
11288
12223
  );
12224
+ if (result.provenance !== void 0) provenance = result.provenance;
11289
12225
  return result.records;
11290
12226
  })();
11291
12227
  for (const r of out) {
@@ -11313,6 +12249,11 @@ async function attachResolved(out, inputs, opts) {
11313
12249
  if (opts.derivable.length > 0) r.derived = derived;
11314
12250
  r.resolved = resolved;
11315
12251
  r.origin = origin;
12252
+ const derivation = provenance.get(r.file);
12253
+ if (r.provenance !== void 0 && derivation !== void 0) {
12254
+ const comparisons = compareProvenance(provenanceEntries(r.values[PROVENANCE_FIELD]), derivation);
12255
+ r.provenance.current = comparisons.every((c) => c.status === "current");
12256
+ }
11316
12257
  }
11317
12258
  }
11318
12259
  function resolveField(data, field) {
@@ -11365,15 +12306,15 @@ function parseJsonPointer(pointer) {
11365
12306
  // src/meta/commands/schemas.ts
11366
12307
  import { existsSync as existsSync4 } from "node:fs";
11367
12308
  import { mkdir as mkdir2, readFile as readFile10 } from "node:fs/promises";
11368
- import { dirname as dirname10, extname as extname5, isAbsolute as isAbsolute5, join as join8, relative as relative11, resolve as resolve14, sep as sep6 } from "node:path";
11369
- import { parseDocument as parseDocument5, parse as parseYaml4, stringify as stringify2 } from "yaml";
12309
+ import { dirname as dirname10, extname as extname5, isAbsolute as isAbsolute7, join as join8, relative as relative12, resolve as resolve15, sep as sep7 } from "node:path";
12310
+ import { parseDocument as parseDocument5, parse as parseYaml5, stringify as stringify2 } from "yaml";
11370
12311
  function getSchemasInfo() {
11371
12312
  return { builtins: listBuiltins(), formats: listFormats() };
11372
12313
  }
11373
12314
  var DEFAULT_VENDOR_DIR = "./schema";
11374
12315
  var DEFAULT_CONFIG_NAME = "manni.config.yaml";
11375
12316
  function posixRelative(from, to) {
11376
- return relative11(from, to).split(sep6).join("/");
12317
+ return relative12(from, to).split(sep7).join("/");
11377
12318
  }
11378
12319
  function vendorFileName(url) {
11379
12320
  let parsed2;
@@ -11447,7 +12388,7 @@ function reparseConfig(text2, source, section) {
11447
12388
  parseConfig(text2, source);
11448
12389
  return;
11449
12390
  }
11450
- const doc = parseYaml4(text2);
12391
+ const doc = parseYaml5(text2);
11451
12392
  parseConfigValue(doc[section], source, section);
11452
12393
  }
11453
12394
  async function runVendorSchema(opts) {
@@ -11459,13 +12400,13 @@ async function runVendorSchema(opts) {
11459
12400
  );
11460
12401
  }
11461
12402
  const loaded = await loadConfig(opts.configPath, cwd);
11462
- const configPath = loaded?.path ?? resolve14(cwd, DEFAULT_CONFIG_NAME);
12403
+ const configPath = loaded?.path ?? resolve15(cwd, DEFAULT_CONFIG_NAME);
11463
12404
  const configDir = loaded?.dir ?? cwd;
11464
- const absDir = resolve14(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
12405
+ const absDir = resolve15(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
11465
12406
  const absFile = join8(absDir, vendorFileName(url));
11466
12407
  await assertNotIgnored(absFile, absDir, cwd, opts.onNotice);
11467
12408
  const fromConfig = posixRelative(configDir, absFile);
11468
- const ref = isAbsolute5(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
12409
+ const ref = isAbsolute7(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
11469
12410
  const { bytes } = await fetchSchemaBytes(url, {
11470
12411
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
11471
12412
  ...opts.maxBytes !== void 0 ? { maxBytes: opts.maxBytes } : {}
@@ -11642,7 +12583,7 @@ async function runInferSchema(opts) {
11642
12583
  }
11643
12584
  let absOut;
11644
12585
  if (opts.out !== void 0) {
11645
- absOut = resolve14(cwd, opts.out);
12586
+ absOut = resolve15(cwd, opts.out);
11646
12587
  await assertNotIgnored(
11647
12588
  absOut,
11648
12589
  dirname10(absOut),
@@ -11730,7 +12671,7 @@ async function runInferSchema(opts) {
11730
12671
  scanOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
11731
12672
  }
11732
12673
  for (const file of files) {
11733
- const content = await readFile10(resolve14(base, file), "utf8");
12674
+ const content = await readFile10(resolve15(base, file), "utf8");
11734
12675
  scanOne(file, content, extname5(file));
11735
12676
  }
11736
12677
  for (const u of unreadable) {
@@ -11792,7 +12733,7 @@ async function runInferSchema(opts) {
11792
12733
  import { randomBytes as randomBytes2 } from "node:crypto";
11793
12734
  import { existsSync as existsSync5 } from "node:fs";
11794
12735
  import { mkdir as mkdir3, open as open2, readFile as readFile11, rename as rename3, rm as rm3 } from "node:fs/promises";
11795
- import { dirname as dirname11, isAbsolute as isAbsolute6, resolve as resolve15, extname as extname6, sep as sep7 } from "node:path";
12736
+ import { dirname as dirname11, isAbsolute as isAbsolute8, resolve as resolve16, extname as extname6, sep as sep8 } from "node:path";
11796
12737
 
11797
12738
  // src/shared/prompt.ts
11798
12739
  import { createInterface } from "node:readline";
@@ -12009,10 +12950,10 @@ async function runQuery(opts) {
12009
12950
  readOne(STDIN_LABEL, opts.stdinContent ?? "", ext);
12010
12951
  }
12011
12952
  for (const file of files) {
12012
- const content = await readFile11(resolve15(base, file), "utf8");
12953
+ const content = await readFile11(resolve16(base, file), "utf8");
12013
12954
  readOne(file, content, extname6(file));
12014
12955
  }
12015
- const db = opts.db === void 0 ? void 0 : { resolved: resolve15(cwd, opts.db), display: opts.db };
12956
+ const db = opts.db === void 0 ? void 0 : { resolved: resolve16(cwd, opts.db), display: opts.db };
12016
12957
  const run2 = await runSql(sql, entries, {
12017
12958
  target: db,
12018
12959
  params: boundParams,
@@ -12143,13 +13084,21 @@ async function runSql(sql, entries, ctx) {
12143
13084
  if (target) buildViews();
12144
13085
  const buildDerived = async () => {
12145
13086
  if (derivedTable.built) return;
12146
- const inputs = entries.filter((e) => e.label !== STDIN_LABEL).map((e) => ({
12147
- label: e.label,
12148
- absPath: resolve15(ctx.base, e.label),
12149
- content: e.content,
12150
- extracted: e.own
12151
- }));
12152
13087
  const commands = commandsOf(ctx.config?.derive);
13088
+ const fields = fieldsForSql(sql, derivableFields(commands));
13089
+ const provenanceDerived = fields.includes(PROVENANCE_FIELD) || ctx.config?.derive?.fields.includes(PROVENANCE_FIELD) === true;
13090
+ const manifestRoot = ctx.configDir ?? ctx.cwd;
13091
+ const manifests = provenanceDerived ? readerManifests(ctx.declaredCollections, manifestRoot, ctx.base) : [];
13092
+ const inputs = entries.filter((e) => e.label !== STDIN_LABEL).map((e) => {
13093
+ const place = manifests.length === 0 ? void 0 : readerPlace(e.label, e.own.data, manifests, ctx.declaredCollections, manifestRoot, ctx.base);
13094
+ return {
13095
+ label: e.label,
13096
+ absPath: resolve16(ctx.base, e.label),
13097
+ content: e.content,
13098
+ extracted: e.own,
13099
+ ...place !== void 0 ? { provenanceManifest: place } : {}
13100
+ };
13101
+ });
12153
13102
  const records = await deriveForTable(
12154
13103
  inputs,
12155
13104
  {
@@ -12161,7 +13110,7 @@ async function runSql(sql, entries, ctx) {
12161
13110
  ...ctx.now !== void 0 ? { now: ctx.now } : {}
12162
13111
  },
12163
13112
  "narrow derive.sources in manni.config.yaml",
12164
- fieldsForSql(sql, derivableFields(commands))
13113
+ fields
12165
13114
  );
12166
13115
  createDerivedView(db, records, derivedColumns(commands));
12167
13116
  createResolvedView(db, dataColumns, commands);
@@ -12603,20 +13552,20 @@ function displayPath(ctx, abs) {
12603
13552
  }
12604
13553
  function assertSchemaWriteWithin(ctx, abs) {
12605
13554
  const within = posixRelative(ctx.trustRoot.dir, abs);
12606
- if (within !== "" && !within.startsWith("..") && !isAbsolute6(within)) return;
13555
+ if (within !== "" && !within.startsWith("..") && !isAbsolute8(within)) return;
12607
13556
  throw new DocmetaError(
12608
13557
  `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.`
12609
13558
  );
12610
13559
  }
12611
13560
  function foldPath(p) {
12612
- const abs = resolve15(p);
13561
+ const abs = resolve16(p);
12613
13562
  return process.platform === "win32" ? abs.toLowerCase() : abs;
12614
13563
  }
12615
13564
  function filePinsByPath(ctx) {
12616
13565
  const byPath = /* @__PURE__ */ new Map();
12617
13566
  for (const [ref, pin] of collectSchemaPins(ctx.config)) {
12618
13567
  if (classifyRef(ref).kind !== "file") continue;
12619
- byPath.set(foldPath(resolve15(ctx.cwd, ref)), pin);
13568
+ byPath.set(foldPath(resolve16(ctx.cwd, ref)), pin);
12620
13569
  }
12621
13570
  return byPath;
12622
13571
  }
@@ -12644,7 +13593,7 @@ async function loadSetMembers(refs, ctx) {
12644
13593
  });
12645
13594
  continue;
12646
13595
  }
12647
- const abs = resolve15(ctx.cwd, ref);
13596
+ const abs = resolve16(ctx.cwd, ref);
12648
13597
  const canon = foldPath(abs);
12649
13598
  if (seenFiles.has(canon)) continue;
12650
13599
  seenFiles.add(canon);
@@ -12849,7 +13798,7 @@ async function planSchemaMutation(ops, entries, ctx) {
12849
13798
  const [, name2, ver] = target.builtinId.split(":");
12850
13799
  const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "-");
12851
13800
  const forkName = `${safe(name2 ?? "schema")}-${safe(ver ?? "0")}.local.json`;
12852
- schemaAbs = resolve15(ctx.configDir ?? ctx.cwd, "schemas", forkName);
13801
+ schemaAbs = resolve16(ctx.configDir ?? ctx.cwd, "schemas", forkName);
12853
13802
  assertSchemaWriteWithin(ctx, schemaAbs);
12854
13803
  if (existsSync5(schemaAbs)) {
12855
13804
  throw new DocmetaError(
@@ -13016,7 +13965,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
13016
13965
  }
13017
13966
  return void 0;
13018
13967
  };
13019
- const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute6(raw) ? resolve15(configDir, raw) : raw;
13968
+ const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute8(raw) ? resolve16(configDir, raw) : raw;
13020
13969
  const wantSet = runRefs.map(rebaseRaw).sort().join("\n");
13021
13970
  const seqResolvesToRunSet = (node) => {
13022
13971
  if (!isSeq3(node)) return false;
@@ -13061,7 +14010,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
13061
14010
  if (!isMap5(item)) continue;
13062
14011
  const raw = rawRefOf(item);
13063
14012
  if (raw === void 0 || classifyRef(raw).kind !== "file") continue;
13064
- const abs = isAbsolute6(raw) ? raw : resolve15(configDir, raw);
14013
+ const abs = isAbsolute8(raw) ? raw : resolve16(configDir, raw);
13065
14014
  if (foldPath(abs) !== wantPath) continue;
13066
14015
  pinFrom = item.get("integrity");
13067
14016
  item.set("integrity", edit.integrity.value);
@@ -13282,7 +14231,7 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
13282
14231
  `"${file}": no writable format for that extension.`
13283
14232
  );
13284
14233
  }
13285
- if (existsSync5(resolve15(ctx.base, file))) {
14234
+ if (existsSync5(resolve16(ctx.base, file))) {
13286
14235
  throw new DocmetaError(
13287
14236
  `"${file}" already exists; INSERT creates new files only.`
13288
14237
  );
@@ -13301,7 +14250,7 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
13301
14250
  `"${from}" -> "${to}": a rename may not change the extension.`
13302
14251
  );
13303
14252
  }
13304
- if (existsSync5(resolve15(ctx.base, to))) {
14253
+ if (existsSync5(resolve16(ctx.base, to))) {
13305
14254
  throw new DocmetaError(
13306
14255
  `"${to}" already exists; the rename would overwrite it.`
13307
14256
  );
@@ -13330,13 +14279,13 @@ function validateNewPath(p, base) {
13330
14279
  const refuse = (why) => {
13331
14280
  throw new DocmetaError(`"${p}" is not a usable path: ${why}.`);
13332
14281
  };
13333
- if (isAbsolute6(p)) refuse("it is absolute");
14282
+ if (isAbsolute8(p)) refuse("it is absolute");
13334
14283
  if (p.split(/[\\/]/).some((segment) => segment === "..")) {
13335
14284
  refuse("it traverses upward");
13336
14285
  }
13337
- const abs = resolve15(base, p);
13338
- if (abs === resolve15(base)) refuse("it points at the corpus root, not a file");
13339
- if (!abs.startsWith(resolve15(base) + sep7)) refuse("it escapes the corpus");
14286
+ const abs = resolve16(base, p);
14287
+ if (abs === resolve16(base)) refuse("it points at the corpus root, not a file");
14288
+ if (!abs.startsWith(resolve16(base) + sep8)) refuse("it escapes the corpus");
13340
14289
  }
13341
14290
  function refuseExternalMetadataWrites(changes, entries, ctx) {
13342
14291
  const index = ctx.externalMetadata;
@@ -13365,7 +14314,7 @@ function refuseExternalMetadataWrites(changes, entries, ctx) {
13365
14314
  continue;
13366
14315
  }
13367
14316
  if ("renamed" in c) {
13368
- const entry = index.byPath.get(resolve15(ctx.base, c.file));
14317
+ const entry = index.byPath.get(resolve16(ctx.base, c.file));
13369
14318
  if (entry !== void 0 && entry.size > 0) {
13370
14319
  const manifest = [...entry.values()][0]?.file ?? "manifest";
13371
14320
  throw new DocmetaError(
@@ -13400,8 +14349,8 @@ function readableJoinValue(held, ctx) {
13400
14349
  return opened.ok ? opened.value : held;
13401
14350
  }
13402
14351
  var sealedWrites = /* @__PURE__ */ new WeakMap();
13403
- function writtenValue(change2, key, value) {
13404
- const sealed = sealedWrites.get(change2)?.get(key);
14352
+ function writtenValue(change, key, value) {
14353
+ const sealed = sealedWrites.get(change)?.get(key);
13405
14354
  return sealed === void 0 ? value : sealed;
13406
14355
  }
13407
14356
  async function sealMarkedWrites(changes, entries, ctx) {
@@ -13503,14 +14452,14 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
13503
14452
  const pendingWrites = [];
13504
14453
  const pendingRenames = [];
13505
14454
  for (const [label, ops] of grouped) {
13506
- const path = resolve15(ctx.base, label);
14455
+ const path = resolve16(ctx.base, label);
13507
14456
  if (ops.renamedTo !== void 0) {
13508
14457
  if (!existsSync5(path)) {
13509
14458
  throw new DocmetaError(
13510
14459
  `"${label}" is gone from disk since it was read; re-run the query.`
13511
14460
  );
13512
14461
  }
13513
- const toDisk = resolve15(ctx.base, ops.renamedTo);
14462
+ const toDisk = resolve16(ctx.base, ops.renamedTo);
13514
14463
  if (existsSync5(toDisk)) {
13515
14464
  throw new DocmetaError(
13516
14465
  `"${ops.renamedTo}" appeared on disk since the plan; re-run the query.`
@@ -13754,7 +14703,7 @@ function skipExpression(sql, from) {
13754
14703
 
13755
14704
  // src/meta/commands/fill.ts
13756
14705
  import { readFile as readFile12 } from "node:fs/promises";
13757
- import { resolve as resolve16, extname as extname7, join as join9 } from "node:path";
14706
+ import { resolve as resolve17, extname as extname7, join as join9 } from "node:path";
13758
14707
  import {
13759
14708
  completeValidatedJSON,
13760
14709
  makeProvider,
@@ -13769,7 +14718,7 @@ import {
13769
14718
  } from "@hawkeyexl/inference";
13770
14719
 
13771
14720
  // src/meta/commands/fill-prompt.ts
13772
- var FILL_PROMPT_VERSION = 3;
14721
+ var FILL_PROMPT_VERSION = 4;
13773
14722
  var DEFAULT_CHUNK_CHARS = 12e3;
13774
14723
  function splitBody(body, chunkChars) {
13775
14724
  if (body.length <= chunkChars) return [body];
@@ -13877,6 +14826,8 @@ function buildUserPrompt(params) {
13877
14826
  }
13878
14827
 
13879
14828
  // src/meta/commands/fill.ts
14829
+ var META_PROVENANCE_KEY = "meta-provenance";
14830
+ var PROVENANCE_KEYS = /* @__PURE__ */ new Set(["provenance", META_PROVENANCE_KEY]);
13880
14831
  var DEFAULT_THRESHOLD = 0.7;
13881
14832
  var DEFAULT_CONCURRENCY = 4;
13882
14833
  var DEFAULT_PROVIDER = "auto";
@@ -14157,7 +15108,7 @@ async function runFill(opts) {
14157
15108
  only
14158
15109
  );
14159
15110
  const managedSkips = proposed.filter((c) => managed.has(c.key)).map((c) => ({
14160
- field: `/${c.key}`,
15111
+ field: pointerOf(c.key),
14161
15112
  required: c.required,
14162
15113
  confidence: 0,
14163
15114
  reasoning: "managed by derive",
@@ -14181,6 +15132,7 @@ async function runFill(opts) {
14181
15132
  const inPlace = view.plain.flatMap((pointer) => {
14182
15133
  const key = topLevelKeyOf(pointer);
14183
15134
  if (key === void 0 || only !== void 0 && !only.has(key)) return [];
15135
+ if (PROVENANCE_KEYS.has(key)) return [];
14184
15136
  if (externalMetadata?.owners.has(key) === true) return [];
14185
15137
  if (candidates.some((c) => c.key === key)) return [];
14186
15138
  return [{ key, pointer, value: extracted.data[key] }];
@@ -14224,7 +15176,11 @@ async function runFill(opts) {
14224
15176
  sha256(content)
14225
15177
  ]);
14226
15178
  const hit = candidates.length > 0 ? cache?.get(cacheKey) : void 0;
14227
- const existing = redactForModel(extracted.data, [...view.marked, ...probeMarks]);
15179
+ const existing = Object.fromEntries(
15180
+ Object.entries(redactForModel(extracted.data, [...view.marked, ...probeMarks])).filter(
15181
+ ([key]) => !PROVENANCE_KEYS.has(key)
15182
+ )
15183
+ );
14228
15184
  let proposals = {};
14229
15185
  if (hit) {
14230
15186
  cachedCount++;
@@ -14377,7 +15333,8 @@ async function runFill(opts) {
14377
15333
  ...opts.includeContent ? { content } : {}
14378
15334
  };
14379
15335
  }
14380
- const finalMarks = writable.length > 0 ? await validator.markedPointers({ ...view.data, ...patchOf(writable) }, schemaSet) : /* @__PURE__ */ new Set();
15336
+ const plainPatch = patchOf(writable);
15337
+ const finalMarks = writable.length > 0 ? await validator.markedPointers({ ...view.data, ...plainPatch }, schemaSet) : /* @__PURE__ */ new Set();
14381
15338
  const patch = {};
14382
15339
  for (const f of writable) {
14383
15340
  const key = keyOf(f);
@@ -14403,6 +15360,23 @@ async function runFill(opts) {
14403
15360
  encrypted: true
14404
15361
  });
14405
15362
  }
15363
+ let metaProvenance;
15364
+ if (writable.length > 0) {
15365
+ const owner = externalMetadata?.owners.get(META_PROVENANCE_KEY)?.find((o) => members.includes(o.collection));
15366
+ const merged2 = owner === void 0 ? mergeMetaProvenance(extracted.data[META_PROVENANCE_KEY], identity.model, writable) : void 0;
15367
+ if (owner !== void 0) {
15368
+ metaProvenance = { written: false, skipReason: "manifest-owned", manifest: owner.file };
15369
+ } else if (merged2 === void 0 || await addsFindings(
15370
+ (data) => validator.validate(data, schemaSet, extracted.lineFor, extracted.colFor).then((errors) => settleFindings(errors, view, extracted)),
15371
+ { ...view.data, ...plainPatch },
15372
+ merged2.list
15373
+ )) {
15374
+ metaProvenance = { written: false, skipReason: "schema-mismatch" };
15375
+ } else {
15376
+ patch[META_PROVENANCE_KEY] = merged2.list;
15377
+ metaProvenance = { written: true, entry: merged2.entry };
15378
+ }
15379
+ }
14406
15380
  let next;
14407
15381
  try {
14408
15382
  next = extractor.apply(content, patch, {
@@ -14410,17 +15384,25 @@ async function runFill(opts) {
14410
15384
  elements
14411
15385
  });
14412
15386
  } catch (err) {
14413
- return errorResult(
14414
- label,
14415
- extractor.name,
14416
- errorMessage(err),
14417
- schemaSet,
14418
- fields
14419
- );
15387
+ const withoutEntry = metaProvenance?.written === true ? tryApply(extractor, content, withoutKey(patch, META_PROVENANCE_KEY), {
15388
+ filePath: label,
15389
+ elements
15390
+ }) : void 0;
15391
+ if (withoutEntry === void 0) {
15392
+ return errorResult(
15393
+ label,
15394
+ extractor.name,
15395
+ errorMessage(err),
15396
+ schemaSet,
15397
+ fields
15398
+ );
15399
+ }
15400
+ next = withoutEntry;
15401
+ metaProvenance = { written: false, skipReason: "unwritable" };
14420
15402
  }
14421
15403
  const changed = next !== content;
14422
15404
  if (changed && !dryRun && label !== "<stdin>") {
14423
- await writeFileAtomic(resolve16(base, label), next);
15405
+ await writeFileAtomic(resolve17(base, label), next);
14424
15406
  }
14425
15407
  return {
14426
15408
  file: label,
@@ -14428,7 +15410,8 @@ async function runFill(opts) {
14428
15410
  schemas: schemaSet,
14429
15411
  fields,
14430
15412
  changed,
14431
- ...opts.includeContent ? { content: next } : {}
15413
+ ...opts.includeContent ? { content: next } : {},
15414
+ ...metaProvenance === void 0 ? {} : { metaProvenance }
14432
15415
  };
14433
15416
  };
14434
15417
  const results = [];
@@ -14442,7 +15425,7 @@ async function runFill(opts) {
14442
15425
  );
14443
15426
  }
14444
15427
  const fileResults = await mapConcurrent(files, concurrency, async (file) => {
14445
- const content = await readFile12(resolve16(base, file), "utf8");
15428
+ const content = await readFile12(resolve17(base, file), "utf8");
14446
15429
  return processOne(file, content, extname7(file));
14447
15430
  });
14448
15431
  results.push(...fileResults);
@@ -14461,7 +15444,57 @@ function topKey(instancePath) {
14461
15444
  const seg = instancePath.slice(1).split("/")[0] ?? "";
14462
15445
  return seg.replace(/~1/g, "/").replace(/~0/g, "~");
14463
15446
  }
14464
- var keyOf = (f) => f.field.slice(1);
15447
+ var keyOf = (f) => topKey(f.field);
15448
+ function isPlainRecord(value) {
15449
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15450
+ }
15451
+ function mergeMetaProvenance(held, model, written) {
15452
+ if (held !== void 0 && !Array.isArray(held)) return void 0;
15453
+ const list = held === void 0 ? [] : [...held];
15454
+ const at2 = list.findIndex((e) => isPlainRecord(e) && e["generated-by"] === model);
15455
+ const prior = list[at2];
15456
+ const kept = isPlainRecord(prior) ? prior : {};
15457
+ const fields = Array.isArray(kept.fields) ? kept.fields.filter((p) => typeof p === "string") : [];
15458
+ const confidence = {};
15459
+ if (isPlainRecord(kept.confidence)) {
15460
+ for (const [k, v] of Object.entries(kept.confidence)) {
15461
+ if (typeof v === "number") confidence[k] = v;
15462
+ }
15463
+ }
15464
+ for (const f of written) {
15465
+ if (!fields.includes(f.field)) fields.push(f.field);
15466
+ confidence[f.field] = f.confidence;
15467
+ }
15468
+ const entry = { ...kept, "generated-by": model, fields, confidence };
15469
+ if (at2 === -1) list.push(entry);
15470
+ else list[at2] = entry;
15471
+ return { list, entry };
15472
+ }
15473
+ async function addsFindings(validate, without, list) {
15474
+ const identity = (e) => JSON.stringify([e.schema, e.instancePath, e.keyword, e.subject ?? null, e.message]);
15475
+ const before = /* @__PURE__ */ new Map();
15476
+ for (const e of await validate(without)) {
15477
+ const key = identity(e);
15478
+ before.set(key, (before.get(key) ?? 0) + 1);
15479
+ }
15480
+ for (const e of await validate({ ...without, [META_PROVENANCE_KEY]: list })) {
15481
+ const key = identity(e);
15482
+ const left = before.get(key) ?? 0;
15483
+ if (left === 0) return true;
15484
+ before.set(key, left - 1);
15485
+ }
15486
+ return false;
15487
+ }
15488
+ function withoutKey(patch, key) {
15489
+ return Object.fromEntries(Object.entries(patch).filter(([k]) => k !== key));
15490
+ }
15491
+ function tryApply(extractor, content, patch, options) {
15492
+ try {
15493
+ return extractor.apply?.(content, patch, options);
15494
+ } catch {
15495
+ return void 0;
15496
+ }
15497
+ }
14465
15498
  var IN_PLACE_REASONING = "The page held this value in plain text; its schema marks it x-manni-encrypt, so it was encrypted.";
14466
15499
  var CIPHERTEXT_IN_TEXT = /~[A-Za-z0-9_-]{82,}/g;
14467
15500
  function contentForModel(content, extractor, existing, held, supplied, options) {
@@ -14470,10 +15503,13 @@ function contentForModel(content, extractor, existing, held, supplied, options)
14470
15503
  if (!Object.hasOwn(held, key) || supplied(pointerOf(key))) continue;
14471
15504
  if (toJsonText(value) !== toJsonText(held[key])) patch[key] = value;
14472
15505
  }
15506
+ const deletions = [...PROVENANCE_KEYS].filter(
15507
+ (key) => Object.hasOwn(held, key) && !supplied(pointerOf(key))
15508
+ );
14473
15509
  let text2 = content;
14474
- if (Object.keys(patch).length > 0 && extractor.apply !== void 0) {
15510
+ if ((Object.keys(patch).length > 0 || deletions.length > 0) && extractor.apply !== void 0) {
14475
15511
  try {
14476
- text2 = extractor.apply(content, patch, options);
15512
+ text2 = extractor.apply(content, patch, { ...options, deletions });
14477
15513
  } catch {
14478
15514
  }
14479
15515
  }
@@ -14511,7 +15547,7 @@ function collectCandidates(schemas, data, errors, only) {
14511
15547
  for (const [key, raw] of Object.entries(
14512
15548
  properties
14513
15549
  )) {
14514
- if (key === FILE_SCHEMA_KEY) continue;
15550
+ if (key === FILE_SCHEMA_KEY || PROVENANCE_KEYS.has(key)) continue;
14515
15551
  if (only && !only.has(key)) continue;
14516
15552
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) continue;
14517
15553
  const present = Object.prototype.hasOwnProperty.call(data, key);
@@ -14677,7 +15713,7 @@ function gate(candidates, proposals, threshold) {
14677
15713
  const proposal = proposals[c.key];
14678
15714
  if (proposal == null) {
14679
15715
  return {
14680
- field: `/${c.key}`,
15716
+ field: pointerOf(c.key),
14681
15717
  required: c.required,
14682
15718
  confidence: 0,
14683
15719
  reasoning: "",
@@ -14687,7 +15723,7 @@ function gate(candidates, proposals, threshold) {
14687
15723
  }
14688
15724
  const passes = proposal.confidence >= threshold;
14689
15725
  return {
14690
- field: `/${c.key}`,
15726
+ field: pointerOf(c.key),
14691
15727
  required: c.required,
14692
15728
  confidence: proposal.confidence,
14693
15729
  reasoning: proposal.reasoning,
@@ -14786,10 +15822,434 @@ async function mapConcurrent(items2, limit, fn) {
14786
15822
 
14787
15823
  // src/meta/commands/derive.ts
14788
15824
  import { readFile as readFile13 } from "node:fs/promises";
14789
- import { extname as extname8, resolve as resolve17 } from "node:path";
15825
+ import { extname as extname8, relative as relative13, resolve as resolve18, sep as sep9 } from "node:path";
15826
+
15827
+ // src/meta/core/external-metadata-write.ts
15828
+ import { posix as posix3 } from "node:path";
15829
+ import {
15830
+ LineCounter as LineCounter3,
15831
+ isMap as isMap6,
15832
+ isNode as isNode2,
15833
+ isPair,
15834
+ isScalar as isScalar4,
15835
+ isSeq as isSeq4,
15836
+ parseDocument as parseDocument7,
15837
+ stringify as stringify3
15838
+ } from "yaml";
15839
+ function spliceManifestValue(text2, options) {
15840
+ const { entry, key, value, file } = options;
15841
+ const join10 = options.join ?? PATH_JOIN;
15842
+ const at2 = (line2) => {
15843
+ const name2 = file === void 0 ? "Manifest" : `Manifest ${file}`;
15844
+ if (line2 === void 0) return name2;
15845
+ return file === void 0 ? `${name2} line ${String(line2)}` : `${name2}:${String(line2)}`;
15846
+ };
15847
+ if (key === FILE_SCHEMA_KEY) {
15848
+ throw new DocmetaError(
15849
+ `${at2()}: refusing to write "${FILE_SCHEMA_KEY}" for "${entry}"; a manifest never chooses the schema a document is judged by. Use "overrides" in the config.`
15850
+ );
15851
+ }
15852
+ if (value === void 0) {
15853
+ throw new DocmetaError(`${at2()}: refusing to write "${key}" for "${entry}" with no value.`);
15854
+ }
15855
+ const eol = detectEol(text2);
15856
+ const lc = new LineCounter3();
15857
+ const doc = parseDocument7(text2, { lineCounter: lc, uniqueKeys: false });
15858
+ const problem = doc.errors[0];
15859
+ if (problem) {
15860
+ throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
15861
+ }
15862
+ const keyLine = (node) => {
15863
+ const r = rangeOf(node);
15864
+ return r ? lc.linePos(r[0]).line : void 0;
15865
+ };
15866
+ const same = join10 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
15867
+ const root = doc.contents;
15868
+ let spelled = entry;
15869
+ let edit;
15870
+ if (root === null || isScalar4(root) && root.value === null && emptySource(text2, root)) {
15871
+ edit = startManifest(text2, entry, key, value, eol);
15872
+ } else if (!isMap6(root)) {
15873
+ throw new DocmetaError(
15874
+ `${at2()}: the manifest must be a mapping from document ${join10 === PATH_JOIN ? "path" : `"${join10}"`} to owned keys.`
15875
+ );
15876
+ } else {
15877
+ const style = detectStyle(text2, root, eol);
15878
+ const matches = root.items.filter((p) => same(keyString(p.key), entry));
15879
+ const [first, second] = matches;
15880
+ if (first && second) {
15881
+ throw new DocmetaError(
15882
+ `${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
15883
+ );
15884
+ }
15885
+ if (first === void 0) {
15886
+ if (root.flow === true && root.items.length > 0) {
15887
+ throw new DocmetaError(
15888
+ `${at2()}: the manifest is a flow mapping ({ \u2026 }), which this writer does not add entries to. Rewrite it in block style.`
15889
+ );
15890
+ }
15891
+ edit = appendEntry(text2, root, entry, key, value, style);
15892
+ } else {
15893
+ spelled = keyString(first.key);
15894
+ const where = at2(keyLine(first.key));
15895
+ const v = first.value;
15896
+ if (!isMap6(v)) {
15897
+ throw new DocmetaError(`${where}: "${spelled}" must be a mapping of owned keys to values.`);
15898
+ }
15899
+ const hits = v.items.filter((kv) => keyString(kv.key) === key);
15900
+ const [hit, again] = hits;
15901
+ if (hit && again) {
15902
+ throw new DocmetaError(
15903
+ `${at2(keyLine(again.key))}: "${spelled}" sets "${key}" twice (first at line ${String(keyLine(hit.key) ?? "?")}). Remove one.`
15904
+ );
15905
+ }
15906
+ const flowRefusal = () => new DocmetaError(
15907
+ `${where}: "${spelled}" is a flow mapping ({ \u2026 }), which this writer does not add keys to. Rewrite the entry in block style.`
15908
+ );
15909
+ const formRefusal = () => new DocmetaError(
15910
+ `${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
15911
+ );
15912
+ let made;
15913
+ if (v.flow === true) {
15914
+ if (hit) made = replaceFlowValue(text2, hit, value);
15915
+ else if (v.items.length === 0) made = fillEmptyEntry(text2, first, key, value, style);
15916
+ else throw flowRefusal();
15917
+ if (!made) throw hit ? flowRefusal() : formRefusal();
15918
+ } else {
15919
+ made = hit ? replaceBlockValue(text2, hit, value, style) : insertKey(text2, v, key, value, style);
15920
+ if (!made) throw formRefusal();
15921
+ }
15922
+ edit = made;
15923
+ }
15924
+ }
15925
+ const out = text2.slice(0, edit.start) + edit.insert + text2.slice(edit.end);
15926
+ const line = readBack(
15927
+ doc,
15928
+ out,
15929
+ spelled,
15930
+ key,
15931
+ value,
15932
+ (why) => new DocmetaError(
15933
+ `${at2()}: writing "${key}" for "${entry}" did not read back as written, so nothing was written. ${why}`
15934
+ )
15935
+ );
15936
+ return { text: out, line };
15937
+ }
15938
+ function removeManifestKey(text2, options) {
15939
+ const { entry, key, file } = options;
15940
+ const join10 = options.join ?? PATH_JOIN;
15941
+ const at2 = (line) => {
15942
+ const name2 = file === void 0 ? "Manifest" : `Manifest ${file}`;
15943
+ if (line === void 0) return name2;
15944
+ return file === void 0 ? `${name2} line ${String(line)}` : `${name2}:${String(line)}`;
15945
+ };
15946
+ const eol = detectEol(text2);
15947
+ const lc = new LineCounter3();
15948
+ const doc = parseDocument7(text2, { lineCounter: lc, uniqueKeys: false });
15949
+ const problem = doc.errors[0];
15950
+ if (problem) {
15951
+ throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
15952
+ }
15953
+ const keyLine = (node) => {
15954
+ const r = rangeOf(node);
15955
+ return r ? lc.linePos(r[0]).line : void 0;
15956
+ };
15957
+ const root = doc.contents;
15958
+ if (root === null || isScalar4(root) && root.value === null) return { text: text2 };
15959
+ if (!isMap6(root)) {
15960
+ throw new DocmetaError(
15961
+ `${at2()}: the manifest must be a mapping from document ${join10 === PATH_JOIN ? "path" : `"${join10}"`} to owned keys.`
15962
+ );
15963
+ }
15964
+ const same = join10 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
15965
+ const matches = root.items.filter((p) => same(keyString(p.key), entry));
15966
+ const [first, second] = matches;
15967
+ if (first && second) {
15968
+ throw new DocmetaError(
15969
+ `${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
15970
+ );
15971
+ }
15972
+ if (first === void 0) return { text: text2 };
15973
+ const spelled = keyString(first.key);
15974
+ const where = at2(keyLine(first.key));
15975
+ const v = first.value;
15976
+ if (!isMap6(v)) {
15977
+ throw new DocmetaError(`${where}: "${spelled}" must be a mapping of owned keys to values.`);
15978
+ }
15979
+ const hits = v.items.filter((kv) => keyString(kv.key) === key);
15980
+ const [hit, again] = hits;
15981
+ if (hit && again) {
15982
+ throw new DocmetaError(
15983
+ `${at2(keyLine(again.key))}: "${spelled}" sets "${key}" twice (first at line ${String(keyLine(hit.key) ?? "?")}). Remove one.`
15984
+ );
15985
+ }
15986
+ if (hit === void 0) return { text: text2 };
15987
+ const whole = v.items.length === 1;
15988
+ if (!whole && v.flow === true) {
15989
+ throw new DocmetaError(
15990
+ `${where}: "${spelled}" is a flow mapping ({ \u2026 }), which this writer does not remove keys from. Rewrite the entry in block style.`
15991
+ );
15992
+ }
15993
+ const from = rangeOf(whole ? first.key : hit.key);
15994
+ const to = whole ? contentEnd(text2, v) : contentEnd(text2, hit.value) ?? contentEnd(text2, hit.key);
15995
+ if (!from || to === void 0) {
15996
+ throw new DocmetaError(
15997
+ `${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
15998
+ );
15999
+ }
16000
+ let start = text2.lastIndexOf("\n", from[0] - 1) + 1;
16001
+ let end = lineEnd(text2, to);
16002
+ if (text2.startsWith(eol, end)) end += eol.length;
16003
+ else if (start >= eol.length) start -= eol.length;
16004
+ if (whole) {
16005
+ if (text2.slice(start - 2 * eol.length, start) === eol + eol) start -= eol.length;
16006
+ else if (start === 0 && text2.startsWith(eol, end)) end += eol.length;
16007
+ }
16008
+ const out = text2.slice(0, start) + text2.slice(end);
16009
+ const after = parseDocument7(out, { uniqueKeys: false });
16010
+ const fail2 = (why) => new DocmetaError(
16011
+ `${at2()}: removing "${key}" for "${entry}" did not read back as removed, so nothing was written. ${why}`
16012
+ );
16013
+ if (after.errors[0]) throw fail2(`The result is not valid YAML: ${after.errors[0].message}`);
16014
+ const was = doc.toJS({ maxAliasCount: 100 });
16015
+ const wasRoot = isRecord5(was) ? was : {};
16016
+ const wasEntry = wasRoot[spelled];
16017
+ const rest = Object.fromEntries(
16018
+ Object.entries(isRecord5(wasEntry) ? wasEntry : {}).filter(([k]) => k !== key)
16019
+ );
16020
+ const kept = Object.entries(wasRoot).filter(([k]) => k !== spelled);
16021
+ if (Object.keys(rest).length > 0) kept.push([spelled, rest]);
16022
+ const now = after.toJS({ maxAliasCount: 100 });
16023
+ const expected = kept.length === 0 ? null : Object.fromEntries(kept);
16024
+ if (!deepEqual(now ?? null, expected)) throw fail2("Another value in the manifest would have changed.");
16025
+ return { text: out };
16026
+ }
16027
+ function startManifest(text2, entry, key, value, eol) {
16028
+ const style = { step: 2, indentSeq: true, eol };
16029
+ const block = blockLines({ [entry]: { [key]: value } }, 0, style);
16030
+ let insert;
16031
+ if (text2 === "" || text2.endsWith("\n")) insert = block + eol;
16032
+ else insert = eol + block;
16033
+ return { start: text2.length, end: text2.length, insert };
16034
+ }
16035
+ function appendEntry(text2, root, entry, key, value, style) {
16036
+ const body = { [entry]: { [key]: value } };
16037
+ if (root.flow === true) {
16038
+ const r = rangeOf(root);
16039
+ const start = r ? r[0] : text2.length;
16040
+ const end = contentEnd(text2, root) ?? start;
16041
+ return { start, end, insert: blockLines(body, colOf(text2, start), style) };
16042
+ }
16043
+ const firstKey = rangeOf(root.items[0]?.key);
16044
+ const col = firstKey ? colOf(text2, firstKey[0]) : 0;
16045
+ const pos = lineEnd(text2, contentEnd(text2, root) ?? text2.length);
16046
+ const last = root.items.at(-1);
16047
+ const spaced = root.items.length > 1 && isNode2(last?.key) && last.key.spaceBefore === true;
16048
+ return {
16049
+ start: pos,
16050
+ end: pos,
16051
+ insert: style.eol + (spaced ? style.eol : "") + blockLines(body, col, style)
16052
+ };
16053
+ }
16054
+ function insertKey(text2, entryMap, key, value, style) {
16055
+ const firstKey = rangeOf(entryMap.items[0]?.key);
16056
+ const end = contentEnd(text2, entryMap);
16057
+ if (!firstKey || end === void 0) return void 0;
16058
+ const pos = lineEnd(text2, end);
16059
+ return {
16060
+ start: pos,
16061
+ end: pos,
16062
+ insert: style.eol + blockLines({ [key]: value }, colOf(text2, firstKey[0]), style)
16063
+ };
16064
+ }
16065
+ function fillEmptyEntry(text2, entryPair, key, value, style) {
16066
+ const start = colonEnd(text2, entryPair);
16067
+ const keyRange = rangeOf(entryPair.key);
16068
+ const end = contentEnd(text2, entryPair.value);
16069
+ if (start === void 0 || !keyRange || end === void 0) return void 0;
16070
+ const col = colOf(text2, keyRange[0]) + style.step;
16071
+ return { start, end, insert: style.eol + blockLines({ [key]: value }, col, style) };
16072
+ }
16073
+ function replaceBlockValue(text2, pair, value, style) {
16074
+ const colon = colonEnd(text2, pair);
16075
+ const keyRange = rangeOf(pair.key);
16076
+ if (colon === void 0 || !keyRange) return void 0;
16077
+ const keyCol = colOf(text2, keyRange[0]);
16078
+ let start = colon;
16079
+ let ownLine = false;
16080
+ let i = colon;
16081
+ while (text2.charAt(i) === " " || text2.charAt(i) === " ") i++;
16082
+ if (text2.charAt(i) === "#") {
16083
+ start = lineEnd(text2, i);
16084
+ ownLine = true;
16085
+ }
16086
+ const old = pair.value;
16087
+ const oldEnd = isScalar4(old) && old.value === null && emptySource(text2, old) ? start : contentEnd(text2, old) ?? start;
16088
+ const end = Math.max(start, oldEnd);
16089
+ let insert = afterColon(value, keyCol, style);
16090
+ if (ownLine && !insert.startsWith(style.eol)) {
16091
+ insert = style.eol + " ".repeat(keyCol + style.step) + insert.trimStart();
16092
+ }
16093
+ return { start, end, insert };
16094
+ }
16095
+ function replaceFlowValue(text2, pair, value) {
16096
+ const r = rangeOf(pair.value);
16097
+ if (!r || emptySource(text2, pair.value)) return void 0;
16098
+ const end = contentEnd(text2, pair.value) ?? r[1];
16099
+ return { start: r[0], end, insert: flowText(value) };
16100
+ }
16101
+ function readBack(before, out, spelled, key, value, fail2) {
16102
+ const lc = new LineCounter3();
16103
+ const after = parseDocument7(out, { lineCounter: lc, uniqueKeys: false });
16104
+ const problem = after.errors[0];
16105
+ if (problem) throw fail2(`The result is not valid YAML: ${problem.message}`);
16106
+ const was = before.toJS({ maxAliasCount: 100 });
16107
+ const now = after.toJS({ maxAliasCount: 100 });
16108
+ const wasRoot = isRecord5(was) ? was : {};
16109
+ const wasEntry = wasRoot[spelled];
16110
+ const expected = {
16111
+ ...wasRoot,
16112
+ [spelled]: { ...isRecord5(wasEntry) ? wasEntry : {}, [key]: value }
16113
+ };
16114
+ const nowEntry = isRecord5(now) ? now[spelled] : void 0;
16115
+ if (!isRecord5(nowEntry) || !deepEqual(nowEntry[key], value)) {
16116
+ throw fail2(
16117
+ "The value reads back differently. A Date, an undefined member, or another value YAML cannot hold does not survive the trip."
16118
+ );
16119
+ }
16120
+ if (!deepEqual(now, expected)) {
16121
+ throw fail2("Another value in the manifest would have changed.");
16122
+ }
16123
+ const root = after.contents;
16124
+ const pair = isMap6(root) ? root.items.find((p) => keyString(p.key) === spelled) : void 0;
16125
+ const entryMap = pair?.value;
16126
+ const kv = isMap6(entryMap) ? entryMap.items.find((p) => keyString(p.key) === key) : void 0;
16127
+ const r = rangeOf(kv?.value) ?? rangeOf(kv?.key);
16128
+ if (!r) throw fail2("The written value could not be found again.");
16129
+ return lc.linePos(r[0]).line;
16130
+ }
16131
+ function blockLines(pair, col, style) {
16132
+ const s = stringify3(pair, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
16133
+ return indentLines(s.replace(/\n$/, "").split("\n"), col, 0).join(style.eol);
16134
+ }
16135
+ function afterColon(value, col, style) {
16136
+ const s = stringify3({ k: value }, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
16137
+ const lines = s.replace(/\n$/, "").slice("k:".length).split("\n");
16138
+ return indentLines(lines, col, 1).join(style.eol);
16139
+ }
16140
+ function indentLines(lines, col, from) {
16141
+ const pad2 = " ".repeat(col);
16142
+ return lines.map((l, i) => i < from || l === "" ? l : pad2 + l);
16143
+ }
16144
+ function flowText(value) {
16145
+ const s = stringify3(value, { lineWidth: 0, collectionStyle: "flow" }).replace(/\n$/, "");
16146
+ return s.includes("\n") ? JSON.stringify(value) : s;
16147
+ }
16148
+ function detectEol(text2) {
16149
+ const i = text2.indexOf("\n");
16150
+ return i > 0 && text2.charAt(i - 1) === "\r" ? "\r\n" : "\n";
16151
+ }
16152
+ function detectStyle(text2, root, eol) {
16153
+ let step = 2;
16154
+ for (const pair of root.items) {
16155
+ const v = pair.value;
16156
+ const entryKey = rangeOf(pair.key);
16157
+ const ownedKey = isMap6(v) && v.flow !== true ? rangeOf(v.items[0]?.key) : void 0;
16158
+ if (entryKey && ownedKey) {
16159
+ const d = colOf(text2, ownedKey[0]) - colOf(text2, entryKey[0]);
16160
+ if (d > 0) {
16161
+ step = d;
16162
+ break;
16163
+ }
16164
+ }
16165
+ }
16166
+ return { step, indentSeq: seqIndented(text2, root) ?? true, eol };
16167
+ }
16168
+ function seqIndented(text2, node) {
16169
+ if (isMap6(node)) {
16170
+ for (const pair of node.items) {
16171
+ const v = pair.value;
16172
+ if (isSeq4(v) && v.flow !== true && v.items.length > 0) {
16173
+ const k = rangeOf(pair.key);
16174
+ const s = rangeOf(v);
16175
+ if (k && s) return colOf(text2, s[0]) > colOf(text2, k[0]);
16176
+ }
16177
+ const inner = seqIndented(text2, v);
16178
+ if (inner !== void 0) return inner;
16179
+ }
16180
+ } else if (isSeq4(node)) {
16181
+ for (const item of node.items) {
16182
+ const inner = seqIndented(text2, item);
16183
+ if (inner !== void 0) return inner;
16184
+ }
16185
+ }
16186
+ return void 0;
16187
+ }
16188
+ function contentEnd(text2, node) {
16189
+ if (isMap6(node) && node.flow !== true) {
16190
+ const last = node.items.at(-1);
16191
+ if (last) return contentEnd(text2, last.value) ?? contentEnd(text2, last.key);
16192
+ } else if (isSeq4(node) && node.flow !== true) {
16193
+ const last = node.items.at(-1);
16194
+ if (last !== void 0) {
16195
+ return isPair(last) ? contentEnd(text2, last.value) ?? contentEnd(text2, last.key) : contentEnd(text2, last);
16196
+ }
16197
+ }
16198
+ const r = rangeOf(node);
16199
+ if (!r) return void 0;
16200
+ let end = r[1];
16201
+ while (end > r[0] && /\s/.test(text2.charAt(end - 1))) end--;
16202
+ return end;
16203
+ }
16204
+ function colonEnd(text2, pair) {
16205
+ const r = rangeOf(pair.key);
16206
+ if (!r) return void 0;
16207
+ let i = r[1];
16208
+ while (text2.charAt(i) === " " || text2.charAt(i) === " ") i++;
16209
+ return text2.charAt(i) === ":" ? i + 1 : void 0;
16210
+ }
16211
+ function lineEnd(text2, i) {
16212
+ const j = text2.indexOf("\n", i);
16213
+ if (j < 0) return text2.length;
16214
+ return j > 0 && text2.charAt(j - 1) === "\r" ? j - 1 : j;
16215
+ }
16216
+ function colOf(text2, offset) {
16217
+ if (offset === 0) return 0;
16218
+ let start = text2.lastIndexOf("\n", offset - 1) + 1;
16219
+ if (start === 0 && text2.startsWith("\uFEFF")) start = 1;
16220
+ return offset - start;
16221
+ }
16222
+ function rangeOf(node) {
16223
+ return isNode2(node) ? node.range ?? void 0 : void 0;
16224
+ }
16225
+ function emptySource(text2, node) {
16226
+ const r = rangeOf(node);
16227
+ return !r || text2.slice(r[0], r[1]).trim() === "";
16228
+ }
16229
+ function keyString(key) {
16230
+ return isScalar4(key) ? String(key.value) : String(key);
16231
+ }
16232
+ function isRecord5(x) {
16233
+ return typeof x === "object" && x !== null && !Array.isArray(x);
16234
+ }
16235
+
16236
+ // src/meta/commands/derive.ts
16237
+ var GENERATED_BY_ENV = "MANNI_GENERATED_BY";
14790
16238
  var SOURCE_HINT = "narrow --sources or --fields";
14791
16239
  async function runDerive(opts) {
14792
16240
  const cwd = opts.cwd ?? process.cwd();
16241
+ const targets = [];
16242
+ const typedInputs = opts.inputs.map((arg) => {
16243
+ if (arg === STDIN_TOKEN) return arg;
16244
+ const { page, lines } = parseProvenanceTarget(arg);
16245
+ if (lines === void 0) return arg;
16246
+ if (page === STDIN_TOKEN) return STDIN_TOKEN;
16247
+ targets.push({ target: arg, page });
16248
+ return page;
16249
+ });
16250
+ const env = opts.env ?? process.env;
16251
+ const flagGeneratedBy = machineName(opts.generatedBy);
16252
+ const generatedBy = opts.generatedBy !== void 0 ? flagGeneratedBy : machineName(env[GENERATED_BY_ENV]);
14793
16253
  const {
14794
16254
  config,
14795
16255
  inputs,
@@ -14803,7 +16263,7 @@ async function runDerive(opts) {
14803
16263
  cwd,
14804
16264
  configPath: opts.configPath,
14805
16265
  noConfig: opts.noConfig,
14806
- inputs: opts.inputs,
16266
+ inputs: typedInputs,
14807
16267
  ...opts.collections !== void 0 ? { collections: opts.collections } : {},
14808
16268
  onConfigLoaded: opts.onConfigLoaded
14809
16269
  });
@@ -14817,7 +16277,20 @@ async function runDerive(opts) {
14817
16277
  }
14818
16278
  const commands = commandsOf(config?.derive);
14819
16279
  const fields = resolveFields(opts.fields, config?.derive?.fields, commands);
16280
+ const wantsProvenance = fields.includes(PROVENANCE_FIELD);
16281
+ const firstTarget = targets[0];
16282
+ if (firstTarget !== void 0 && generatedBy === void 0) {
16283
+ throw new DocmetaError(
16284
+ `${firstTarget.target} names lines, which only --generated-by uses. Pass --generated-by, or drop the range.`
16285
+ );
16286
+ }
16287
+ if ((flagGeneratedBy !== void 0 || firstTarget !== void 0) && !wantsProvenance) {
16288
+ throw new DocmetaError(
16289
+ "--generated-by attributes provenance, which is not in --fields. Add provenance, or drop --generated-by."
16290
+ );
16291
+ }
14820
16292
  for (const field of fields) {
16293
+ if (field === PROVENANCE_FIELD) continue;
14821
16294
  const owner = manifestOwning(field, declaredCollections);
14822
16295
  if (owner !== void 0) {
14823
16296
  throw new DocmetaError(
@@ -14880,6 +16353,18 @@ async function runDerive(opts) {
14880
16353
  })
14881
16354
  });
14882
16355
  const files = fromCollections ? retainMembers(walked, membersFor) : walked;
16356
+ const rangesByPage = /* @__PURE__ */ new Map();
16357
+ for (const t of targets) {
16358
+ const label = relative13(base, resolve18(base, t.page)).split(sep9).join("/");
16359
+ if (!files.includes(label)) {
16360
+ throw new DocmetaError(
16361
+ `${t.target} does not name one file that derive reads; a range names lines of one file.`
16362
+ );
16363
+ }
16364
+ const named = rangesByPage.get(label) ?? [];
16365
+ if (!named.includes(t.target)) named.push(t.target);
16366
+ rangesByPage.set(label, named);
16367
+ }
14883
16368
  assertNonEmpty({
14884
16369
  files,
14885
16370
  inputs,
@@ -14893,7 +16378,7 @@ async function runDerive(opts) {
14893
16378
  const loaded = /* @__PURE__ */ new Map();
14894
16379
  const errors = /* @__PURE__ */ new Map();
14895
16380
  for (const label of files) {
14896
- const absPath = resolve17(base, label);
16381
+ const absPath = resolve18(base, label);
14897
16382
  const extractor = forcedExtractor ?? extractorForExtension(extname8(label));
14898
16383
  if (!extractor) {
14899
16384
  errors.set(label, {
@@ -14918,6 +16403,38 @@ async function runDerive(opts) {
14918
16403
  errors.set(label, { format: extractor.name, message: errorMessage(err) });
14919
16404
  }
14920
16405
  }
16406
+ const provenanceRoot = configDir ?? cwd;
16407
+ const manifests = wantsProvenance ? provenanceManifests(declaredCollections, provenanceRoot, base) : [];
16408
+ const provenanceCollections = declaredCollections.map((c) => ({
16409
+ ...c,
16410
+ externalMetadata: c.externalMetadata.filter((m) => m.keys.includes(PROVENANCE_FIELD))
16411
+ })).filter((c) => c.externalMetadata.length > 0);
16412
+ const provenanceIndex = manifests.length === 0 ? null : await loadExternalMetadata(provenanceCollections, { configDir: provenanceRoot, base });
16413
+ const places = /* @__PURE__ */ new Map();
16414
+ for (const doc of loaded.values()) {
16415
+ if (!wantsProvenance) break;
16416
+ const place = provenancePlace(
16417
+ doc.label,
16418
+ doc.extracted.data,
16419
+ manifests,
16420
+ declaredCollections,
16421
+ provenanceRoot,
16422
+ base
16423
+ );
16424
+ if (place !== void 0) {
16425
+ places.set(doc.label, place);
16426
+ doc.provenanceManifest = { absPath: place.absPath, entry: place.entry, join: place.join };
16427
+ }
16428
+ const named = rangesByPage.get(doc.label);
16429
+ if (named !== void 0 && generatedBy !== void 0) {
16430
+ doc.attribution = { targets: named, generatedBy };
16431
+ }
16432
+ }
16433
+ const stampOf = (doc) => {
16434
+ if (!places.has(doc.label)) return doc.extracted.data[PROVENANCE_FIELD];
16435
+ const members = memberOf(provenanceCollections, provenanceRoot, base, doc.label);
16436
+ return mergeExternalMetadata(doc.label, doc.extracted, provenanceIndex, members, base).extracted.data[PROVENANCE_FIELD];
16437
+ };
14921
16438
  const derived = await deriveMetadata([...loaded.values()], {
14922
16439
  cwd,
14923
16440
  base,
@@ -14928,7 +16445,9 @@ async function runDerive(opts) {
14928
16445
  commands,
14929
16446
  cache: opts.cache ?? true,
14930
16447
  now: opts.now ?? (() => /* @__PURE__ */ new Date()),
14931
- reviews: opts.reviews
16448
+ reviews: opts.reviews,
16449
+ machines: machinesOf(config?.derive),
16450
+ ...generatedBy !== void 0 ? { generatedBy } : {}
14932
16451
  });
14933
16452
  assertSourcesAvailable(derived.sources, SOURCE_HINT);
14934
16453
  for (const [name2, status] of Object.entries(derived.sources)) {
@@ -14997,6 +16516,20 @@ async function runDerive(opts) {
14997
16516
  }
14998
16517
  return holder[field];
14999
16518
  };
16519
+ const heldManifests = /* @__PURE__ */ new Map();
16520
+ const holdManifest = async (manifest) => {
16521
+ const already = heldManifests.get(manifest.absPath);
16522
+ if (already !== void 0) return already;
16523
+ let before;
16524
+ try {
16525
+ before = await readFile13(manifest.absPath, "utf8");
16526
+ } catch (err) {
16527
+ throw new DocmetaError(`Manifest ${manifest.file} could not be read: ${errorMessage(err)}`);
16528
+ }
16529
+ const held = { path: manifest.absPath, before, text: before };
16530
+ heldManifests.set(manifest.absPath, held);
16531
+ return held;
16532
+ };
15000
16533
  const results = [];
15001
16534
  for (const label of files) {
15002
16535
  const failure = errors.get(label);
@@ -15016,12 +16549,49 @@ async function runDerive(opts) {
15016
16549
  const at2 = pointerOf(field);
15017
16550
  return marks.marked.some((p) => isAtOrUnder(p, at2));
15018
16551
  };
16552
+ const place = places.get(label);
16553
+ const provenance = wantsProvenance ? judgeProvenance(stampOf(doc), derived.provenance?.get(label), record?.fields[PROVENANCE_FIELD] ?? null, place) : void 0;
15019
16554
  const compared = fields.map(
15020
- (field) => compareDerived(field, marks.data[field], record?.fields[field])
16555
+ (field) => field === PROVENANCE_FIELD && provenance !== void 0 ? provenance.field : compareDerived(field, marks.data[field], record?.fields[field])
15021
16556
  );
15022
- const shown = () => compared.map((f) => isMarked(f.field) ? redactedDerived(f) : f);
15023
- const pending = compared.filter((f) => f.status === "stale" || f.status === "unset");
16557
+ const shown = () => compared.map(
16558
+ (f) => f.field !== PROVENANCE_FIELD && isMarked(f.field) ? redactedDerived(f) : f
16559
+ );
16560
+ const findingsOf = (reported2) => reported2.flatMap(
16561
+ (f) => f.field === PROVENANCE_FIELD && provenance?.judged !== void 0 ? provenanceFindings(provenance.judged.comparisons, provenance.judged.derivation.page.bodyLine) : staleFindings([f], doc.extracted.lineFor)
16562
+ );
16563
+ if (provenance?.judged !== void 0 && flagGeneratedBy !== void 0 && doc.attribution === void 0 && ![...provenance.judged.derivation.evidenceByLine.values()].some((e) => e.rule === 1)) {
16564
+ opts.onNotice?.(
16565
+ `${label}: no uncommitted body lines; --generated-by attributes only what is not yet committed.`
16566
+ );
16567
+ }
16568
+ const stale = (f) => f.status === "stale" || f.status === "unset";
16569
+ const toManifest = provenance !== void 0 && place !== void 0 && stale(provenance.field) ? provenance : void 0;
16570
+ const pending = compared.filter((f) => stale(f) && !(f.field === PROVENANCE_FIELD && toManifest !== void 0));
16571
+ const onPage = pending.find((f) => f.field === PROVENANCE_FIELD);
16572
+ if (onPage !== void 0 && !provenanceFenced(doc.extracted)) {
16573
+ results.push(
16574
+ errorResult2(
16575
+ label,
16576
+ doc.format,
16577
+ `provenance cannot be stamped into the page: in the "${doc.format}" format the metadata is part of the body it pins. Keep provenance in an externalMetadata manifest.`,
16578
+ check,
16579
+ shown()
16580
+ )
16581
+ );
16582
+ continue;
16583
+ }
15024
16584
  let changed = false;
16585
+ if (toManifest?.judged !== void 0 && place !== void 0) {
16586
+ const held = await holdManifest(place.manifest);
16587
+ const before = held.text;
16588
+ const where = { entry: place.entry, key: PROVENANCE_FIELD, join: place.join, file: place.manifest.file };
16589
+ held.text = toManifest.judged.plan.length === 0 ? removeManifestKey(held.text, where).text : spliceManifestValue(held.text, { ...where, value: toManifest.judged.plan }).text;
16590
+ if (held.text !== before) {
16591
+ changed = true;
16592
+ if (!dryRun) markWritten(toManifest.field);
16593
+ }
16594
+ }
15025
16595
  if (pending.length > 0) {
15026
16596
  if (typeof doc.apply !== "function") {
15027
16597
  if (!dryRun) {
@@ -15039,20 +16609,37 @@ async function runDerive(opts) {
15039
16609
  changed = true;
15040
16610
  } else {
15041
16611
  const patch = {};
16612
+ const deletions = [];
15042
16613
  for (const f of pending) {
16614
+ if (f.field === PROVENANCE_FIELD) {
16615
+ if (Array.isArray(f.derived) && f.derived.length === 0) deletions.push(f.field);
16616
+ else patch[f.field] = f.derived;
16617
+ continue;
16618
+ }
15043
16619
  patch[f.field] = isMarked(f.field) ? await sealField(f.field, f.derived, marks.marked) : f.derived;
15044
16620
  }
16621
+ const apply = doc.apply;
16622
+ const write = (value) => apply(doc.content, value, {
16623
+ filePath: label,
16624
+ elements: doc.elements,
16625
+ ...deletions.length > 0 ? { deletions } : {}
16626
+ });
15045
16627
  let next;
15046
16628
  try {
15047
- next = doc.apply(doc.content, patch, { filePath: label, elements: doc.elements });
16629
+ next = write(patch);
16630
+ const plan = patch[PROVENANCE_FIELD];
16631
+ const shift = bodyShift(doc.content, next);
16632
+ if (shift > 0 && Array.isArray(plan)) {
16633
+ next = write({ ...patch, [PROVENANCE_FIELD]: shiftEntries(provenanceEntries(plan), shift) });
16634
+ }
15048
16635
  } catch (err) {
15049
16636
  results.push(errorResult2(label, doc.format, errorMessage(err), check, shown()));
15050
16637
  continue;
15051
16638
  }
15052
- changed = next !== doc.content;
15053
- if (changed && !dryRun) {
16639
+ changed = changed || next !== doc.content;
16640
+ if (next !== doc.content && !dryRun) {
15054
16641
  await writeFileAtomic(doc.absPath, next);
15055
- for (const f of pending) f.written = true;
16642
+ for (const f of pending) markWritten(f);
15056
16643
  }
15057
16644
  }
15058
16645
  }
@@ -15062,18 +16649,32 @@ async function runDerive(opts) {
15062
16649
  format: doc.format,
15063
16650
  fields: reported,
15064
16651
  changed,
15065
- ...check ? { findings: staleFindings(reported, doc.extracted.lineFor) } : {}
16652
+ ...check ? { findings: findingsOf(reported) } : {}
15066
16653
  });
15067
16654
  }
16655
+ if (!dryRun) {
16656
+ for (const held of heldManifests.values()) {
16657
+ if (held.text !== held.before) await writeFileAtomic(held.path, held.text);
16658
+ }
16659
+ }
16660
+ const summary = summarize2(results);
15068
16661
  return {
15069
16662
  results,
15070
- summary: summarize2(results),
16663
+ summary: wantsProvenance ? { ...summary, ranges: countRanges(results) } : summary,
15071
16664
  dryRun,
15072
16665
  check,
15073
16666
  sources: derived.sources,
15074
16667
  frame: { cwd, base: configDir ?? cwd, runBase: base }
15075
16668
  };
15076
16669
  }
16670
+ function deriveFailed(run2) {
16671
+ if (run2.summary.errors > 0) return true;
16672
+ return run2.check && run2.results.some((r) => (r.findings ?? []).length > 0);
16673
+ }
16674
+ function machineName(value) {
16675
+ const name2 = value?.trim();
16676
+ return name2 === void 0 || name2 === "" ? void 0 : name2;
16677
+ }
15077
16678
  function resolveFields(flag, configured, commands) {
15078
16679
  const names = flag ?? configured;
15079
16680
  if (names === void 0 || names.length === 0) {
@@ -15141,6 +16742,59 @@ function summarize2(results) {
15141
16742
  }
15142
16743
  return summary;
15143
16744
  }
16745
+ function markWritten(field) {
16746
+ field.written = true;
16747
+ for (const range of field.ranges ?? []) {
16748
+ if (range.status !== "current") range.written = true;
16749
+ }
16750
+ }
16751
+ function countRanges(results) {
16752
+ let n = 0;
16753
+ for (const r of results) {
16754
+ for (const f of r.fields) n += (f.ranges ?? []).filter((range) => range.written).length;
16755
+ }
16756
+ return n;
16757
+ }
16758
+ function judgeProvenance(asserted, derivation, value, place) {
16759
+ const base = {
16760
+ field: PROVENANCE_FIELD,
16761
+ ...asserted !== void 0 ? { asserted } : {},
16762
+ ...place !== void 0 ? { manifest: place.manifest.file } : {}
16763
+ };
16764
+ if (derivation === void 0) {
16765
+ return { field: { ...base, derived: null, status: "unknown", written: false } };
16766
+ }
16767
+ const comparisons = compareProvenance(provenanceEntries(asserted), derivation);
16768
+ const plan = planProvenanceWrite(comparisons, derivation);
16769
+ const settled = comparisons.every((c) => c.status === "current");
16770
+ const status = settled ? "current" : asserted === void 0 || comparisons.every((c) => c.status === "unset") ? "unset" : "stale";
16771
+ return {
16772
+ field: {
16773
+ ...base,
16774
+ derived: plan,
16775
+ source: "git",
16776
+ ...value !== null ? { evidence: value.evidence } : {},
16777
+ status,
16778
+ written: false,
16779
+ ranges: rangeResults(comparisons, derivation.page.bodyLine)
16780
+ },
16781
+ judged: { derivation, comparisons, plan }
16782
+ };
16783
+ }
16784
+ function bodyShift(before, after) {
16785
+ const was = readProvenancePage(before).body;
16786
+ const now = readProvenancePage(after).body;
16787
+ const shift = now.length - was.length;
16788
+ if (shift <= 0) return 0;
16789
+ return now.slice(shift).join("\n") === was.join("\n") ? shift : 0;
16790
+ }
16791
+ function shiftEntries(entries, shift) {
16792
+ return entries.map((e) => {
16793
+ const span = parseLines(e.lines);
16794
+ if (span === void 0) return e;
16795
+ return { ...e, lines: lineSpec({ start: span.start + shift, end: span.end + shift }) };
16796
+ });
16797
+ }
15144
16798
 
15145
16799
  // src/meta/reporters/fill.ts
15146
16800
  var FILL_FORMATS = ["pretty", "json", "github"];
@@ -15176,6 +16830,26 @@ function renderFillPretty(run2, opts = {}) {
15176
16830
  ` ${c.cyan(f.field)} ${formatValue(f.value)} ${c.dim(score(f.confidence))}`
15177
16831
  );
15178
16832
  }
16833
+ const record = result.metaProvenance;
16834
+ if (record?.written === true) {
16835
+ lines.push(
16836
+ ` ${c.cyan("meta-provenance")} ${record.entry["generated-by"]}: ${record.entry.fields.join(", ")}`
16837
+ );
16838
+ } else if (record?.skipReason === "schema-mismatch") {
16839
+ lines.push(
16840
+ c.dim(" meta-provenance not written: this page's schemas do not allow it")
16841
+ );
16842
+ } else if (record?.skipReason === "manifest-owned") {
16843
+ lines.push(
16844
+ c.dim(
16845
+ ` meta-provenance not written: owned by manifest ${record.manifest}, which manni meta fill does not write`
16846
+ )
16847
+ );
16848
+ } else if (record?.skipReason === "unwritable") {
16849
+ lines.push(
16850
+ c.dim(` meta-provenance not written: ${result.format} metadata cannot hold it`)
16851
+ );
16852
+ }
15179
16853
  if (low.length > 0) {
15180
16854
  lines.push(
15181
16855
  c.dim(
@@ -15327,26 +17001,49 @@ function renderDerivePretty(run2, opts = {}) {
15327
17001
  lines.push(`${result.file} ${c.dim("current")}${unknownNote(result, c.dim)}`);
15328
17002
  continue;
15329
17003
  }
15330
- lines.push(result.file);
15331
- const nameWidth = Math.max(...pending.map((f) => f.field.length));
15332
- const changeWidth = Math.max(...pending.map((f) => change(f).length));
17004
+ const page = [];
17005
+ const held = /* @__PURE__ */ new Map();
15333
17006
  for (const f of pending) {
15334
- const name2 = c.cyan(f.field.padEnd(nameWidth));
15335
- const from = f.status === "unset" ? c.dim("(unset)") : c.dim(fmt2(f.asserted));
15336
- const to = c.green(fmt2(f.derived));
15337
- const pad2 = " ".repeat(changeWidth - change(f).length);
15338
- const trail = c.dim(`(${f.source ?? "a source"}: ${f.evidence ?? "no evidence"})`);
15339
- lines.push(` ${name2} ${from} \u2192 ${to}${pad2} ${trail}`);
17007
+ if (f.ranges === void 0) {
17008
+ page.push(fieldRow(f, c));
17009
+ continue;
17010
+ }
17011
+ const rows = f.ranges.filter((r) => r.status !== "current").map((r) => rangeRow(f, r, c));
17012
+ if (f.manifest === void 0) page.push(...rows);
17013
+ else held.set(f.manifest, [...held.get(f.manifest) ?? [], ...rows]);
17014
+ }
17015
+ const all = [...page, ...[...held.values()].flat()];
17016
+ const nameWidth = Math.max(...all.map((r) => r.name.length));
17017
+ const changeWidth = Math.max(...all.map((r) => r.plain.length));
17018
+ const print = (row) => {
17019
+ const pad2 = " ".repeat(changeWidth - row.plain.length);
17020
+ lines.push(` ${c.cyan(row.name.padEnd(nameWidth))} ${row.change}${pad2} ${row.trail}`);
17021
+ };
17022
+ if (page.length > 0 || held.size === 0) lines.push(result.file);
17023
+ page.forEach(print);
17024
+ for (const [manifest, rows] of held) {
17025
+ lines.push(`${manifest} ${c.dim(`(for ${result.file})`)}`);
17026
+ rows.forEach(print);
15340
17027
  }
15341
17028
  const note = unknownNote(result, c.dim);
15342
17029
  if (note !== "") lines.push(` ${note}`);
15343
17030
  }
15344
17031
  const s = run2.summary;
15345
17032
  const parts = [plural2(s.files, "file")];
17033
+ const provenance = run2.results.flatMap((r) => r.fields.filter((f) => f.ranges !== void 0));
17034
+ const provenanceFields = provenance.filter((f) => run2.dryRun ? f.status === "stale" || f.status === "unset" : f.written).length;
15346
17035
  if (run2.dryRun) {
15347
- parts.push(`${s.changed} would change`, plural2(s.stale + s.unset, "field"));
17036
+ const ranges = provenance.reduce((n, f) => n + (f.ranges ?? []).filter((r) => r.status !== "current").length, 0);
17037
+ const fields = s.stale + s.unset - provenanceFields;
17038
+ parts.push(`${s.changed} would change`);
17039
+ if (fields > 0 || ranges === 0) parts.push(plural2(fields, "field"));
17040
+ if (ranges > 0) parts.push(plural2(ranges, "range"));
15348
17041
  } else {
15349
- parts.push(`${s.changed} changed`, `${plural2(s.written, "field")} written`);
17042
+ const ranges = s.ranges ?? 0;
17043
+ const fields = s.written - provenanceFields;
17044
+ parts.push(`${s.changed} changed`);
17045
+ if (fields > 0 || ranges === 0) parts.push(`${plural2(fields, "field")} written`);
17046
+ if (ranges > 0) parts.push(`${plural2(ranges, "range")} written`);
15350
17047
  }
15351
17048
  if (s.errors > 0) parts.push(plural2(s.errors, "error"));
15352
17049
  let footer = parts.join(", ");
@@ -15355,9 +17052,47 @@ function renderDerivePretty(run2, opts = {}) {
15355
17052
  lines.push(s.errors > 0 ? c.yellow(footer) : footer);
15356
17053
  return lines.join("\n");
15357
17054
  }
15358
- function change(f) {
17055
+ function fieldRow(f, c) {
15359
17056
  const from = f.status === "unset" ? "(unset)" : fmt2(f.asserted);
15360
- return `${from} \u2192 ${fmt2(f.derived)}`;
17057
+ const to = fmt2(f.derived);
17058
+ return {
17059
+ name: f.field,
17060
+ change: `${c.dim(from)} \u2192 ${c.green(to)}`,
17061
+ plain: `${from} \u2192 ${to}`,
17062
+ trail: c.dim(`(${f.source ?? "a source"}: ${f.evidence ?? "no evidence"})`)
17063
+ };
17064
+ }
17065
+ function rangeRow(f, r, c) {
17066
+ const head = `lines ${r.lines}: `;
17067
+ const machine = r["generated-by"];
17068
+ let from;
17069
+ let to;
17070
+ switch (r.status) {
17071
+ case "moved":
17072
+ from = "";
17073
+ to = `moved from ${r.from?.lines ?? r.lines}`;
17074
+ break;
17075
+ case "stale":
17076
+ from = r.from?.["generated-by"] ?? "(unset)";
17077
+ to = machine;
17078
+ break;
17079
+ case "changed":
17080
+ from = machine;
17081
+ to = "re-derived";
17082
+ break;
17083
+ case "unset":
17084
+ case "current":
17085
+ from = "(unset)";
17086
+ to = machine;
17087
+ break;
17088
+ }
17089
+ const arrow = from === "" ? "" : `${from} \u2192 `;
17090
+ return {
17091
+ name: f.field,
17092
+ change: `${head}${from === "" ? "" : `${c.dim(from)} \u2192 `}${c.green(to)}`,
17093
+ plain: `${head}${arrow}${to}`,
17094
+ trail: c.dim(`(${f.source ?? "git"}: ${r.evidence})`)
17095
+ };
15361
17096
  }
15362
17097
  function unknownNote(result, dim) {
15363
17098
  const unknown = result.fields.filter((f) => f.status === "unknown").map((f) => f.field);
@@ -15375,6 +17110,15 @@ function stringifyValue(value) {
15375
17110
  if (typeof value === "string") return value;
15376
17111
  return JSON.stringify(value);
15377
17112
  }
17113
+ function valueOf2(r, field, value) {
17114
+ if (field !== PROVENANCE_FIELD || r.provenance === void 0) return stringifyValue(value);
17115
+ if (value === void 0 || value === null) return "(unset)";
17116
+ const entries = provenanceEntries(value);
17117
+ if (!Array.isArray(value) || entries.length !== value.length || entries.length === 0) {
17118
+ return stringifyValue(value);
17119
+ }
17120
+ return renderProvenanceValue(entries, r.provenance.bodyLine);
17121
+ }
15378
17122
  function evidenceOf(d) {
15379
17123
  const repeated = ` (${stringifyValue(d.value)})`;
15380
17124
  return d.evidence.endsWith(repeated) ? d.evidence.slice(0, -repeated.length) : d.evidence;
@@ -15386,6 +17130,10 @@ function annotation(r, field) {
15386
17130
  if (origin === "derived") {
15387
17131
  return d == null ? "" : ` (derived, ${d.source}: ${evidenceOf(d)})`;
15388
17132
  }
17133
+ if (field === PROVENANCE_FIELD && r.provenance !== void 0) {
17134
+ if (r.provenance.current !== false) return " (asserted)";
17135
+ return ` (asserted; ${d?.source ?? "git"} says ${valueOf2(r, field, d?.value)})`;
17136
+ }
15389
17137
  if (d == null) return " (asserted)";
15390
17138
  if (compareDerived(field, r.resolved?.[field], d).status === "current") {
15391
17139
  return " (asserted)";
@@ -15406,7 +17154,7 @@ function renderGet(results, fields, opts = {}) {
15406
17154
  const note = annotation(r, f);
15407
17155
  const suffix = note === "" ? "" : c.dim(note);
15408
17156
  lines.push(
15409
- `${c.dim(`${r.file}:`)} ${f}=${stringifyValue(effective[f])}${suffix}`
17157
+ `${c.dim(`${r.file}:`)} ${f}=${valueOf2(r, f, effective[f])}${suffix}`
15410
17158
  );
15411
17159
  }
15412
17160
  }
@@ -15520,7 +17268,8 @@ export {
15520
17268
  ToolError,
15521
17269
  errorMessage,
15522
17270
  programName,
15523
- setProgramName,
17271
+ fail,
17272
+ runIfMain,
15524
17273
  externalMetadataUrlProblem,
15525
17274
  parseCollections,
15526
17275
  selectCollections,
@@ -15530,8 +17279,6 @@ export {
15530
17279
  generateEncryptionKey,
15531
17280
  encryptValue,
15532
17281
  decryptValue,
15533
- KEYED_PIN_PREFIX,
15534
- keyedPin,
15535
17282
  findGitRoot,
15536
17283
  notice,
15537
17284
  findConfigFile,
@@ -15540,6 +17287,27 @@ export {
15540
17287
  readFamilyConfigFile,
15541
17288
  escapeWorkflowCommandMessage,
15542
17289
  escapeWorkflowCommandProperty,
17290
+ INTEGRITY_SHAPE,
17291
+ integrityOf,
17292
+ isIntegrity,
17293
+ LineRangeError,
17294
+ normalizeText,
17295
+ splitLines,
17296
+ sliceLines,
17297
+ hashRange,
17298
+ hashLines,
17299
+ isKeyedPin,
17300
+ pinOfLines,
17301
+ MOVE_WINDOW_LINES,
17302
+ MOVE_BUDGET_BYTES,
17303
+ MAX_RANGE_LINES,
17304
+ findWindows,
17305
+ parseLines,
17306
+ lineSpec,
17307
+ spellLines,
17308
+ toFileLines,
17309
+ toBodyLines,
17310
+ splitPageArgument,
15543
17311
  ENCRYPTION_KEY_ENV,
15544
17312
  resolveEncryptionKey,
15545
17313
  writeEncryptionKey,
@@ -15553,7 +17321,6 @@ export {
15553
17321
  extractFrontmatter,
15554
17322
  isErrorSeverity,
15555
17323
  DocmetaError,
15556
- deepEqual,
15557
17324
  toJsonText,
15558
17325
  applyFrontmatter,
15559
17326
  extractorForExtension,
@@ -15570,9 +17337,6 @@ export {
15570
17337
  DEFAULT_TTL_HOURS,
15571
17338
  schemaCacheDir,
15572
17339
  SchemaCache,
15573
- INTEGRITY_SHAPE,
15574
- integrityOf,
15575
- isIntegrity,
15576
17340
  listBuiltins,
15577
17341
  classifyRef,
15578
17342
  fetchSchemaBytes,
@@ -15581,7 +17345,6 @@ export {
15581
17345
  resolveBaselineRequest,
15582
17346
  settleBaseline,
15583
17347
  DEFAULT_SCHEMAS,
15584
- FILE_SCHEMA_KEY,
15585
17348
  schemaEntryRef,
15586
17349
  collectSchemaPins,
15587
17350
  rebaseConfigSchemaRefs,
@@ -15629,6 +17392,7 @@ export {
15629
17392
  orphanEntries,
15630
17393
  orphanJoins,
15631
17394
  orphanError,
17395
+ spliceManifestValue,
15632
17396
  isParamName,
15633
17397
  memberOf,
15634
17398
  Validator,
@@ -15669,6 +17433,7 @@ export {
15669
17433
  runQuery,
15670
17434
  runFill,
15671
17435
  runDerive,
17436
+ deriveFailed,
15672
17437
  FILL_FORMATS,
15673
17438
  FILL_FORMAT_LIST,
15674
17439
  isFillFormat,
@@ -15683,4 +17448,4 @@ export {
15683
17448
  renderQuery,
15684
17449
  renderQueryCsv
15685
17450
  };
15686
- //# sourceMappingURL=chunk-PDNSN5UJ.js.map
17451
+ //# sourceMappingURL=chunk-2DC2GSRH.js.map