@hawkeyexl/manni 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,10 +2,12 @@
2
2
  import {
3
3
  DocmetaError,
4
4
  ENCRYPTION_KEY_ENV,
5
- FILE_SCHEMA_KEY,
6
- KEYED_PIN_PREFIX,
7
5
  LEGACY_CONFIG_NAMES,
6
+ LineRangeError,
7
+ MAX_RANGE_LINES,
8
8
  META_CONTEXT,
9
+ MOVE_BUDGET_BYTES,
10
+ MOVE_WINDOW_LINES,
9
11
  PATH_JOIN,
10
12
  STDIN_LABEL,
11
13
  STDIN_TOKEN,
@@ -15,7 +17,6 @@ import {
15
17
  classifyRef,
16
18
  committedKeyWarning,
17
19
  decryptValue,
18
- deepEqual,
19
20
  encryptValue,
20
21
  ensureEncryptionKey,
21
22
  errorMessage,
@@ -28,23 +29,29 @@ import {
28
29
  findConfigFile,
29
30
  findFamilyConfigFile,
30
31
  findGitRoot,
32
+ findWindows,
31
33
  generateEncryptionKey,
32
34
  gitignoreOptions,
33
- integrityOf,
35
+ hashLines,
36
+ hashRange,
34
37
  isEncryptedValue,
35
38
  isErrorSeverity,
36
39
  isIgnoredByGit,
40
+ isKeyedPin,
37
41
  isMember,
38
42
  isValidEncryptionKey,
39
- keyedPin,
43
+ lineSpec,
40
44
  loadExternalMetadata,
41
45
  locateFrontmatter,
42
46
  memberOf,
43
47
  mergeExternalMetadata,
48
+ normalizeText,
44
49
  orphanEntries,
45
50
  orphanError,
46
51
  orphanJoins,
47
52
  palette,
53
+ parseLines,
54
+ pinOfLines,
48
55
  readConfigFile,
49
56
  readFamilyConfigFile,
50
57
  resolveBaselineRequest,
@@ -52,11 +59,17 @@ import {
52
59
  resolveTargetSet,
53
60
  selectCollections,
54
61
  settleBaseline,
62
+ sliceLines,
63
+ spellLines,
64
+ spliceManifestValue,
65
+ splitLines,
55
66
  supportedExtensions,
67
+ toBodyLines,
68
+ toFileLines,
56
69
  withValueAt,
57
70
  writeEncryptionKey,
58
71
  writeFileAtomic
59
- } from "./chunk-PDNSN5UJ.js";
72
+ } from "./chunk-5O4FIGDJ.js";
60
73
 
61
74
  // src/meta/core/reencrypt.ts
62
75
  import { extname } from "node:path";
@@ -200,7 +213,7 @@ __export(cite_exports, {
200
213
  formatStatement: () => formatStatement,
201
214
  gitClient: () => gitClient,
202
215
  hashLines: () => hashLines,
203
- hashRange: () => hashRange,
216
+ hashRange: () => hashRange2,
204
217
  historyOf: () => historyOf,
205
218
  insertStatementBefore: () => insertStatementBefore,
206
219
  insideFence: () => insideFence,
@@ -249,7 +262,7 @@ __export(cite_exports, {
249
262
  shortPin: () => shortPin,
250
263
  shortSrc: () => shortSrc,
251
264
  sidecarsFor: () => sidecarsFor,
252
- sliceLines: () => sliceLines,
265
+ sliceLines: () => sliceLines2,
253
266
  sourceRange: () => sourceRange,
254
267
  spellLines: () => spellLines,
255
268
  spellSource: () => spellSource,
@@ -292,6 +305,14 @@ var CiteError = class extends ToolError {
292
305
  this.name = "CiteError";
293
306
  }
294
307
  };
308
+ function asCiteError(call) {
309
+ try {
310
+ return call();
311
+ } catch (error) {
312
+ if (error instanceof LineRangeError) throw new CiteError(error.message);
313
+ throw error;
314
+ }
315
+ }
295
316
 
296
317
  // src/cite/core/severity.ts
297
318
  var DEFAULT_SEVERITY = {
@@ -324,7 +345,6 @@ function resolveSeverity(overrides) {
324
345
  // src/cite/core/range.ts
325
346
  var FILE_PATTERN = /^(?:~[A-Za-z0-9_-]{82,}|(?!~)(?:(?!\.\.?(?:\/|$))[^/\\:\r\n\t]+)(?:\/(?:(?!\.\.?(?:\/|$))[^/\\:\r\n\t]+))*)$/;
326
347
  var SRC_PATTERN = /^(?:~[A-Za-z0-9_-]{82,}|(?!~)(?:(?!\.\.?(?:\/|:|$))[^/\\:\r\n\t]+)(?:\/(?:(?!\.\.?(?:\/|:|$))[^/\\:\r\n\t]+))*)(?::[1-9][0-9]*(?:-[1-9][0-9]*)?)?$/;
327
- var RANGE_SPEC = /^([1-9][0-9]*)(?:-([1-9][0-9]*))?$/;
328
348
  function parseSrc(src) {
329
349
  if (!SRC_PATTERN.test(src)) {
330
350
  throw new CiteError(
@@ -351,24 +371,6 @@ function formatSrc(range) {
351
371
  if (end === void 0 || end === start) return `${path}:${start}`;
352
372
  return `${path}:${start}-${end}`;
353
373
  }
354
- function parseLines(spec) {
355
- if (typeof spec === "number") {
356
- return Number.isInteger(spec) && spec >= 1 ? { start: spec, end: spec } : void 0;
357
- }
358
- const m = RANGE_SPEC.exec(spec);
359
- const startText = m?.[1];
360
- if (startText === void 0) return void 0;
361
- const start = Number(startText);
362
- const endText = m?.[2];
363
- const end = endText === void 0 ? start : Number(endText);
364
- return end < start ? void 0 : { start, end };
365
- }
366
- function lineSpec(lines) {
367
- return lines.start === lines.end ? lines.start : `${String(lines.start)}-${String(lines.end)}`;
368
- }
369
- function spellLines(lines) {
370
- return String(lineSpec(lines));
371
- }
372
374
  function sourceRange(source) {
373
375
  const range = { path: source.file, encrypted: isEncryptedValue(source.file) };
374
376
  if (source.lines === void 0) return range;
@@ -388,33 +390,11 @@ function rangeLines(range) {
388
390
  }
389
391
 
390
392
  // src/cite/core/hash.ts
391
- function normalizeText(text) {
392
- const body = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
393
- return body.replace(/\r\n/g, "\n");
394
- }
395
- function splitLines(text) {
396
- const parts = normalizeText(text).split("\n");
397
- if (parts.length > 0 && parts[parts.length - 1] === "") parts.pop();
398
- return parts;
399
- }
400
- function sliceLines(lines, range, label) {
401
- const start = range?.start;
402
- if (start === void 0) return lines.join("\n");
403
- const end = range?.end ?? start;
404
- if (start > lines.length || end > lines.length) {
405
- const first = start > lines.length ? start : end;
406
- throw new CiteError(`${label ?? "the source"} has ${lines.length} lines; line ${first} is out of range.`);
407
- }
408
- return lines.slice(start - 1, end).join("\n");
393
+ function sliceLines2(lines, range, label) {
394
+ return asCiteError(() => sliceLines(lines, range, label));
409
395
  }
410
- function hashRange(text, range, key) {
411
- return hashLines(sliceLines(splitLines(text), range), key);
412
- }
413
- function hashLines(joined, key) {
414
- return key === void 0 ? integrityOf(Buffer.from(joined, "utf8")) : keyedPin(joined, key);
415
- }
416
- function isKeyedPin(pin) {
417
- return pin.startsWith(KEYED_PIN_PREFIX);
396
+ function hashRange2(text, range, key) {
397
+ return asCiteError(() => hashRange(text, range, key));
418
398
  }
419
399
 
420
400
  // src/cite/core/spell.ts
@@ -855,141 +835,6 @@ function offsetOfLine(content, line) {
855
835
  return pos;
856
836
  }
857
837
 
858
- // src/cite/core/classify.ts
859
- var MOVE_WINDOW_LINES = 2e3;
860
- var MOVE_BUDGET_BYTES = 64 * 1024 * 1024;
861
- var MAX_RANGE_LINES = 5e3;
862
- function findWindows(lines, length, pin, key, opts) {
863
- const starts = [];
864
- const lastStart = lines.length - length + 1;
865
- if (length < 1 || lastStart < 1) return { starts, truncated: false };
866
- const joined = (start) => lines.slice(start - 1, start - 1 + length).join("\n");
867
- const original = opts?.original;
868
- if (original !== void 0) {
869
- const first = original[0];
870
- for (let start = 1; start <= lastStart; start++) {
871
- if (lines[start - 1] !== first) continue;
872
- let same = true;
873
- for (let i = 1; i < length; i++) {
874
- if (lines[start - 1 + i] !== original[i]) {
875
- same = false;
876
- break;
877
- }
878
- }
879
- if (same && hashLines(joined(start), key) === pin) starts.push(start);
880
- }
881
- return { starts, truncated: false };
882
- }
883
- const budget = opts?.budget ?? MOVE_BUDGET_BYTES;
884
- const around = opts?.around ?? 1;
885
- const bandStart = Math.max(1, around - MOVE_WINDOW_LINES);
886
- const bandEnd = Math.min(lastStart, around + MOVE_WINDOW_LINES);
887
- const order = [];
888
- for (let start = bandStart; start <= bandEnd; start++) order.push(start);
889
- for (let start = 1; start < bandStart; start++) order.push(start);
890
- for (let start = bandEnd + 1; start <= lastStart; start++) order.push(start);
891
- let spent = 0;
892
- for (const start of order) {
893
- const text = joined(start);
894
- const bytes = Buffer.byteLength(text, "utf8");
895
- if (spent + bytes > budget) return { starts, truncated: true };
896
- spent += bytes;
897
- if (hashLines(text, key) === pin) starts.push(start);
898
- }
899
- return { starts, truncated: false };
900
- }
901
- async function historyOf(git, commit, path, range, pin, key, budget) {
902
- const shown = await git.showFile(commit, path);
903
- if ("missing" in shown) {
904
- return shown.missing === "commit" ? { kind: "unavailable" } : { kind: "never-true" };
905
- }
906
- const then = splitLines(shown.text);
907
- let joined;
908
- try {
909
- joined = sliceLines(then, range);
910
- } catch {
911
- joined = void 0;
912
- }
913
- if (joined !== void 0 && hashLines(joined, key) === pin) {
914
- return { kind: "original", lines: joined.split("\n") };
915
- }
916
- if (range.start === void 0) return { kind: "never-true" };
917
- const length = (range.end ?? range.start) - range.start + 1;
918
- const search = { around: range.start };
919
- if (budget !== void 0) search.budget = budget;
920
- const found = findWindows(then, length, pin, key, search);
921
- const [first] = found.starts;
922
- if (first !== void 0) {
923
- return { kind: "original", lines: then.slice(first - 1, first - 1 + length) };
924
- }
925
- return found.truncated ? { kind: "unknown" } : { kind: "never-true" };
926
- }
927
- async function classifyCitation(entry, opts) {
928
- const { citation } = entry;
929
- const range = sourceRange(citation.source);
930
- const commit = citation.source["commit-sha"];
931
- const result = { src: spellSource(citation.source), status: "missing" };
932
- if (commit !== void 0) result.commitSha = commit;
933
- const source = await readSource(opts.root, opts.index, range, opts.key);
934
- if (source.kind === "missing") {
935
- result.missingReason = source.reason;
936
- return result;
937
- }
938
- result.resolvedPath = source.resolvedPath;
939
- const key = range.encrypted ? opts.key : void 0;
940
- const pin = citation.source.integrity;
941
- const lines = splitLines(source.text);
942
- let here;
943
- try {
944
- here = hashLines(sliceLines(lines, range), key);
945
- } catch {
946
- here = void 0;
947
- }
948
- if (here === pin) {
949
- result.status = "current";
950
- return result;
951
- }
952
- let history = { kind: "none" };
953
- if (commit !== void 0 && await opts.git.available()) {
954
- history = await historyOf(opts.git, commit, source.resolvedPath, range, pin, key, opts.budget);
955
- }
956
- if (range.start !== void 0) {
957
- const start = range.start;
958
- const end = range.end ?? start;
959
- const search = { around: start };
960
- if (history.kind === "original") search.original = history.lines;
961
- if (opts.budget !== void 0) search.budget = opts.budget;
962
- const found = findWindows(lines, end - start + 1, pin, key, search);
963
- if (found.truncated) result.truncatedSearch = true;
964
- const spell = (at2) => formatSrc({ ...range, start: at2, end: at2 + (end - start) });
965
- const [only] = found.starts;
966
- if (found.starts.length === 1 && only !== void 0) {
967
- result.status = "moved";
968
- result.newSrc = spell(only);
969
- result.newLines = String(lineSpec({ start: only, end: only + (end - start) }));
970
- return result;
971
- }
972
- if (found.starts.length > 1) {
973
- result.status = "moved-ambiguous";
974
- result.candidates = found.starts.map(spell);
975
- return result;
976
- }
977
- }
978
- if (history.kind === "never-true") {
979
- result.status = "never-true";
980
- return result;
981
- }
982
- result.status = "changed";
983
- if (history.kind === "unavailable") result.historyAvailable = false;
984
- if (history.kind === "unknown") result.truncatedSearch = true;
985
- if (history.kind === "original" && commit !== void 0) {
986
- result.historyAvailable = true;
987
- result.commitsSince = await opts.git.subjectsSince(commit, source.resolvedPath);
988
- result.diff = await opts.git.diffSince(commit, source.resolvedPath);
989
- }
990
- return result;
991
- }
992
-
993
838
  // src/cite/core/claims.ts
994
839
  function normalizeWhitespace(text) {
995
840
  return text.replace(/\s+/g, " ").trim();
@@ -1002,19 +847,9 @@ function normalizeBlock(text) {
1002
847
  function blockMatches(blockText, citedJoined) {
1003
848
  return normalizeBlock(blockText) === normalizeBlock(citedJoined);
1004
849
  }
1005
- function toFileLines(lines, bodyLine) {
1006
- return { start: bodyLine + lines.start - 1, end: bodyLine + lines.end - 1 };
1007
- }
1008
- function toBodyLines(lines, bodyLine) {
1009
- return { start: lines.start - bodyLine + 1, end: lines.end - bodyLine + 1 };
1010
- }
1011
850
  function textAt(lines, range) {
1012
851
  return lines.slice(range.start - 1, Math.min(range.end, lines.length));
1013
852
  }
1014
- function pinOfLines(lines, range) {
1015
- if (range.start < 1 || range.end > lines.length) return void 0;
1016
- return hashLines(lines.slice(range.start - 1, range.end).join("\n"));
1017
- }
1018
853
  function claimLine(claim) {
1019
854
  const at2 = claim.fileLines === void 0 ? void 0 : parseLines(claim.fileLines);
1020
855
  return at2?.start;
@@ -1188,6 +1023,99 @@ function noGit() {
1188
1023
  };
1189
1024
  }
1190
1025
 
1026
+ // src/cite/core/classify.ts
1027
+ async function historyOf(git, commit, path, range, pin, key, budget) {
1028
+ const shown = await git.showFile(commit, path);
1029
+ if ("missing" in shown) {
1030
+ return shown.missing === "commit" ? { kind: "unavailable" } : { kind: "never-true" };
1031
+ }
1032
+ const then = splitLines(shown.text);
1033
+ let joined;
1034
+ try {
1035
+ joined = sliceLines2(then, range);
1036
+ } catch {
1037
+ joined = void 0;
1038
+ }
1039
+ if (joined !== void 0 && hashLines(joined, key) === pin) {
1040
+ return { kind: "original", lines: joined.split("\n") };
1041
+ }
1042
+ if (range.start === void 0) return { kind: "never-true" };
1043
+ const length = (range.end ?? range.start) - range.start + 1;
1044
+ const search = { around: range.start };
1045
+ if (budget !== void 0) search.budget = budget;
1046
+ const found = findWindows(then, length, pin, key, search);
1047
+ const [first] = found.starts;
1048
+ if (first !== void 0) {
1049
+ return { kind: "original", lines: then.slice(first - 1, first - 1 + length) };
1050
+ }
1051
+ return found.truncated ? { kind: "unknown" } : { kind: "never-true" };
1052
+ }
1053
+ async function classifyCitation(entry, opts) {
1054
+ const { citation } = entry;
1055
+ const range = sourceRange(citation.source);
1056
+ const commit = citation.source["commit-sha"];
1057
+ const result = { src: spellSource(citation.source), status: "missing" };
1058
+ if (commit !== void 0) result.commitSha = commit;
1059
+ const source = await readSource(opts.root, opts.index, range, opts.key);
1060
+ if (source.kind === "missing") {
1061
+ result.missingReason = source.reason;
1062
+ return result;
1063
+ }
1064
+ result.resolvedPath = source.resolvedPath;
1065
+ const key = range.encrypted ? opts.key : void 0;
1066
+ const pin = citation.source.integrity;
1067
+ const lines = splitLines(source.text);
1068
+ let here;
1069
+ try {
1070
+ here = hashLines(sliceLines2(lines, range), key);
1071
+ } catch {
1072
+ here = void 0;
1073
+ }
1074
+ if (here === pin) {
1075
+ result.status = "current";
1076
+ return result;
1077
+ }
1078
+ let history = { kind: "none" };
1079
+ if (commit !== void 0 && await opts.git.available()) {
1080
+ history = await historyOf(opts.git, commit, source.resolvedPath, range, pin, key, opts.budget);
1081
+ }
1082
+ if (range.start !== void 0) {
1083
+ const start = range.start;
1084
+ const end = range.end ?? start;
1085
+ const search = { around: start };
1086
+ if (history.kind === "original") search.original = history.lines;
1087
+ if (opts.budget !== void 0) search.budget = opts.budget;
1088
+ const found = findWindows(lines, end - start + 1, pin, key, search);
1089
+ if (found.truncated) result.truncatedSearch = true;
1090
+ const spell = (at2) => formatSrc({ ...range, start: at2, end: at2 + (end - start) });
1091
+ const [only] = found.starts;
1092
+ if (found.starts.length === 1 && only !== void 0) {
1093
+ result.status = "moved";
1094
+ result.newSrc = spell(only);
1095
+ result.newLines = String(lineSpec({ start: only, end: only + (end - start) }));
1096
+ return result;
1097
+ }
1098
+ if (found.starts.length > 1) {
1099
+ result.status = "moved-ambiguous";
1100
+ result.candidates = found.starts.map(spell);
1101
+ return result;
1102
+ }
1103
+ }
1104
+ if (history.kind === "never-true") {
1105
+ result.status = "never-true";
1106
+ return result;
1107
+ }
1108
+ result.status = "changed";
1109
+ if (history.kind === "unavailable") result.historyAvailable = false;
1110
+ if (history.kind === "unknown") result.truncatedSearch = true;
1111
+ if (history.kind === "original" && commit !== void 0) {
1112
+ result.historyAvailable = true;
1113
+ result.commitsSince = await opts.git.subjectsSince(commit, source.resolvedPath);
1114
+ result.diff = await opts.git.diffSince(commit, source.resolvedPath);
1115
+ }
1116
+ return result;
1117
+ }
1118
+
1191
1119
  // src/cite/core/page.ts
1192
1120
  import { extname as extname2 } from "node:path";
1193
1121
  import * as Ajv2020Ns from "ajv/dist/2020.js";
@@ -1195,8 +1123,8 @@ import * as Ajv2020Ns from "ajv/dist/2020.js";
1195
1123
  // src/cite/schema/citations.json
1196
1124
  var citations_default = {
1197
1125
  $schema: "https://json-schema.org/draft/2020-12/schema",
1198
- $id: "manni:citations:1.0.0-proposal.3",
1199
- title: "manni citations vocabulary (1.0.0-proposal.3)",
1126
+ $id: "manni:citations:1.0.0-proposal.4",
1127
+ title: "manni citations vocabulary (1.0.0-proposal.4)",
1200
1128
  description: "What do this page's claims rest on? For each claim, the page lines that make it, the source lines it was written from, and a hash of each. A manni common vocabulary: any drift check can implement it, and other schemas may compose on top of it. A pin is a record of the past, not a copy of the present. It says what the lines were when the sentence was written. The check compares that record with the files as they are now. Recompute it on every run and there is nothing to compare against. `source-of-truth` (manni:stewardship) names the file a page answers to; a citation names the lines a sentence answers to. The page root stays open so sibling tools' keys pass untouched. The `citation-` prefix is reserved, so an unrecognized `citation-*` key is rejected and a typo fails loudly.",
1201
1129
  type: "object",
1202
1130
  additionalProperties: true,
@@ -1207,7 +1135,8 @@ var citations_default = {
1207
1135
  items: {
1208
1136
  $ref: "#/$defs/citationEntry"
1209
1137
  },
1210
- description: "The page's citations, one entry per claim or pin. An empty list is not a declaration; a page with no citations omits the key. Ids are unique within a page, and two entries claiming one id is an error. JSON Schema cannot say that over a list whose members may omit the id. So it is a requirement on implementations, stated here so every tool agrees."
1138
+ description: "The page's citations, one entry per claim or pin. An empty list is not a declaration; a page with no citations omits the key. Ids are unique within a page, and two entries claiming one id is an error. JSON Schema cannot say that over a list whose members may omit the id. So it is a requirement on implementations, stated here so every tool agrees.",
1139
+ "x-manni-location": "external"
1211
1140
  }
1212
1141
  },
1213
1142
  patternProperties: {
@@ -1308,326 +1237,6 @@ var citations_default = {
1308
1237
  }
1309
1238
  };
1310
1239
 
1311
- // src/meta/core/external-metadata-write.ts
1312
- import { posix } from "node:path";
1313
- import {
1314
- LineCounter,
1315
- isMap,
1316
- isNode,
1317
- isPair,
1318
- isScalar,
1319
- isSeq,
1320
- parseDocument,
1321
- stringify
1322
- } from "yaml";
1323
- function spliceManifestValue(text, options) {
1324
- const { entry, key, value, file } = options;
1325
- const join4 = options.join ?? PATH_JOIN;
1326
- const at2 = (line2) => {
1327
- const name = file === void 0 ? "Manifest" : `Manifest ${file}`;
1328
- if (line2 === void 0) return name;
1329
- return file === void 0 ? `${name} line ${String(line2)}` : `${name}:${String(line2)}`;
1330
- };
1331
- if (key === FILE_SCHEMA_KEY) {
1332
- throw new DocmetaError(
1333
- `${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.`
1334
- );
1335
- }
1336
- if (value === void 0) {
1337
- throw new DocmetaError(`${at2()}: refusing to write "${key}" for "${entry}" with no value.`);
1338
- }
1339
- const eol = detectEol2(text);
1340
- const lc = new LineCounter();
1341
- const doc = parseDocument(text, { lineCounter: lc, uniqueKeys: false });
1342
- const problem = doc.errors[0];
1343
- if (problem) {
1344
- throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
1345
- }
1346
- const keyLine = (node) => {
1347
- const r = rangeOf(node);
1348
- return r ? lc.linePos(r[0]).line : void 0;
1349
- };
1350
- const same = join4 === PATH_JOIN ? (a, b) => posix.normalize(a) === posix.normalize(b) : (a, b) => a === b;
1351
- const root = doc.contents;
1352
- let spelled = entry;
1353
- let edit;
1354
- if (root === null || isScalar(root) && root.value === null && emptySource(text, root)) {
1355
- edit = startManifest(text, entry, key, value, eol);
1356
- } else if (!isMap(root)) {
1357
- throw new DocmetaError(
1358
- `${at2()}: the manifest must be a mapping from document ${join4 === PATH_JOIN ? "path" : `"${join4}"`} to owned keys.`
1359
- );
1360
- } else {
1361
- const style = detectStyle(text, root, eol);
1362
- const matches = root.items.filter((p) => same(keyString(p.key), entry));
1363
- const [first, second] = matches;
1364
- if (first && second) {
1365
- throw new DocmetaError(
1366
- `${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
1367
- );
1368
- }
1369
- if (first === void 0) {
1370
- if (root.flow === true && root.items.length > 0) {
1371
- throw new DocmetaError(
1372
- `${at2()}: the manifest is a flow mapping ({ \u2026 }), which this writer does not add entries to. Rewrite it in block style.`
1373
- );
1374
- }
1375
- edit = appendEntry(text, root, entry, key, value, style);
1376
- } else {
1377
- spelled = keyString(first.key);
1378
- const where = at2(keyLine(first.key));
1379
- const v = first.value;
1380
- if (!isMap(v)) {
1381
- throw new DocmetaError(`${where}: "${spelled}" must be a mapping of owned keys to values.`);
1382
- }
1383
- const hits = v.items.filter((kv) => keyString(kv.key) === key);
1384
- const [hit, again] = hits;
1385
- if (hit && again) {
1386
- throw new DocmetaError(
1387
- `${at2(keyLine(again.key))}: "${spelled}" sets "${key}" twice (first at line ${String(keyLine(hit.key) ?? "?")}). Remove one.`
1388
- );
1389
- }
1390
- const flowRefusal = () => new DocmetaError(
1391
- `${where}: "${spelled}" is a flow mapping ({ \u2026 }), which this writer does not add keys to. Rewrite the entry in block style.`
1392
- );
1393
- const formRefusal = () => new DocmetaError(
1394
- `${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
1395
- );
1396
- let made;
1397
- if (v.flow === true) {
1398
- if (hit) made = replaceFlowValue(text, hit, value);
1399
- else if (v.items.length === 0) made = fillEmptyEntry(text, first, key, value, style);
1400
- else throw flowRefusal();
1401
- if (!made) throw hit ? flowRefusal() : formRefusal();
1402
- } else {
1403
- made = hit ? replaceBlockValue(text, hit, value, style) : insertKey(text, v, key, value, style);
1404
- if (!made) throw formRefusal();
1405
- }
1406
- edit = made;
1407
- }
1408
- }
1409
- const out = text.slice(0, edit.start) + edit.insert + text.slice(edit.end);
1410
- const line = readBack(
1411
- doc,
1412
- out,
1413
- spelled,
1414
- key,
1415
- value,
1416
- (why) => new DocmetaError(
1417
- `${at2()}: writing "${key}" for "${entry}" did not read back as written, so nothing was written. ${why}`
1418
- )
1419
- );
1420
- return { text: out, line };
1421
- }
1422
- function startManifest(text, entry, key, value, eol) {
1423
- const style = { step: 2, indentSeq: true, eol };
1424
- const block = blockLines({ [entry]: { [key]: value } }, 0, style);
1425
- let insert;
1426
- if (text === "" || text.endsWith("\n")) insert = block + eol;
1427
- else insert = eol + block;
1428
- return { start: text.length, end: text.length, insert };
1429
- }
1430
- function appendEntry(text, root, entry, key, value, style) {
1431
- const body = { [entry]: { [key]: value } };
1432
- if (root.flow === true) {
1433
- const r = rangeOf(root);
1434
- const start = r ? r[0] : text.length;
1435
- const end = contentEnd(text, root) ?? start;
1436
- return { start, end, insert: blockLines(body, colOf(text, start), style) };
1437
- }
1438
- const firstKey = rangeOf(root.items[0]?.key);
1439
- const col = firstKey ? colOf(text, firstKey[0]) : 0;
1440
- const pos = lineEnd2(text, contentEnd(text, root) ?? text.length);
1441
- const last = root.items.at(-1);
1442
- const spaced = root.items.length > 1 && isNode(last?.key) && last.key.spaceBefore === true;
1443
- return {
1444
- start: pos,
1445
- end: pos,
1446
- insert: style.eol + (spaced ? style.eol : "") + blockLines(body, col, style)
1447
- };
1448
- }
1449
- function insertKey(text, entryMap, key, value, style) {
1450
- const firstKey = rangeOf(entryMap.items[0]?.key);
1451
- const end = contentEnd(text, entryMap);
1452
- if (!firstKey || end === void 0) return void 0;
1453
- const pos = lineEnd2(text, end);
1454
- return {
1455
- start: pos,
1456
- end: pos,
1457
- insert: style.eol + blockLines({ [key]: value }, colOf(text, firstKey[0]), style)
1458
- };
1459
- }
1460
- function fillEmptyEntry(text, entryPair, key, value, style) {
1461
- const start = colonEnd(text, entryPair);
1462
- const keyRange = rangeOf(entryPair.key);
1463
- const end = contentEnd(text, entryPair.value);
1464
- if (start === void 0 || !keyRange || end === void 0) return void 0;
1465
- const col = colOf(text, keyRange[0]) + style.step;
1466
- return { start, end, insert: style.eol + blockLines({ [key]: value }, col, style) };
1467
- }
1468
- function replaceBlockValue(text, pair, value, style) {
1469
- const colon = colonEnd(text, pair);
1470
- const keyRange = rangeOf(pair.key);
1471
- if (colon === void 0 || !keyRange) return void 0;
1472
- const keyCol = colOf(text, keyRange[0]);
1473
- let start = colon;
1474
- let ownLine = false;
1475
- let i = colon;
1476
- while (text.charAt(i) === " " || text.charAt(i) === " ") i++;
1477
- if (text.charAt(i) === "#") {
1478
- start = lineEnd2(text, i);
1479
- ownLine = true;
1480
- }
1481
- const old = pair.value;
1482
- const oldEnd = isScalar(old) && old.value === null && emptySource(text, old) ? start : contentEnd(text, old) ?? start;
1483
- const end = Math.max(start, oldEnd);
1484
- let insert = afterColon(value, keyCol, style);
1485
- if (ownLine && !insert.startsWith(style.eol)) {
1486
- insert = style.eol + " ".repeat(keyCol + style.step) + insert.trimStart();
1487
- }
1488
- return { start, end, insert };
1489
- }
1490
- function replaceFlowValue(text, pair, value) {
1491
- const r = rangeOf(pair.value);
1492
- if (!r || emptySource(text, pair.value)) return void 0;
1493
- const end = contentEnd(text, pair.value) ?? r[1];
1494
- return { start: r[0], end, insert: flowText(value) };
1495
- }
1496
- function readBack(before, out, spelled, key, value, fail) {
1497
- const lc = new LineCounter();
1498
- const after = parseDocument(out, { lineCounter: lc, uniqueKeys: false });
1499
- const problem = after.errors[0];
1500
- if (problem) throw fail(`The result is not valid YAML: ${problem.message}`);
1501
- const was = before.toJS({ maxAliasCount: 100 });
1502
- const now = after.toJS({ maxAliasCount: 100 });
1503
- const wasRoot = isRecord(was) ? was : {};
1504
- const wasEntry = wasRoot[spelled];
1505
- const expected = {
1506
- ...wasRoot,
1507
- [spelled]: { ...isRecord(wasEntry) ? wasEntry : {}, [key]: value }
1508
- };
1509
- const nowEntry = isRecord(now) ? now[spelled] : void 0;
1510
- if (!isRecord(nowEntry) || !deepEqual(nowEntry[key], value)) {
1511
- throw fail(
1512
- "The value reads back differently. A Date, an undefined member, or another value YAML cannot hold does not survive the trip."
1513
- );
1514
- }
1515
- if (!deepEqual(now, expected)) {
1516
- throw fail("Another value in the manifest would have changed.");
1517
- }
1518
- const root = after.contents;
1519
- const pair = isMap(root) ? root.items.find((p) => keyString(p.key) === spelled) : void 0;
1520
- const entryMap = pair?.value;
1521
- const kv = isMap(entryMap) ? entryMap.items.find((p) => keyString(p.key) === key) : void 0;
1522
- const r = rangeOf(kv?.value) ?? rangeOf(kv?.key);
1523
- if (!r) throw fail("The written value could not be found again.");
1524
- return lc.linePos(r[0]).line;
1525
- }
1526
- function blockLines(pair, col, style) {
1527
- const s = stringify(pair, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
1528
- return indentLines(s.replace(/\n$/, "").split("\n"), col, 0).join(style.eol);
1529
- }
1530
- function afterColon(value, col, style) {
1531
- const s = stringify({ k: value }, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
1532
- const lines = s.replace(/\n$/, "").slice("k:".length).split("\n");
1533
- return indentLines(lines, col, 1).join(style.eol);
1534
- }
1535
- function indentLines(lines, col, from) {
1536
- const pad = " ".repeat(col);
1537
- return lines.map((l, i) => i < from || l === "" ? l : pad + l);
1538
- }
1539
- function flowText(value) {
1540
- const s = stringify(value, { lineWidth: 0, collectionStyle: "flow" }).replace(/\n$/, "");
1541
- return s.includes("\n") ? JSON.stringify(value) : s;
1542
- }
1543
- function detectEol2(text) {
1544
- const i = text.indexOf("\n");
1545
- return i > 0 && text.charAt(i - 1) === "\r" ? "\r\n" : "\n";
1546
- }
1547
- function detectStyle(text, root, eol) {
1548
- let step = 2;
1549
- for (const pair of root.items) {
1550
- const v = pair.value;
1551
- const entryKey = rangeOf(pair.key);
1552
- const ownedKey = isMap(v) && v.flow !== true ? rangeOf(v.items[0]?.key) : void 0;
1553
- if (entryKey && ownedKey) {
1554
- const d = colOf(text, ownedKey[0]) - colOf(text, entryKey[0]);
1555
- if (d > 0) {
1556
- step = d;
1557
- break;
1558
- }
1559
- }
1560
- }
1561
- return { step, indentSeq: seqIndented(text, root) ?? true, eol };
1562
- }
1563
- function seqIndented(text, node) {
1564
- if (isMap(node)) {
1565
- for (const pair of node.items) {
1566
- const v = pair.value;
1567
- if (isSeq(v) && v.flow !== true && v.items.length > 0) {
1568
- const k = rangeOf(pair.key);
1569
- const s = rangeOf(v);
1570
- if (k && s) return colOf(text, s[0]) > colOf(text, k[0]);
1571
- }
1572
- const inner = seqIndented(text, v);
1573
- if (inner !== void 0) return inner;
1574
- }
1575
- } else if (isSeq(node)) {
1576
- for (const item of node.items) {
1577
- const inner = seqIndented(text, item);
1578
- if (inner !== void 0) return inner;
1579
- }
1580
- }
1581
- return void 0;
1582
- }
1583
- function contentEnd(text, node) {
1584
- if (isMap(node) && node.flow !== true) {
1585
- const last = node.items.at(-1);
1586
- if (last) return contentEnd(text, last.value) ?? contentEnd(text, last.key);
1587
- } else if (isSeq(node) && node.flow !== true) {
1588
- const last = node.items.at(-1);
1589
- if (last !== void 0) {
1590
- return isPair(last) ? contentEnd(text, last.value) ?? contentEnd(text, last.key) : contentEnd(text, last);
1591
- }
1592
- }
1593
- const r = rangeOf(node);
1594
- if (!r) return void 0;
1595
- let end = r[1];
1596
- while (end > r[0] && /\s/.test(text.charAt(end - 1))) end--;
1597
- return end;
1598
- }
1599
- function colonEnd(text, pair) {
1600
- const r = rangeOf(pair.key);
1601
- if (!r) return void 0;
1602
- let i = r[1];
1603
- while (text.charAt(i) === " " || text.charAt(i) === " ") i++;
1604
- return text.charAt(i) === ":" ? i + 1 : void 0;
1605
- }
1606
- function lineEnd2(text, i) {
1607
- const j = text.indexOf("\n", i);
1608
- if (j < 0) return text.length;
1609
- return j > 0 && text.charAt(j - 1) === "\r" ? j - 1 : j;
1610
- }
1611
- function colOf(text, offset) {
1612
- if (offset === 0) return 0;
1613
- let start = text.lastIndexOf("\n", offset - 1) + 1;
1614
- if (start === 0 && text.startsWith("\uFEFF")) start = 1;
1615
- return offset - start;
1616
- }
1617
- function rangeOf(node) {
1618
- return isNode(node) ? node.range ?? void 0 : void 0;
1619
- }
1620
- function emptySource(text, node) {
1621
- const r = rangeOf(node);
1622
- return !r || text.slice(r[0], r[1]).trim() === "";
1623
- }
1624
- function keyString(key) {
1625
- return isScalar(key) ? String(key.value) : String(key);
1626
- }
1627
- function isRecord(x) {
1628
- return typeof x === "object" && x !== null && !Array.isArray(x);
1629
- }
1630
-
1631
1240
  // src/cite/core/page.ts
1632
1241
  var MAX_MARKERS_PER_PAGE = 500;
1633
1242
  var ELEMENT_BACKED = /* @__PURE__ */ new Set(["html", "xml"]);
@@ -1653,11 +1262,11 @@ function validateEntry(entry) {
1653
1262
  if (!first) return "invalid entry";
1654
1263
  return `${first.instancePath} ${first.message ?? "is invalid"}`.trim();
1655
1264
  }
1656
- function isRecord2(value) {
1265
+ function isRecord(value) {
1657
1266
  return typeof value === "object" && value !== null && !Array.isArray(value);
1658
1267
  }
1659
1268
  function toCitation(entry) {
1660
- const source = isRecord2(entry.source) ? entry.source : {};
1269
+ const source = isRecord(entry.source) ? entry.source : {};
1661
1270
  const citation = {
1662
1271
  source: {
1663
1272
  file: String(source.file),
@@ -1671,7 +1280,7 @@ function toCitation(entry) {
1671
1280
  citation.source["commit-sha"] = source["commit-sha"];
1672
1281
  }
1673
1282
  if (typeof entry.id === "string") citation.id = entry.id;
1674
- if (isRecord2(entry.claim)) {
1283
+ if (isRecord(entry.claim)) {
1675
1284
  const claim = entry.claim;
1676
1285
  citation.claim = { integrity: String(claim.integrity) };
1677
1286
  if (typeof claim.lines === "number" || typeof claim.lines === "string") {
@@ -1703,9 +1312,9 @@ function named(id, text) {
1703
1312
  }
1704
1313
  function subjectOf(entry) {
1705
1314
  const out = {};
1706
- if (!isRecord2(entry)) return out;
1315
+ if (!isRecord(entry)) return out;
1707
1316
  if (typeof entry.id === "string") out.id = entry.id;
1708
- if (isRecord2(entry.source) && typeof entry.source.file === "string") out.src = entry.source.file;
1317
+ if (isRecord(entry.source) && typeof entry.source.file === "string") out.src = entry.source.file;
1709
1318
  return out;
1710
1319
  }
1711
1320
  function pickExtractor(file, format) {
@@ -1786,7 +1395,7 @@ function readPage(file, content, opts) {
1786
1395
  if (origin.kind === "manifest") where.file = origin.file;
1787
1396
  const entry = input.entry;
1788
1397
  const error = validateEntry(entry);
1789
- if (error !== void 0 || !isRecord2(entry)) {
1398
+ if (error !== void 0 || !isRecord(entry)) {
1790
1399
  findings.push(finding("entry-invalid", error ?? "must be object", { ...where, ...subjectOf(entry) }));
1791
1400
  return;
1792
1401
  }
@@ -2046,8 +1655,8 @@ function sidecarsFor(run3, over = {}) {
2046
1655
 
2047
1656
  // src/cite/core/manifest.ts
2048
1657
  import { readFile as readFile2 } from "node:fs/promises";
2049
- import { posix as posix2 } from "node:path";
2050
- import { LineCounter as LineCounter2, isMap as isMap2, isNode as isNode2, isScalar as isScalar2, isSeq as isSeq2, parseDocument as parseDocument2 } from "yaml";
1658
+ import { posix } from "node:path";
1659
+ import { LineCounter, isMap, isNode, isScalar, isSeq, parseDocument } from "yaml";
2051
1660
 
2052
1661
  // src/cite/core/write.ts
2053
1662
  function extractorFor(format) {
@@ -2159,12 +1768,12 @@ function spliceEntryField(content, format, index, path, value) {
2159
1768
  const out = content.slice(0, start) + parts.lead + spelled + parts.trail + content.slice(end);
2160
1769
  const check = extractor.extract(out, "page").data.citations;
2161
1770
  const entry = Array.isArray(check) ? check[index] : void 0;
2162
- const block = isRecord3(entry) ? entry[which] : void 0;
2163
- const readBack2 = isRecord3(block) ? block[field] : void 0;
2164
- if (readBack2 !== value) throw refuse();
1771
+ const block = isRecord2(entry) ? entry[which] : void 0;
1772
+ const readBack = isRecord2(block) ? block[field] : void 0;
1773
+ if (readBack !== value) throw refuse();
2165
1774
  return out;
2166
1775
  }
2167
- function isRecord3(value) {
1776
+ function isRecord2(value) {
2168
1777
  return typeof value === "object" && value !== null && !Array.isArray(value);
2169
1778
  }
2170
1779
  function insertStatementBefore(content, offset, statement) {
@@ -2290,25 +1899,25 @@ function splice(text, options) {
2290
1899
  }
2291
1900
  }
2292
1901
  function itemLine(text, entry, index, join4) {
2293
- const lc = new LineCounter2();
2294
- const doc = parseDocument2(text, { lineCounter: lc, uniqueKeys: false });
1902
+ const lc = new LineCounter();
1903
+ const doc = parseDocument(text, { lineCounter: lc, uniqueKeys: false });
2295
1904
  if (doc.errors.length > 0) return void 0;
2296
1905
  const root = doc.contents;
2297
- if (!isMap2(root)) return void 0;
1906
+ if (!isMap(root)) return void 0;
2298
1907
  const same = (key) => {
2299
- const spelled = isScalar2(key) ? String(key.value) : String(key);
2300
- return join4 === "path" ? posix2.normalize(spelled) === posix2.normalize(entry) : spelled === entry;
1908
+ const spelled = isScalar(key) ? String(key.value) : String(key);
1909
+ return join4 === "path" ? posix.normalize(spelled) === posix.normalize(entry) : spelled === entry;
2301
1910
  };
2302
1911
  const pair = root.items.find((p) => same(p.key));
2303
1912
  const value = pair?.value;
2304
- if (!isMap2(value)) return void 0;
1913
+ if (!isMap(value)) return void 0;
2305
1914
  const kv = value.items.find(
2306
- (p) => (isScalar2(p.key) ? String(p.key.value) : String(p.key)) === CITATIONS_KEY2
1915
+ (p) => (isScalar(p.key) ? String(p.key.value) : String(p.key)) === CITATIONS_KEY2
2307
1916
  );
2308
1917
  const list = kv?.value;
2309
- if (!isSeq2(list)) return void 0;
1918
+ if (!isSeq(list)) return void 0;
2310
1919
  const item = list.items[index];
2311
- const range = isNode2(item) ? item.range : void 0;
1920
+ const range = isNode(item) ? item.range : void 0;
2312
1921
  return range ? lc.linePos(range[0]).line : void 0;
2313
1922
  }
2314
1923
 
@@ -2509,7 +2118,7 @@ async function citedNow(input, source, entry) {
2509
2118
  if (read.kind === "missing") return void 0;
2510
2119
  const at2 = source.status === "moved" && source.newSrc !== void 0 ? parseSrc(source.newSrc) : range;
2511
2120
  try {
2512
- return sliceLines(splitLines(read.text), at2);
2121
+ return sliceLines2(splitLines(read.text), at2);
2513
2122
  } catch {
2514
2123
  return void 0;
2515
2124
  }
@@ -2713,7 +2322,7 @@ async function mintCitation(opts) {
2713
2322
  const index = opts.sourceIndex ?? await buildSourceIndex(opts.root, { gitClient: opts.gitClient });
2714
2323
  const read = await readSource(opts.root, index, range, key);
2715
2324
  if (read.kind === "missing") throw refusalFor(read.reason, range.path, range.encrypted);
2716
- const joined = sliceLines(splitLines(read.text), range, range.path);
2325
+ const joined = sliceLines2(splitLines(read.text), range, range.path);
2717
2326
  const pinKey = encrypt ? key : void 0;
2718
2327
  const file = pinKey !== void 0 && !range.encrypted ? encryptSourcePath(read.resolvedPath, pinKey) : range.path;
2719
2328
  const lines = rangeLines(range);
@@ -3137,7 +2746,7 @@ async function citedText(root, index, src, key) {
3137
2746
  if (source.kind === "missing") {
3138
2747
  throw new CiteError(`Source not readable: ${range.path} could not be read.`);
3139
2748
  }
3140
- return sliceLines(splitLines(source.text), range, range.path);
2749
+ return sliceLines2(splitLines(source.text), range, range.path);
3141
2750
  }
3142
2751
  async function runAdd(opts) {
3143
2752
  const cwd = resolve4(opts.cwd ?? process.cwd());
@@ -3376,12 +2985,12 @@ function apply(content, format, plan) {
3376
2985
  }
3377
2986
  }
3378
2987
  }
3379
- function isRecord4(value) {
2988
+ function isRecord3(value) {
3380
2989
  return typeof value === "object" && value !== null && !Array.isArray(value);
3381
2990
  }
3382
2991
  function applyToEntry(entry, plan) {
3383
- if (!isRecord4(entry)) return false;
3384
- const end = (name) => isRecord4(entry[name]) ? entry[name] : void 0;
2992
+ if (!isRecord3(entry)) return false;
2993
+ const end = (name) => isRecord3(entry[name]) ? entry[name] : void 0;
3385
2994
  switch (plan.kind) {
3386
2995
  case "claim-moved": {
3387
2996
  const claim = end("claim");
@@ -3622,7 +3231,7 @@ async function pinnedLines(ctx, citation, range, path, key) {
3622
3231
  const pin = citation.source.integrity;
3623
3232
  let joined;
3624
3233
  try {
3625
- joined = sliceLines(splitLines(source.text), range);
3234
+ joined = sliceLines2(splitLines(source.text), range);
3626
3235
  } catch {
3627
3236
  joined = void 0;
3628
3237
  }
@@ -4472,6 +4081,7 @@ export {
4472
4081
  meetsSeverity,
4473
4082
  reencryptMetadata,
4474
4083
  CiteError,
4084
+ asCiteError,
4475
4085
  spellSource,
4476
4086
  runAdd,
4477
4087
  runCheck,
@@ -4489,4 +4099,4 @@ export {
4489
4099
  runKeyRotate,
4490
4100
  runKeySet
4491
4101
  };
4492
- //# sourceMappingURL=chunk-QLVNSAHR.js.map
4102
+ //# sourceMappingURL=chunk-6JWX6JM2.js.map