@hawkeyexl/manni 2.4.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-LYLNOJ32.js → chunk-22WVCFP4.js} +2 -2
- package/dist/{chunk-LYLNOJ32.js.map → chunk-22WVCFP4.js.map} +1 -1
- package/dist/{chunk-Z2PBY6LN.js → chunk-KZDTVSFK.js} +2 -2
- package/dist/{chunk-P4KR3XFD.js → chunk-NFCH7HCA.js} +1354 -732
- package/dist/chunk-NFCH7HCA.js.map +1 -0
- package/dist/cli.js +14 -6
- package/dist/cli.js.map +1 -1
- package/dist/docmeta.js +2 -2
- package/dist/index.d.ts +181 -13
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-P4KR3XFD.js.map +0 -1
- /package/dist/{chunk-Z2PBY6LN.js.map → chunk-KZDTVSFK.js.map} +0 -0
|
@@ -71,7 +71,7 @@ import {
|
|
|
71
71
|
withValueAt,
|
|
72
72
|
writeEncryptionKey,
|
|
73
73
|
writeFileAtomic
|
|
74
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-22WVCFP4.js";
|
|
75
75
|
|
|
76
76
|
// src/meta/core/reencrypt.ts
|
|
77
77
|
import { extname } from "node:path";
|
|
@@ -220,16 +220,24 @@ __export(cite_exports, {
|
|
|
220
220
|
historyOf: () => historyOf,
|
|
221
221
|
insertStatementBefore: () => insertStatementBefore,
|
|
222
222
|
insideFence: () => insideFence,
|
|
223
|
+
isBoundLine: () => isBoundLine,
|
|
223
224
|
isCiteRule: () => isCiteRule,
|
|
224
225
|
isKeyedPin: () => isKeyedPin,
|
|
226
|
+
isUnitText: () => isUnitText,
|
|
225
227
|
itemLine: () => itemLine,
|
|
226
228
|
lineAt: () => lineAt,
|
|
227
229
|
lineSpec: () => lineSpec,
|
|
228
230
|
loadCitationSidecars: () => loadCitationSidecars,
|
|
229
231
|
loadCiteConfig: () => loadCiteConfig,
|
|
232
|
+
markerClaimEnd: () => markerClaimEnd,
|
|
233
|
+
markerRunAt: () => markerRunAt,
|
|
230
234
|
markerUnit: () => markerUnit,
|
|
231
235
|
messageFor: () => messageFor,
|
|
232
236
|
mintCitation: () => mintCitation,
|
|
237
|
+
misplacedMarkerAt: () => misplacedMarkerAt,
|
|
238
|
+
misplacedMarkers: () => misplacedMarkers,
|
|
239
|
+
misplacedMessageFor: () => misplacedMessageFor,
|
|
240
|
+
movedUnit: () => movedUnit,
|
|
233
241
|
noGit: () => noGit,
|
|
234
242
|
normalizeText: () => normalizeText,
|
|
235
243
|
normalizeWhitespace: () => normalizeWhitespace,
|
|
@@ -243,6 +251,7 @@ __export(cite_exports, {
|
|
|
243
251
|
parseStatements: () => parseStatements,
|
|
244
252
|
pickExtractor: () => pickExtractor,
|
|
245
253
|
pinOfLines: () => pinOfLines,
|
|
254
|
+
pre43Span: () => pre43Span,
|
|
246
255
|
rangeLines: () => rangeLines,
|
|
247
256
|
readCiteConfigFile: () => readCiteConfigFile,
|
|
248
257
|
readPage: () => readPage,
|
|
@@ -281,6 +290,7 @@ __export(cite_exports, {
|
|
|
281
290
|
splice: () => splice,
|
|
282
291
|
spliceEntryField: () => spliceEntryField,
|
|
283
292
|
splitLines: () => splitLines,
|
|
293
|
+
splitUnit: () => splitUnit,
|
|
284
294
|
statementForms: () => statementForms,
|
|
285
295
|
toBodyLines: () => toBodyLines,
|
|
286
296
|
toFileLines: () => toFileLines,
|
|
@@ -288,6 +298,7 @@ __export(cite_exports, {
|
|
|
288
298
|
twoManifestsRefusal: () => twoManifestsRefusal,
|
|
289
299
|
unifiedDiff: () => unifiedDiff,
|
|
290
300
|
unitAt: () => unitAt,
|
|
301
|
+
unitText: () => unitText,
|
|
291
302
|
urlManifestRefusal: () => urlManifestRefusal,
|
|
292
303
|
validateEntry: () => validateEntry,
|
|
293
304
|
withClaimLines: () => withClaimLines
|
|
@@ -306,6 +317,7 @@ var CITE_RULES = [
|
|
|
306
317
|
"marker-orphan",
|
|
307
318
|
"marker-invalid",
|
|
308
319
|
"marker-repeated",
|
|
320
|
+
"marker-misplaced",
|
|
309
321
|
"anchor-invalid",
|
|
310
322
|
"entry-invalid",
|
|
311
323
|
"quote-drift"
|
|
@@ -340,6 +352,7 @@ var DEFAULT_SEVERITY = {
|
|
|
340
352
|
"marker-orphan": "error",
|
|
341
353
|
"marker-invalid": "error",
|
|
342
354
|
"marker-repeated": "warning",
|
|
355
|
+
"marker-misplaced": "warning",
|
|
343
356
|
"anchor-invalid": "error",
|
|
344
357
|
"entry-invalid": "error",
|
|
345
358
|
"quote-drift": "error"
|
|
@@ -437,6 +450,13 @@ function shortLine(text) {
|
|
|
437
450
|
const one = text.replace(/\s+/g, " ").trim();
|
|
438
451
|
return one.length <= LINE_WIDTH ? one : `${one.slice(0, LINE_WIDTH)}\u2026`;
|
|
439
452
|
}
|
|
453
|
+
function spellAt(lines) {
|
|
454
|
+
return lines.start === lines.end ? `line ${String(lines.start)}` : `lines ${String(lines.start)}-${String(lines.end)}`;
|
|
455
|
+
}
|
|
456
|
+
function listOf(values) {
|
|
457
|
+
if (values.length <= 1) return values.join("");
|
|
458
|
+
return `${values.slice(0, -1).join(", ")} and ${values[values.length - 1] ?? ""}`;
|
|
459
|
+
}
|
|
440
460
|
|
|
441
461
|
// src/cite/core/sources.ts
|
|
442
462
|
import fg from "fast-glob";
|
|
@@ -524,6 +544,127 @@ async function readSource(root, index, range, key) {
|
|
|
524
544
|
}
|
|
525
545
|
}
|
|
526
546
|
|
|
547
|
+
// src/cite/core/page.ts
|
|
548
|
+
import { extname as extname2 } from "node:path";
|
|
549
|
+
import * as Ajv2020Ns from "ajv/dist/2020.js";
|
|
550
|
+
|
|
551
|
+
// src/cite/schema/citations.json
|
|
552
|
+
var citations_default = {
|
|
553
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
554
|
+
$id: "manni:citations:1.0.0-proposal.4",
|
|
555
|
+
title: "manni citations vocabulary (1.0.0-proposal.4)",
|
|
556
|
+
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.",
|
|
557
|
+
type: "object",
|
|
558
|
+
additionalProperties: true,
|
|
559
|
+
properties: {
|
|
560
|
+
citations: {
|
|
561
|
+
type: "array",
|
|
562
|
+
minItems: 1,
|
|
563
|
+
items: {
|
|
564
|
+
$ref: "#/$defs/citationEntry"
|
|
565
|
+
},
|
|
566
|
+
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.",
|
|
567
|
+
"x-manni-location": "external"
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
patternProperties: {
|
|
571
|
+
"^citation-": false
|
|
572
|
+
},
|
|
573
|
+
$defs: {
|
|
574
|
+
citationEntry: {
|
|
575
|
+
type: "object",
|
|
576
|
+
additionalProperties: false,
|
|
577
|
+
required: [
|
|
578
|
+
"source"
|
|
579
|
+
],
|
|
580
|
+
description: "One citation: the page text it supports, the source lines it rests on, and how the two are anchored. Both ends share one shape, a line range and a hash.",
|
|
581
|
+
properties: {
|
|
582
|
+
id: {
|
|
583
|
+
type: "string",
|
|
584
|
+
pattern: "^[a-z0-9][a-z0-9-]*$",
|
|
585
|
+
description: "A kebab-case name for this citation, unique within the page. A body marker names an entry by it, and findings are reported under it. Optional for an entry anchored by its claim lines or pinned bare. Required when a marker names the entry."
|
|
586
|
+
},
|
|
587
|
+
claim: {
|
|
588
|
+
$ref: "#/$defs/claim"
|
|
589
|
+
},
|
|
590
|
+
source: {
|
|
591
|
+
$ref: "#/$defs/source"
|
|
592
|
+
},
|
|
593
|
+
quote: {
|
|
594
|
+
type: "boolean",
|
|
595
|
+
default: false,
|
|
596
|
+
description: "The claim is a fenced block that reproduces the source lines. The pinned claim covers the whole block, fences included. An implementer compares the lines inside the fences with the source under the pin's rule. So a quoted snippet that drifts from its source is a finding even while the pin still holds. Under a marker, the block is the next fence after the marker. Default false."
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
claim: {
|
|
601
|
+
type: "object",
|
|
602
|
+
additionalProperties: false,
|
|
603
|
+
required: [
|
|
604
|
+
"integrity"
|
|
605
|
+
],
|
|
606
|
+
description: "The page text the citation supports, pinned by lines and a hash. Absent, the entry is a bare pin, or a marker with no drift check on its sentence. Without `lines`, the text pinned is what a body marker naming this entry anchors.",
|
|
607
|
+
properties: {
|
|
608
|
+
lines: {
|
|
609
|
+
$ref: "#/$defs/lines",
|
|
610
|
+
description: "Lines of the page body. The body starts on the first line after the closing frontmatter fence, or on line 1 with no frontmatter. So editing the frontmatter never moves a claim. Required unless a marker names the entry, and never inside the frontmatter."
|
|
611
|
+
},
|
|
612
|
+
integrity: {
|
|
613
|
+
type: "string",
|
|
614
|
+
pattern: "^sha256-[0-9a-f]{64}$",
|
|
615
|
+
description: "`sha256-` and sixty-four lowercase hex digits over the claimed lines, under the rule `source.integrity` states. Always plain, never keyed: the page is public, so there is nothing to hide. With a marker, the lines hashed are the ones the marker anchors."
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
source: {
|
|
620
|
+
type: "object",
|
|
621
|
+
additionalProperties: false,
|
|
622
|
+
required: [
|
|
623
|
+
"file",
|
|
624
|
+
"integrity"
|
|
625
|
+
],
|
|
626
|
+
description: "The lines the claim rests on: a file, an optional line range, a pin, and the commit the pin was taken at.",
|
|
627
|
+
properties: {
|
|
628
|
+
file: {
|
|
629
|
+
$ref: "#/$defs/fileRef"
|
|
630
|
+
},
|
|
631
|
+
lines: {
|
|
632
|
+
$ref: "#/$defs/lines",
|
|
633
|
+
description: "File lines. Absent, the whole file is pinned. The lines stay readable even when `file` is encrypted."
|
|
634
|
+
},
|
|
635
|
+
integrity: {
|
|
636
|
+
type: "string",
|
|
637
|
+
pattern: "^(?:sha256|hmac-sha256)-[0-9a-f]{64}$",
|
|
638
|
+
description: "The pin: an algorithm prefix and sixty-four lowercase hex digits over the cited lines under one rule. Decode as UTF-8 and drop one leading byte-order mark. Turn CRLF into LF, split on LF, and discard the empty element a trailing LF leaves. Take lines L1 to L2 inclusive, or every line for a whole file. Join with LF and no trailing LF, and keep trailing whitespace. A plain `file` is pinned `sha256-` over that text. An encrypted `file` is pinned `hmac-sha256-`: the HMAC-SHA256 of that text under a key derived from the encryption key. So a public page carries no verifier a reader could run against a guessed private line. The prefix follows `file`, and any other pairing is an invalid entry."
|
|
639
|
+
},
|
|
640
|
+
"commit-sha": {
|
|
641
|
+
type: "string",
|
|
642
|
+
pattern: "^[0-9a-f]{7,64}$",
|
|
643
|
+
description: "The git commit hash the source pin was taken at, as lowercase hex. Seven digits is what git abbreviates to; sixty-four covers a SHA-256 repository. A tool writes the full hash, because an abbreviation that is unique today need not be next year. Without it, a check can still say the lines changed. With it, the check can say since when, and whether the pin was ever true."
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
lines: {
|
|
648
|
+
oneOf: [
|
|
649
|
+
{
|
|
650
|
+
type: "integer",
|
|
651
|
+
minimum: 1
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
type: "string",
|
|
655
|
+
pattern: "^[1-9][0-9]*-[1-9][0-9]*$"
|
|
656
|
+
}
|
|
657
|
+
],
|
|
658
|
+
description: 'A line range, 1-based and inclusive: an integer for one line, or `"L1-L2"` for several. `L1 <= L2` is a rule on implementations, since a pattern cannot compare two numbers. There is no line 0 to cite.'
|
|
659
|
+
},
|
|
660
|
+
fileRef: {
|
|
661
|
+
type: "string",
|
|
662
|
+
pattern: "^(?:~[A-Za-z0-9_-]{82,}|(?!~)(?:(?!\\.\\.?(?:/|$))[^/\\\\:\\r\\n\\t]+)(?:/(?:(?!\\.\\.?(?:/|$))[^/\\\\:\\r\\n\\t]+))*)$",
|
|
663
|
+
description: "The file: a path relative to the repository root, posix-separated, and free of `.` and `..` segments. Segments may hold spaces and dots but not `/`, `\\`, `:` or a line break. So a URL, a drive letter, a Windows path and an absolute path all fail here. In place of the path, `~` and at least 82 base64url characters is the path encrypted with the family key. Proposal 0045 states the construction. Only a checkout with the key and the files can decrypt the path and read the lines. So a public docs repo can cite a private code repo without publishing its paths. The value carries no encryption mark, because the citation tool encrypts it, and a plain path stays valid."
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
|
|
527
668
|
// src/shared/code-regions.ts
|
|
528
669
|
var SKIP_MARKDOWN_FENCE = /^[ \t]*(`{3,}|~{3,})/;
|
|
529
670
|
var SKIP_ASCIIDOC_FENCE = /^(-{4,})/;
|
|
@@ -690,6 +831,25 @@ function isMarkerLine(line, format) {
|
|
|
690
831
|
return isCite(text.slice(form.open.length, closeAt).trim());
|
|
691
832
|
});
|
|
692
833
|
}
|
|
834
|
+
var ATX_HEADING = /^[ \t]*#{1,6}([ \t]|$)/;
|
|
835
|
+
var ASCIIDOC_TITLE = /^={1,6}[ \t]/;
|
|
836
|
+
var ADORNMENT = /^[ \t]*([-=~^"'*+#_:<>])(?:[ \t]*\1){2,}[ \t]*$/;
|
|
837
|
+
var TAG_LINE = /^[ \t]*<\/?[A-Za-z][^<>]*>[ \t]*$/;
|
|
838
|
+
function isBoundLine(line, format) {
|
|
839
|
+
if (ADORNMENT.test(line)) return true;
|
|
840
|
+
switch (format) {
|
|
841
|
+
case "markdown":
|
|
842
|
+
case "mdx":
|
|
843
|
+
return ATX_HEADING.test(line) || TAG_LINE.test(line);
|
|
844
|
+
case "html":
|
|
845
|
+
case "xml":
|
|
846
|
+
return TAG_LINE.test(line);
|
|
847
|
+
case "asciidoc":
|
|
848
|
+
return ASCIIDOC_TITLE.test(line);
|
|
849
|
+
default:
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
693
853
|
function parseStatements(body, format, from = { offset: 0, line: 1 }) {
|
|
694
854
|
const out = [];
|
|
695
855
|
const fileLine = (bodyOffset) => from.line + lineAt(body, bodyOffset) - 1;
|
|
@@ -732,6 +892,7 @@ function detectEol(text) {
|
|
|
732
892
|
}
|
|
733
893
|
function paragraphAfter(content, offset, format) {
|
|
734
894
|
const marker = (text) => format !== void 0 && isMarkerLine(text, format);
|
|
895
|
+
const bound = (text) => format !== void 0 && isBoundLine(text, format);
|
|
735
896
|
let pos = offset;
|
|
736
897
|
const firstEnd = lineEnd(content, pos);
|
|
737
898
|
if (lineText(content, pos, firstEnd).trim() === "") pos = firstEnd + 1;
|
|
@@ -745,11 +906,11 @@ function paragraphAfter(content, offset, format) {
|
|
|
745
906
|
if (ANY_FENCE.test(text)) return void 0;
|
|
746
907
|
const start = pos;
|
|
747
908
|
let last = end;
|
|
748
|
-
let cursor = end + 1;
|
|
909
|
+
let cursor = bound(text) ? content.length : end + 1;
|
|
749
910
|
while (cursor < content.length) {
|
|
750
911
|
const e = lineEnd(content, cursor);
|
|
751
912
|
const t = lineText(content, cursor, e);
|
|
752
|
-
if (t.trim() === "" || ANY_FENCE.test(t) || marker(t)) break;
|
|
913
|
+
if (t.trim() === "" || ANY_FENCE.test(t) || marker(t) || bound(t)) break;
|
|
753
914
|
last = e;
|
|
754
915
|
cursor = e + 1;
|
|
755
916
|
}
|
|
@@ -864,9 +1025,11 @@ function unitHolding(text, line, format, body) {
|
|
|
864
1025
|
}
|
|
865
1026
|
if (ANY_FENCE.test(own)) return void 0;
|
|
866
1027
|
let start = line;
|
|
867
|
-
while (start - 1 >= body.line) {
|
|
1028
|
+
while (start - 1 >= body.line && !isBoundLine(own, format)) {
|
|
868
1029
|
const above = textOf(start - 1);
|
|
869
|
-
if (above.trim() === "" || ANY_FENCE.test(above) || isMarkerLine(above, format))
|
|
1030
|
+
if (above.trim() === "" || ANY_FENCE.test(above) || isMarkerLine(above, format) || isBoundLine(above, format)) {
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
870
1033
|
start--;
|
|
871
1034
|
}
|
|
872
1035
|
const paragraph = paragraphAfter(text, offsetOfLine(text, start), format);
|
|
@@ -905,7 +1068,9 @@ function continuesList(text, line, format, bodyLine, column) {
|
|
|
905
1068
|
if (columnOf(indentOf(textOfLine(text, n))) > column) return true;
|
|
906
1069
|
for (; n >= bodyLine; n--) {
|
|
907
1070
|
const above = textOfLine(text, n);
|
|
908
|
-
if (above.trim() === "" || ANY_FENCE.test(above) || isMarkerLine(above, format))
|
|
1071
|
+
if (above.trim() === "" || ANY_FENCE.test(above) || isMarkerLine(above, format) || isBoundLine(above, format)) {
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
909
1074
|
const item = listItemOf(above);
|
|
910
1075
|
if (item !== void 0 && columnOf(item.lead) === column) return true;
|
|
911
1076
|
}
|
|
@@ -958,730 +1123,764 @@ function offsetOfLine(content, line) {
|
|
|
958
1123
|
return pos;
|
|
959
1124
|
}
|
|
960
1125
|
|
|
961
|
-
// src/cite/core/
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
return out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
969
|
-
}
|
|
970
|
-
function blockMatches(blockText, citedJoined) {
|
|
971
|
-
return normalizeBlock(blockText) === normalizeBlock(citedJoined);
|
|
972
|
-
}
|
|
973
|
-
function textAt(lines, range) {
|
|
974
|
-
return lines.slice(range.start - 1, Math.min(range.end, lines.length));
|
|
1126
|
+
// src/cite/core/page.ts
|
|
1127
|
+
var MAX_MARKERS_PER_PAGE = 500;
|
|
1128
|
+
var ELEMENT_BACKED = /* @__PURE__ */ new Set(["html", "xml"]);
|
|
1129
|
+
var FENCE_ONLY = /* @__PURE__ */ new Set(["markdown", "mdx"]);
|
|
1130
|
+
var MARKER_JSON = "A marker names an entry by id. Write the entry in frontmatter or the sidecar.";
|
|
1131
|
+
function ownedMessage(owner) {
|
|
1132
|
+
return `"citations" is owned by manifest ${owner.file} (collection ${owner.collection}); remove it from the document`;
|
|
975
1133
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1134
|
+
var Ajv2020 = Ajv2020Ns.default;
|
|
1135
|
+
var entryValidator;
|
|
1136
|
+
function compileEntryValidator() {
|
|
1137
|
+
if (entryValidator) return entryValidator;
|
|
1138
|
+
const ajv = new Ajv2020({ allErrors: false, strict: false });
|
|
1139
|
+
ajv.addSchema(citations_default);
|
|
1140
|
+
entryValidator = ajv.compile({
|
|
1141
|
+
$ref: `${citations_default.$id}#/$defs/citationEntry`
|
|
1142
|
+
});
|
|
1143
|
+
return entryValidator;
|
|
979
1144
|
}
|
|
980
|
-
function
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
1145
|
+
function validateEntry(entry) {
|
|
1146
|
+
const validate = compileEntryValidator();
|
|
1147
|
+
if (validate(entry)) return void 0;
|
|
1148
|
+
const first = validate.errors?.[0];
|
|
1149
|
+
if (!first) return "invalid entry";
|
|
1150
|
+
return `${first.instancePath} ${first.message ?? "is invalid"}`.trim();
|
|
984
1151
|
}
|
|
985
|
-
function
|
|
986
|
-
|
|
987
|
-
return spec === void 0 ? void 0 : parseLines(spec);
|
|
1152
|
+
function isRecord(value) {
|
|
1153
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
988
1154
|
}
|
|
989
|
-
function
|
|
990
|
-
const
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
if (claim.lines !== void 0) {
|
|
996
|
-
const recorded = parseLines(claim.lines);
|
|
997
|
-
if (recorded === void 0) return { status: "skipped" };
|
|
998
|
-
const file = toFileLines(recorded, body);
|
|
999
|
-
const end = {
|
|
1000
|
-
lines: spellLines(recorded),
|
|
1001
|
-
fileLines: spellLines(file),
|
|
1002
|
-
status: "changed"
|
|
1003
|
-
};
|
|
1004
|
-
if (marker !== void 0) return { ...end, status: "skipped" };
|
|
1005
|
-
if (pinOfLines(lines, file) === claim.integrity) return { ...end, status: "current" };
|
|
1006
|
-
const width = recorded.end - recorded.start + 1;
|
|
1007
|
-
const found = findWindows(lines.slice(body - 1), width, claim.integrity, void 0, {
|
|
1008
|
-
around: recorded.start
|
|
1009
|
-
});
|
|
1010
|
-
const spans = found.starts.map((start) => ({ start, end: start + width - 1 }));
|
|
1011
|
-
const [only] = spans;
|
|
1012
|
-
if (spans.length === 1 && only !== void 0) {
|
|
1013
|
-
return {
|
|
1014
|
-
...end,
|
|
1015
|
-
status: "moved",
|
|
1016
|
-
newLines: spellLines(only),
|
|
1017
|
-
newFileLines: spellLines(toFileLines(only, body))
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
if (spans.length > 1) {
|
|
1021
|
-
return {
|
|
1022
|
-
...end,
|
|
1023
|
-
status: "moved-ambiguous",
|
|
1024
|
-
candidates: spans.map(spellLines),
|
|
1025
|
-
candidateFileLines: spans.map((span) => spellLines(toFileLines(span, body)))
|
|
1026
|
-
};
|
|
1155
|
+
function toCitation(entry) {
|
|
1156
|
+
const source = isRecord(entry.source) ? entry.source : {};
|
|
1157
|
+
const citation = {
|
|
1158
|
+
source: {
|
|
1159
|
+
file: String(source.file),
|
|
1160
|
+
integrity: String(source.integrity)
|
|
1027
1161
|
}
|
|
1028
|
-
|
|
1162
|
+
};
|
|
1163
|
+
if (typeof source.lines === "number" || typeof source.lines === "string") {
|
|
1164
|
+
citation.source.lines = source.lines;
|
|
1029
1165
|
}
|
|
1030
|
-
if (
|
|
1031
|
-
|
|
1032
|
-
if (unit === void 0) return { status: "changed" };
|
|
1033
|
-
const end = { fileLines: spellLines(unit), status: "changed" };
|
|
1034
|
-
if (pinOfLines(lines, unit) === claim.integrity) return { ...end, status: "current" };
|
|
1035
|
-
return { ...end, text: textAt(lines, unit) };
|
|
1166
|
+
if (typeof source["commit-sha"] === "string") {
|
|
1167
|
+
citation.source["commit-sha"] = source["commit-sha"];
|
|
1036
1168
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
return { lines: block, kind: "block", text: lines.slice(block.start - 1, block.end) };
|
|
1169
|
+
if (typeof entry.id === "string") citation.id = entry.id;
|
|
1170
|
+
if (isRecord(entry.claim)) {
|
|
1171
|
+
const claim = entry.claim;
|
|
1172
|
+
citation.claim = { integrity: String(claim.integrity) };
|
|
1173
|
+
if (typeof claim.lines === "number" || typeof claim.lines === "string") {
|
|
1174
|
+
citation.claim.lines = claim.lines;
|
|
1175
|
+
}
|
|
1045
1176
|
}
|
|
1046
|
-
if (
|
|
1047
|
-
|
|
1048
|
-
if (paragraph === void 0 || paragraph.line !== line) return void 0;
|
|
1049
|
-
const span = { start: line, end: lineAt(page.content, paragraph.end) };
|
|
1050
|
-
return { lines: span, kind: "paragraph", text: lines.slice(span.start - 1, span.end) };
|
|
1177
|
+
if (typeof entry.quote === "boolean") citation.quote = entry.quote;
|
|
1178
|
+
return citation;
|
|
1051
1179
|
}
|
|
1052
|
-
function
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1180
|
+
function finding(rule, message, extra) {
|
|
1181
|
+
const level = DEFAULT_SEVERITY[rule];
|
|
1182
|
+
const out = {
|
|
1183
|
+
rule,
|
|
1184
|
+
ruleId: ruleId(rule),
|
|
1185
|
+
// The caller re-applies the configured severity table; no page-side rule
|
|
1186
|
+
// defaults to `off`, so there is nothing to collapse.
|
|
1187
|
+
severity: level === "off" ? "error" : level,
|
|
1188
|
+
message
|
|
1061
1189
|
};
|
|
1190
|
+
if (extra.line !== void 0) out.line = extra.line;
|
|
1191
|
+
if (extra.file !== void 0) out.file = extra.file;
|
|
1192
|
+
if (extra.id !== void 0) out.id = extra.id;
|
|
1193
|
+
if (extra.src !== void 0) out.src = extra.src;
|
|
1194
|
+
if (extra.index !== void 0) out.index = extra.index;
|
|
1195
|
+
return out;
|
|
1062
1196
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
import { execFile } from "node:child_process";
|
|
1066
|
-
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
1067
|
-
var NO_COMMIT = /invalid object name|bad revision|unknown revision|bad object/i;
|
|
1068
|
-
var NO_PATH = /does not exist in|exists on disk, but not in|path .* does not exist/i;
|
|
1069
|
-
function run2(root, args) {
|
|
1070
|
-
return new Promise((resolve9) => {
|
|
1071
|
-
execFile(
|
|
1072
|
-
"git",
|
|
1073
|
-
["-c", "core.quotepath=false", ...args],
|
|
1074
|
-
{ cwd: root, windowsHide: true, maxBuffer: MAX_BUFFER, encoding: "utf8" },
|
|
1075
|
-
(error, stdout, stderr) => {
|
|
1076
|
-
if (error) {
|
|
1077
|
-
const failure = { ok: false, stderr, message: error.message };
|
|
1078
|
-
if (typeof error.code === "number") failure.code = error.code;
|
|
1079
|
-
resolve9(failure);
|
|
1080
|
-
} else resolve9({ ok: true, stdout });
|
|
1081
|
-
}
|
|
1082
|
-
);
|
|
1083
|
-
});
|
|
1084
|
-
}
|
|
1085
|
-
function gitClient(root) {
|
|
1086
|
-
const memo = /* @__PURE__ */ new Map();
|
|
1087
|
-
const once = (key, compute) => {
|
|
1088
|
-
const held = memo.get(key);
|
|
1089
|
-
if (held !== void 0) return held;
|
|
1090
|
-
const made = compute();
|
|
1091
|
-
memo.set(key, made);
|
|
1092
|
-
return made;
|
|
1093
|
-
};
|
|
1094
|
-
const available = () => once("available", async () => (await run2(root, ["rev-parse", "--show-toplevel"])).ok);
|
|
1095
|
-
const failed = (verb, outcome) => new CiteError(`git ${verb} failed: ${outcome.stderr.trim() || outcome.message}`);
|
|
1096
|
-
const hasCommit = (commit) => once(`has\0${commit}`, async () => {
|
|
1097
|
-
const out = await run2(root, ["rev-parse", "--verify", "--quiet", "--end-of-options", `${commit}^{commit}`]);
|
|
1098
|
-
if (out.ok) return true;
|
|
1099
|
-
if (out.code === 1) return false;
|
|
1100
|
-
throw failed("rev-parse", out);
|
|
1101
|
-
});
|
|
1102
|
-
return {
|
|
1103
|
-
available,
|
|
1104
|
-
head: () => once("head", async () => {
|
|
1105
|
-
if (!await available()) return null;
|
|
1106
|
-
const out = await run2(root, ["rev-parse", "HEAD"]);
|
|
1107
|
-
return out.ok ? out.stdout.trim() : null;
|
|
1108
|
-
}),
|
|
1109
|
-
lsFiles: () => once("ls-files", async () => {
|
|
1110
|
-
if (!await available()) return [];
|
|
1111
|
-
const out = await run2(root, ["ls-files", "-z"]);
|
|
1112
|
-
if (!out.ok) throw failed("ls-files", out);
|
|
1113
|
-
return out.stdout.split("\0").filter((entry) => entry !== "");
|
|
1114
|
-
}),
|
|
1115
|
-
showFile: (commit, path) => once(`show\0${commit}\0${path}`, async () => {
|
|
1116
|
-
const out = await run2(root, ["show", "--end-of-options", `${commit}:./${path}`]);
|
|
1117
|
-
if (out.ok) return { text: out.stdout };
|
|
1118
|
-
if (NO_COMMIT.test(out.stderr)) return { missing: "commit" };
|
|
1119
|
-
if (NO_PATH.test(out.stderr)) {
|
|
1120
|
-
return await hasCommit(commit) ? { missing: "path" } : { missing: "commit" };
|
|
1121
|
-
}
|
|
1122
|
-
throw failed("show", out);
|
|
1123
|
-
}),
|
|
1124
|
-
subjectsSince: (commit, path) => once(`log\0${commit}\0${path}`, async () => {
|
|
1125
|
-
const out = await run2(root, ["log", "--format=%s", "--end-of-options", `${commit}..HEAD`, "--", path]);
|
|
1126
|
-
if (!out.ok) throw failed("log", out);
|
|
1127
|
-
return out.stdout.split(/\r?\n/).filter((line) => line !== "");
|
|
1128
|
-
}),
|
|
1129
|
-
diffSince: (commit, path) => once(`diff\0${commit}\0${path}`, async () => {
|
|
1130
|
-
const out = await run2(root, ["diff", "--end-of-options", commit, "--", path]);
|
|
1131
|
-
if (!out.ok) throw failed("diff", out);
|
|
1132
|
-
return out.stdout;
|
|
1133
|
-
})
|
|
1134
|
-
};
|
|
1197
|
+
function named(id, text) {
|
|
1198
|
+
return id === void 0 ? text : `${id}: ${text}`;
|
|
1135
1199
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
lsFiles: () => Promise.resolve([]),
|
|
1143
|
-
showFile: () => Promise.resolve({ missing: "commit" }),
|
|
1144
|
-
subjectsSince: () => Promise.resolve([]),
|
|
1145
|
-
diffSince: () => Promise.resolve("")
|
|
1146
|
-
};
|
|
1200
|
+
function subjectOf(entry) {
|
|
1201
|
+
const out = {};
|
|
1202
|
+
if (!isRecord(entry)) return out;
|
|
1203
|
+
if (typeof entry.id === "string") out.id = entry.id;
|
|
1204
|
+
if (isRecord(entry.source) && typeof entry.source.file === "string") out.src = entry.source.file;
|
|
1205
|
+
return out;
|
|
1147
1206
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
try {
|
|
1158
|
-
joined = sliceLines2(then, range);
|
|
1159
|
-
} catch {
|
|
1160
|
-
joined = void 0;
|
|
1161
|
-
}
|
|
1162
|
-
if (joined !== void 0 && hashLines(joined, key) === pin) {
|
|
1163
|
-
return { kind: "original", lines: joined.split("\n") };
|
|
1207
|
+
function pickExtractor(file, format) {
|
|
1208
|
+
if (format !== void 0) {
|
|
1209
|
+
const forced = extractorByName(format);
|
|
1210
|
+
if (forced === void 0) {
|
|
1211
|
+
throw new CiteError(
|
|
1212
|
+
`Unknown format "${format}". Supported extensions: ${supportedExtensions().join(", ")}.`
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
return forced;
|
|
1164
1216
|
}
|
|
1165
|
-
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
if (first !== void 0) {
|
|
1172
|
-
return { kind: "original", lines: then.slice(first - 1, first - 1 + length) };
|
|
1217
|
+
const extension = extname2(file);
|
|
1218
|
+
const byExt = extractorForExtension(extension);
|
|
1219
|
+
if (!byExt) {
|
|
1220
|
+
throw new CiteError(
|
|
1221
|
+
`Unsupported file type "${extension}" for "${file}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
|
|
1222
|
+
);
|
|
1173
1223
|
}
|
|
1174
|
-
return
|
|
1224
|
+
return byExt;
|
|
1175
1225
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1226
|
+
function byLine(a, b) {
|
|
1227
|
+
return (a.line ?? Number.MAX_SAFE_INTEGER) - (b.line ?? Number.MAX_SAFE_INTEGER);
|
|
1228
|
+
}
|
|
1229
|
+
function bodyLineOf(content, bodyOffset) {
|
|
1230
|
+
if (bodyOffset <= 0) return 1;
|
|
1231
|
+
const line = lineAt(content, bodyOffset);
|
|
1232
|
+
return content.charCodeAt(bodyOffset - 1) === 10 ? line : line + 1;
|
|
1233
|
+
}
|
|
1234
|
+
function citationInputs(file, extracted, injected) {
|
|
1235
|
+
if (injected !== void 0) return [...injected];
|
|
1236
|
+
const raw = extracted.data.citations;
|
|
1237
|
+
const list = Array.isArray(raw) ? raw : [];
|
|
1238
|
+
return list.map((entry, index) => {
|
|
1239
|
+
const origin = { kind: "frontmatter", file };
|
|
1240
|
+
const line = extracted.lineFor(`/citations/${String(index)}`);
|
|
1241
|
+
if (line !== void 0) origin.line = line;
|
|
1242
|
+
return { entry, origin };
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
function readPage(file, content, opts) {
|
|
1246
|
+
const extractor = pickExtractor(file, opts?.format);
|
|
1247
|
+
const format = extractor.name;
|
|
1248
|
+
if (FENCE_ONLY.has(format) && hasFrontmatterFence(content) && locateFrontmatter(content) === null) {
|
|
1249
|
+
throw new CiteError(
|
|
1250
|
+
`${file}: Unterminated front matter fence: the opening fence has no matching close, so the page's citations cannot be read. Add a closing fence.`
|
|
1251
|
+
);
|
|
1186
1252
|
}
|
|
1187
|
-
|
|
1188
|
-
const
|
|
1189
|
-
const
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1253
|
+
const extracted = extractor.extract(content, file);
|
|
1254
|
+
const data = extracted.data;
|
|
1255
|
+
const bodyOffset = ELEMENT_BACKED.has(format) ? 0 : locateFrontmatter(content)?.closeEnd ?? 0;
|
|
1256
|
+
const bodyLine = bodyLineOf(content, bodyOffset);
|
|
1257
|
+
const findings = [];
|
|
1258
|
+
const citations = [];
|
|
1259
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1260
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1261
|
+
const markedAt = /* @__PURE__ */ new Map();
|
|
1262
|
+
for (const key of Object.keys(data)) {
|
|
1263
|
+
if (!key.startsWith("citation-")) continue;
|
|
1264
|
+
findings.push(
|
|
1265
|
+
finding(
|
|
1266
|
+
"entry-invalid",
|
|
1267
|
+
`"${key}" is not a citations key; the page root reserves the citation- prefix`,
|
|
1268
|
+
{ line: extracted.lineFor(`/${key}`) }
|
|
1269
|
+
)
|
|
1270
|
+
);
|
|
1196
1271
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1272
|
+
const injected = opts?.citations;
|
|
1273
|
+
const rawCitations = data.citations;
|
|
1274
|
+
if (opts?.owned !== void 0 && rawCitations !== void 0) {
|
|
1275
|
+
findings.push(
|
|
1276
|
+
finding("entry-invalid", ownedMessage(opts.owned), {
|
|
1277
|
+
line: extracted.lineFor("/citations")
|
|
1278
|
+
})
|
|
1279
|
+
);
|
|
1200
1280
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1281
|
+
if (injected === void 0 && rawCitations !== void 0 && !Array.isArray(rawCitations)) {
|
|
1282
|
+
findings.push(
|
|
1283
|
+
finding("entry-invalid", "citations must be an array of entries", {
|
|
1284
|
+
line: extracted.lineFor("/citations")
|
|
1285
|
+
})
|
|
1286
|
+
);
|
|
1204
1287
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
const
|
|
1208
|
-
const
|
|
1209
|
-
if (
|
|
1210
|
-
if (
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
result.status = "moved";
|
|
1217
|
-
result.newSrc = spell(only);
|
|
1218
|
-
result.newLines = String(lineSpec({ start: only, end: only + (end - start) }));
|
|
1219
|
-
return result;
|
|
1288
|
+
const inputs = citationInputs(file, extracted, injected);
|
|
1289
|
+
inputs.forEach((input, index) => {
|
|
1290
|
+
const origin = { ...input.origin, index };
|
|
1291
|
+
const where = { index };
|
|
1292
|
+
if (origin.line !== void 0) where.line = origin.line;
|
|
1293
|
+
if (origin.kind === "manifest") where.file = origin.file;
|
|
1294
|
+
const entry = input.entry;
|
|
1295
|
+
const error = validateEntry(entry);
|
|
1296
|
+
if (error !== void 0 || !isRecord(entry)) {
|
|
1297
|
+
findings.push(finding("entry-invalid", error ?? "must be object", { ...where, ...subjectOf(entry) }));
|
|
1298
|
+
return;
|
|
1220
1299
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1300
|
+
const citation = toCitation(entry);
|
|
1301
|
+
const subject = { ...where, src: spellSource(citation.source) };
|
|
1302
|
+
if (citation.id !== void 0) subject.id = citation.id;
|
|
1303
|
+
const bad = ["claim", "source"].find((end) => {
|
|
1304
|
+
const lines = end === "claim" ? citation.claim?.lines : citation.source.lines;
|
|
1305
|
+
return lines !== void 0 && parseLines(lines) === void 0;
|
|
1306
|
+
});
|
|
1307
|
+
if (bad !== void 0) {
|
|
1308
|
+
const lines = bad === "claim" ? citation.claim?.lines : citation.source.lines;
|
|
1309
|
+
findings.push(
|
|
1310
|
+
finding(
|
|
1311
|
+
"entry-invalid",
|
|
1312
|
+
named(citation.id, `${bad}.lines "${String(lines)}" ends before it starts`),
|
|
1313
|
+
subject
|
|
1314
|
+
)
|
|
1315
|
+
);
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
const wide = ["claim", "source"].map((end) => {
|
|
1319
|
+
const lines = end === "claim" ? citation.claim?.lines : citation.source.lines;
|
|
1320
|
+
const parsed = lines === void 0 ? void 0 : parseLines(lines);
|
|
1321
|
+
const reason = parsed === void 0 ? void 0 : tooWide(parsed);
|
|
1322
|
+
return reason === void 0 ? void 0 : `${end}.lines "${String(lines)}" ${reason}`;
|
|
1323
|
+
}).find((message) => message !== void 0);
|
|
1324
|
+
if (wide !== void 0) {
|
|
1325
|
+
findings.push(finding("entry-invalid", named(citation.id, wide), subject));
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
const encrypted = isEncryptedValue(citation.source.file);
|
|
1329
|
+
if (encrypted !== isKeyedPin(citation.source.integrity)) {
|
|
1330
|
+
findings.push(
|
|
1331
|
+
finding(
|
|
1332
|
+
"entry-invalid",
|
|
1333
|
+
named(
|
|
1334
|
+
citation.id,
|
|
1335
|
+
encrypted ? "an encrypted source is pinned with hmac-sha256-, not sha256-." : "a plain source is pinned with sha256-, not hmac-sha256-."
|
|
1336
|
+
),
|
|
1337
|
+
subject
|
|
1338
|
+
)
|
|
1339
|
+
);
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
const page = { citation, origin };
|
|
1343
|
+
citations.push(page);
|
|
1344
|
+
if (citation.id !== void 0) {
|
|
1345
|
+
if (ids.has(citation.id)) {
|
|
1346
|
+
findings.push(finding("entry-invalid", `duplicate id "${citation.id}"`, subject));
|
|
1347
|
+
} else {
|
|
1348
|
+
ids.add(citation.id);
|
|
1349
|
+
byId.set(citation.id, page);
|
|
1350
|
+
}
|
|
1225
1351
|
}
|
|
1352
|
+
});
|
|
1353
|
+
const body = content.slice(bodyOffset);
|
|
1354
|
+
const statements = parseStatements(body, format, {
|
|
1355
|
+
offset: bodyOffset,
|
|
1356
|
+
line: bodyLine
|
|
1357
|
+
});
|
|
1358
|
+
if (statements.length > MAX_MARKERS_PER_PAGE) {
|
|
1359
|
+
findings.push(
|
|
1360
|
+
finding(
|
|
1361
|
+
"marker-invalid",
|
|
1362
|
+
`more than ${String(MAX_MARKERS_PER_PAGE)} markers on one page (${String(statements.length)}); the rest are not read`,
|
|
1363
|
+
{ line: statements[MAX_MARKERS_PER_PAGE]?.line }
|
|
1364
|
+
)
|
|
1365
|
+
);
|
|
1226
1366
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1367
|
+
for (const statement of statements.slice(0, MAX_MARKERS_PER_PAGE)) {
|
|
1368
|
+
const { payload } = statement;
|
|
1369
|
+
if (payload.kind === "bad") {
|
|
1370
|
+
findings.push(
|
|
1371
|
+
finding(
|
|
1372
|
+
"marker-invalid",
|
|
1373
|
+
payload.json === true ? MARKER_JSON : `invalid marker: ${payload.reason}`,
|
|
1374
|
+
{ line: statement.line }
|
|
1375
|
+
)
|
|
1376
|
+
);
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
const target = byId.get(payload.id);
|
|
1380
|
+
if (target === void 0) {
|
|
1381
|
+
findings.push(
|
|
1382
|
+
finding("marker-orphan", `no entry has id "${payload.id}"`, {
|
|
1383
|
+
line: statement.line,
|
|
1384
|
+
id: payload.id
|
|
1385
|
+
})
|
|
1386
|
+
);
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
const first = markedAt.get(target);
|
|
1390
|
+
if (first !== void 0) {
|
|
1391
|
+
findings.push(
|
|
1392
|
+
finding(
|
|
1393
|
+
"marker-repeated",
|
|
1394
|
+
`${payload.id} is named by markers at lines ${String(first)} and ${String(statement.line)}; the first anchors it.`,
|
|
1395
|
+
{
|
|
1396
|
+
line: statement.line,
|
|
1397
|
+
id: payload.id,
|
|
1398
|
+
index: target.origin.index,
|
|
1399
|
+
src: spellSource(target.citation.source)
|
|
1400
|
+
}
|
|
1401
|
+
)
|
|
1402
|
+
);
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
markedAt.set(target, statement.line);
|
|
1406
|
+
target.marker = statement;
|
|
1230
1407
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1408
|
+
for (const entry of citations) {
|
|
1409
|
+
const { citation, origin, marker } = entry;
|
|
1410
|
+
const subject = { index: origin.index, src: spellSource(citation.source) };
|
|
1411
|
+
if (citation.id !== void 0) subject.id = citation.id;
|
|
1412
|
+
if (origin.line !== void 0) subject.line = origin.line;
|
|
1413
|
+
if (origin.kind === "manifest") subject.file = origin.file;
|
|
1414
|
+
if (citation.claim?.lines !== void 0 && marker !== void 0) {
|
|
1415
|
+
const onPage = { index: origin.index, line: marker.line, src: subject.src };
|
|
1416
|
+
if (citation.id !== void 0) onPage.id = citation.id;
|
|
1417
|
+
findings.push(
|
|
1418
|
+
finding(
|
|
1419
|
+
"anchor-invalid",
|
|
1420
|
+
`${citation.id ?? "the entry"} has claim lines and a marker. Keep one.`,
|
|
1421
|
+
onPage
|
|
1422
|
+
)
|
|
1423
|
+
);
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
if (citation.quote === true && citation.claim?.lines === void 0 && marker === void 0) {
|
|
1427
|
+
findings.push(
|
|
1428
|
+
finding("anchor-invalid", named(citation.id, "quote needs a claim or a marker."), subject)
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1238
1431
|
}
|
|
1239
|
-
return
|
|
1432
|
+
return {
|
|
1433
|
+
file,
|
|
1434
|
+
format,
|
|
1435
|
+
content,
|
|
1436
|
+
bodyOffset,
|
|
1437
|
+
bodyLine,
|
|
1438
|
+
citations,
|
|
1439
|
+
statements,
|
|
1440
|
+
findings: findings.sort(byLine)
|
|
1441
|
+
};
|
|
1240
1442
|
}
|
|
1241
1443
|
|
|
1242
|
-
// src/cite/core/
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1444
|
+
// src/cite/core/reanchor.ts
|
|
1445
|
+
function isUnitText(line, format) {
|
|
1446
|
+
if (line === void 0) return false;
|
|
1447
|
+
if (line.trim() === "") return false;
|
|
1448
|
+
if (ANY_FENCE.test(line)) return false;
|
|
1449
|
+
if (isMarkerLine(line, format)) return false;
|
|
1450
|
+
return !isBoundLine(line, format);
|
|
1451
|
+
}
|
|
1452
|
+
function markerAt(lines, n, format) {
|
|
1453
|
+
const line = lines[n - 1];
|
|
1454
|
+
return line !== void 0 && isMarkerLine(line, format);
|
|
1455
|
+
}
|
|
1456
|
+
function markerRunAt(lines, line, format, bodyLine) {
|
|
1457
|
+
let start = line;
|
|
1458
|
+
while (start - 1 >= bodyLine && markerAt(lines, start - 1, format)) start--;
|
|
1459
|
+
let end = line;
|
|
1460
|
+
while (end + 1 <= lines.length && markerAt(lines, end + 1, format)) end++;
|
|
1461
|
+
return { start, end };
|
|
1462
|
+
}
|
|
1463
|
+
function splitUnit(lines, run3, format, bodyLine) {
|
|
1464
|
+
if (run3.start - 1 < bodyLine) return void 0;
|
|
1465
|
+
if (!isUnitText(lines[run3.start - 2], format)) return void 0;
|
|
1466
|
+
if (!isUnitText(lines[run3.end], format)) return void 0;
|
|
1467
|
+
let start = run3.start - 1;
|
|
1468
|
+
while (start - 1 >= bodyLine && isUnitText(lines[start - 2], format)) start--;
|
|
1469
|
+
let end = run3.end + 1;
|
|
1470
|
+
while (end + 1 <= lines.length && isUnitText(lines[end], format)) end++;
|
|
1471
|
+
return { start, end };
|
|
1472
|
+
}
|
|
1473
|
+
function pre43Span(lines, markerLine) {
|
|
1474
|
+
const start = markerLine + 1;
|
|
1475
|
+
const first = lines[start - 1];
|
|
1476
|
+
if (first === void 0 || first.trim() === "" || ANY_FENCE.test(first)) return void 0;
|
|
1477
|
+
let end = start;
|
|
1478
|
+
while (end + 1 <= lines.length) {
|
|
1479
|
+
const next = lines[end];
|
|
1480
|
+
if (next === void 0 || next.trim() === "" || ANY_FENCE.test(next)) break;
|
|
1481
|
+
end++;
|
|
1482
|
+
}
|
|
1483
|
+
return { start, end };
|
|
1484
|
+
}
|
|
1485
|
+
function runLength(run3) {
|
|
1486
|
+
return run3.end - run3.start + 1;
|
|
1487
|
+
}
|
|
1488
|
+
function misplacedMarkerAt(page, lines, line, entry) {
|
|
1489
|
+
if (!markerAt(lines, line, page.format)) return void 0;
|
|
1490
|
+
const run3 = markerRunAt(lines, line, page.format, page.bodyLine);
|
|
1491
|
+
const unit = splitUnit(lines, run3, page.format, page.bodyLine);
|
|
1492
|
+
if (unit === void 0) return void 0;
|
|
1493
|
+
const order = line - run3.start;
|
|
1494
|
+
const quote = entry?.citation.quote === true;
|
|
1495
|
+
const marker = entry?.marker;
|
|
1496
|
+
const blockAt = quote && marker !== void 0 ? anchoredLines(page.content, marker.end, page.format, true)?.start : void 0;
|
|
1497
|
+
const out = blockAt === void 0 ? { line, run: run3, order, unit, place: unit.start, block: false, to: unit.start + order } : {
|
|
1498
|
+
line,
|
|
1499
|
+
run: run3,
|
|
1500
|
+
order,
|
|
1501
|
+
unit,
|
|
1502
|
+
place: blockAt,
|
|
1503
|
+
block: true,
|
|
1504
|
+
to: blockAt - runLength(run3) + order
|
|
1505
|
+
};
|
|
1506
|
+
if (entry !== void 0) out.entry = entry;
|
|
1507
|
+
return out;
|
|
1508
|
+
}
|
|
1509
|
+
function misplacedMarkers(page, lines) {
|
|
1510
|
+
const out = [];
|
|
1511
|
+
for (const statement of page.statements.slice(0, MAX_MARKERS_PER_PAGE)) {
|
|
1512
|
+
const entry = page.citations.find((c) => c.marker?.line === statement.line);
|
|
1513
|
+
const found = misplacedMarkerAt(page, lines, statement.line, entry);
|
|
1514
|
+
if (found !== void 0) out.push(found);
|
|
1515
|
+
}
|
|
1516
|
+
return out;
|
|
1517
|
+
}
|
|
1518
|
+
function movedUnit(misplaced) {
|
|
1519
|
+
return { start: misplaced.unit.start + runLength(misplaced.run), end: misplaced.unit.end };
|
|
1520
|
+
}
|
|
1521
|
+
function unitText(lines, misplaced) {
|
|
1522
|
+
const { unit, run: run3 } = misplaced;
|
|
1523
|
+
const out = [];
|
|
1524
|
+
for (let n = unit.start; n <= unit.end; n++) {
|
|
1525
|
+
if (n >= run3.start && n <= run3.end) continue;
|
|
1526
|
+
const line = lines[n - 1];
|
|
1527
|
+
if (line !== void 0) out.push(line);
|
|
1528
|
+
}
|
|
1529
|
+
return out;
|
|
1530
|
+
}
|
|
1531
|
+
function splitKeepingEol(content) {
|
|
1532
|
+
const parts = content.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
1533
|
+
const texts = [];
|
|
1534
|
+
const eols = [];
|
|
1535
|
+
for (const part of parts) {
|
|
1536
|
+
const eol = /\r?\n$/.exec(part)?.[0] ?? "";
|
|
1537
|
+
texts.push(part.slice(0, part.length - eol.length));
|
|
1538
|
+
eols.push(eol);
|
|
1539
|
+
}
|
|
1540
|
+
return { texts, eols };
|
|
1541
|
+
}
|
|
1542
|
+
function applyMarkerMoves(content, moves, indentAt) {
|
|
1543
|
+
const { texts, eols } = splitKeepingEol(content);
|
|
1544
|
+
const moving = new Set(moves.map((move) => move.from));
|
|
1545
|
+
const inserts = /* @__PURE__ */ new Map();
|
|
1546
|
+
for (const move of moves) {
|
|
1547
|
+
const list = inserts.get(move.place) ?? [];
|
|
1548
|
+
list.push(move.from);
|
|
1549
|
+
inserts.set(move.place, list);
|
|
1550
|
+
}
|
|
1551
|
+
const order = [];
|
|
1552
|
+
for (let n = 1; n <= texts.length; n++) {
|
|
1553
|
+
for (const from of inserts.get(n) ?? []) order.push(from);
|
|
1554
|
+
if (!moving.has(n)) order.push(n);
|
|
1555
|
+
}
|
|
1556
|
+
const moved = /* @__PURE__ */ new Map();
|
|
1557
|
+
for (const move of moves) {
|
|
1558
|
+
moved.set(move.from, indentAt(move.place) + (texts[move.from - 1] ?? "").trim());
|
|
1559
|
+
}
|
|
1560
|
+
const at2 = /* @__PURE__ */ new Map();
|
|
1561
|
+
order.forEach((old, index) => at2.set(old, index + 1));
|
|
1562
|
+
const out = order.map((old, index) => (moved.get(old) ?? texts[old - 1] ?? "") + (eols[index] ?? "")).join("");
|
|
1563
|
+
return { content: out, line: (old) => at2.get(old) ?? old };
|
|
1564
|
+
}
|
|
1565
|
+
function pinOf(lines, span) {
|
|
1566
|
+
if (span.start < 1 || span.end > lines.length || span.end < span.start) return void 0;
|
|
1567
|
+
return hashLines(lines.slice(span.start - 1, span.end).join("\n"));
|
|
1568
|
+
}
|
|
1569
|
+
function markerClaimEnd(page, entry, lines, misplaced) {
|
|
1570
|
+
const { citation, marker } = entry;
|
|
1571
|
+
const pin = citation.claim?.integrity;
|
|
1572
|
+
const quote = citation.quote === true;
|
|
1573
|
+
if (marker === void 0 || pin === void 0) return { status: "changed" };
|
|
1574
|
+
const current = anchoredLines(page.content, marker.end, page.format, quote);
|
|
1575
|
+
if (current === void 0) return { status: "changed" };
|
|
1576
|
+
const claimAt = (span, status, to) => {
|
|
1577
|
+
const end = { fileLines: spellLines(span), status };
|
|
1578
|
+
if (to !== void 0) {
|
|
1579
|
+
end.newLines = spellLines(to);
|
|
1580
|
+
end.newFileLines = end.newLines;
|
|
1359
1581
|
}
|
|
1582
|
+
if (status === "changed") end.text = lines.slice(span.start - 1, Math.min(span.end, lines.length));
|
|
1583
|
+
return end;
|
|
1584
|
+
};
|
|
1585
|
+
if (quote) {
|
|
1586
|
+
return claimAt(current, pinOf(lines, current) === pin ? "current" : "changed");
|
|
1360
1587
|
}
|
|
1361
|
-
|
|
1588
|
+
if (misplaced !== void 0) {
|
|
1589
|
+
const to = movedUnit(misplaced);
|
|
1590
|
+
if (hashLines(unitText(lines, misplaced).join("\n")) === pin) return claimAt(to, "current");
|
|
1591
|
+
if (pinOf(lines, current) === pin) return claimAt(current, "moved", to);
|
|
1592
|
+
const pre432 = pre43Span(lines, marker.line);
|
|
1593
|
+
if (pre432 !== void 0 && pinOf(lines, pre432) === pin) return claimAt(pre432, "moved", to);
|
|
1594
|
+
return claimAt(current, "changed");
|
|
1595
|
+
}
|
|
1596
|
+
if (pinOf(lines, current) === pin) return claimAt(current, "current");
|
|
1597
|
+
const pre43 = pre43Span(lines, marker.line);
|
|
1598
|
+
if (pre43 !== void 0 && pinOf(lines, pre43) === pin) return claimAt(pre43, "moved", current);
|
|
1599
|
+
return claimAt(current, "changed");
|
|
1600
|
+
}
|
|
1362
1601
|
|
|
1363
|
-
// src/cite/core/
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
var FENCE_ONLY = /* @__PURE__ */ new Set(["markdown", "mdx"]);
|
|
1367
|
-
var MARKER_JSON = "A marker names an entry by id. Write the entry in frontmatter or the sidecar.";
|
|
1368
|
-
function ownedMessage(owner) {
|
|
1369
|
-
return `"citations" is owned by manifest ${owner.file} (collection ${owner.collection}); remove it from the document`;
|
|
1602
|
+
// src/cite/core/claims.ts
|
|
1603
|
+
function normalizeWhitespace(text) {
|
|
1604
|
+
return text.replace(/\s+/g, " ").trim();
|
|
1370
1605
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
const ajv = new Ajv2020({ allErrors: false, strict: false });
|
|
1376
|
-
ajv.addSchema(citations_default);
|
|
1377
|
-
entryValidator = ajv.compile({
|
|
1378
|
-
$ref: `${citations_default.$id}#/$defs/citationEntry`
|
|
1379
|
-
});
|
|
1380
|
-
return entryValidator;
|
|
1606
|
+
function normalizeBlock(text) {
|
|
1607
|
+
let out = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1608
|
+
out = out.replace(/\r\n/g, "\n");
|
|
1609
|
+
return out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
1381
1610
|
}
|
|
1382
|
-
function
|
|
1383
|
-
|
|
1384
|
-
if (validate(entry)) return void 0;
|
|
1385
|
-
const first = validate.errors?.[0];
|
|
1386
|
-
if (!first) return "invalid entry";
|
|
1387
|
-
return `${first.instancePath} ${first.message ?? "is invalid"}`.trim();
|
|
1611
|
+
function blockMatches(blockText, citedJoined) {
|
|
1612
|
+
return normalizeBlock(blockText) === normalizeBlock(citedJoined);
|
|
1388
1613
|
}
|
|
1389
|
-
function
|
|
1390
|
-
return
|
|
1614
|
+
function textAt(lines, range) {
|
|
1615
|
+
return lines.slice(range.start - 1, Math.min(range.end, lines.length));
|
|
1391
1616
|
}
|
|
1392
|
-
function
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1617
|
+
function claimLine(claim) {
|
|
1618
|
+
const at2 = claim.fileLines === void 0 ? void 0 : parseLines(claim.fileLines);
|
|
1619
|
+
return at2?.start;
|
|
1620
|
+
}
|
|
1621
|
+
function claimLineNow(claim) {
|
|
1622
|
+
const spec = claim.newFileLines ?? claim.candidateFileLines?.[0] ?? claim.fileLines;
|
|
1623
|
+
const at2 = spec === void 0 ? void 0 : parseLines(spec);
|
|
1624
|
+
return at2?.start;
|
|
1625
|
+
}
|
|
1626
|
+
function claimLinesNow(claim) {
|
|
1627
|
+
const spec = claim.newFileLines ?? claim.candidateFileLines?.[0] ?? claim.fileLines;
|
|
1628
|
+
return spec === void 0 ? void 0 : parseLines(spec);
|
|
1629
|
+
}
|
|
1630
|
+
function claimEnd(page, entry, lines, misplaced) {
|
|
1631
|
+
const { citation, marker } = entry;
|
|
1632
|
+
const claim = citation.claim;
|
|
1633
|
+
if (claim === void 0) return null;
|
|
1634
|
+
const body = page.bodyLine;
|
|
1635
|
+
if (claim.lines !== void 0) {
|
|
1636
|
+
const recorded = parseLines(claim.lines);
|
|
1637
|
+
if (recorded === void 0) return { status: "skipped" };
|
|
1638
|
+
const file = toFileLines(recorded, body);
|
|
1639
|
+
const end = {
|
|
1640
|
+
lines: spellLines(recorded),
|
|
1641
|
+
fileLines: spellLines(file),
|
|
1642
|
+
status: "changed"
|
|
1643
|
+
};
|
|
1644
|
+
if (marker !== void 0) return { ...end, status: "skipped" };
|
|
1645
|
+
if (pinOfLines(lines, file) === claim.integrity) return { ...end, status: "current" };
|
|
1646
|
+
const width = recorded.end - recorded.start + 1;
|
|
1647
|
+
const found = findWindows(lines.slice(body - 1), width, claim.integrity, void 0, {
|
|
1648
|
+
around: recorded.start
|
|
1649
|
+
});
|
|
1650
|
+
const spans = found.starts.map((start) => ({ start, end: start + width - 1 }));
|
|
1651
|
+
const [only] = spans;
|
|
1652
|
+
if (spans.length === 1 && only !== void 0) {
|
|
1653
|
+
return {
|
|
1654
|
+
...end,
|
|
1655
|
+
status: "moved",
|
|
1656
|
+
newLines: spellLines(only),
|
|
1657
|
+
newFileLines: spellLines(toFileLines(only, body))
|
|
1658
|
+
};
|
|
1398
1659
|
}
|
|
1660
|
+
if (spans.length > 1) {
|
|
1661
|
+
return {
|
|
1662
|
+
...end,
|
|
1663
|
+
status: "moved-ambiguous",
|
|
1664
|
+
candidates: spans.map(spellLines),
|
|
1665
|
+
candidateFileLines: spans.map((span) => spellLines(toFileLines(span, body)))
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
return { ...end, text: textAt(lines, file) };
|
|
1669
|
+
}
|
|
1670
|
+
if (marker !== void 0) {
|
|
1671
|
+
return markerClaimEnd(
|
|
1672
|
+
page,
|
|
1673
|
+
entry,
|
|
1674
|
+
lines,
|
|
1675
|
+
misplaced ?? misplacedMarkerAt(page, lines, marker.line, entry)
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
return { status: "changed" };
|
|
1679
|
+
}
|
|
1680
|
+
function unitAt(page, line, lines) {
|
|
1681
|
+
if (line < page.bodyLine || line > lines.length) return void 0;
|
|
1682
|
+
if ((lines[line - 1] ?? "").trim() === "") return void 0;
|
|
1683
|
+
const block = fenceSpanAt(page.content, line, page.format);
|
|
1684
|
+
if (block !== void 0) {
|
|
1685
|
+
return { lines: block, kind: "block", text: lines.slice(block.start - 1, block.end) };
|
|
1686
|
+
}
|
|
1687
|
+
if (insideFence(page.content, page.bodyOffset, line, page.format)) return void 0;
|
|
1688
|
+
const paragraph = paragraphAfter(page.content, offsetOfLine(page.content, line));
|
|
1689
|
+
if (paragraph === void 0 || paragraph.line !== line) return void 0;
|
|
1690
|
+
const span = { start: line, end: lineAt(page.content, paragraph.end) };
|
|
1691
|
+
return { lines: span, kind: "paragraph", text: lines.slice(span.start - 1, span.end) };
|
|
1692
|
+
}
|
|
1693
|
+
function markerUnit(page, entry, lines) {
|
|
1694
|
+
const { marker, citation } = entry;
|
|
1695
|
+
if (marker === void 0) return void 0;
|
|
1696
|
+
const span = anchoredLines(page.content, marker.end, page.format, citation.quote === true);
|
|
1697
|
+
if (span === void 0 || span.end > lines.length) return void 0;
|
|
1698
|
+
return {
|
|
1699
|
+
lines: span,
|
|
1700
|
+
kind: citation.quote === true ? "block" : "paragraph",
|
|
1701
|
+
text: lines.slice(span.start - 1, span.end)
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// src/cite/core/git.ts
|
|
1706
|
+
import { execFile } from "node:child_process";
|
|
1707
|
+
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
1708
|
+
var NO_COMMIT = /invalid object name|bad revision|unknown revision|bad object/i;
|
|
1709
|
+
var NO_PATH = /does not exist in|exists on disk, but not in|path .* does not exist/i;
|
|
1710
|
+
function run2(root, args) {
|
|
1711
|
+
return new Promise((resolve9) => {
|
|
1712
|
+
execFile(
|
|
1713
|
+
"git",
|
|
1714
|
+
["-c", "core.quotepath=false", ...args],
|
|
1715
|
+
{ cwd: root, windowsHide: true, maxBuffer: MAX_BUFFER, encoding: "utf8" },
|
|
1716
|
+
(error, stdout, stderr) => {
|
|
1717
|
+
if (error) {
|
|
1718
|
+
const failure = { ok: false, stderr, message: error.message };
|
|
1719
|
+
if (typeof error.code === "number") failure.code = error.code;
|
|
1720
|
+
resolve9(failure);
|
|
1721
|
+
} else resolve9({ ok: true, stdout });
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
function gitClient(root) {
|
|
1727
|
+
const memo = /* @__PURE__ */ new Map();
|
|
1728
|
+
const once = (key, compute) => {
|
|
1729
|
+
const held = memo.get(key);
|
|
1730
|
+
if (held !== void 0) return held;
|
|
1731
|
+
const made = compute();
|
|
1732
|
+
memo.set(key, made);
|
|
1733
|
+
return made;
|
|
1734
|
+
};
|
|
1735
|
+
const available = () => once("available", async () => (await run2(root, ["rev-parse", "--show-toplevel"])).ok);
|
|
1736
|
+
const failed = (verb, outcome) => new CiteError(`git ${verb} failed: ${outcome.stderr.trim() || outcome.message}`);
|
|
1737
|
+
const hasCommit = (commit) => once(`has\0${commit}`, async () => {
|
|
1738
|
+
const out = await run2(root, ["rev-parse", "--verify", "--quiet", "--end-of-options", `${commit}^{commit}`]);
|
|
1739
|
+
if (out.ok) return true;
|
|
1740
|
+
if (out.code === 1) return false;
|
|
1741
|
+
throw failed("rev-parse", out);
|
|
1742
|
+
});
|
|
1743
|
+
return {
|
|
1744
|
+
available,
|
|
1745
|
+
head: () => once("head", async () => {
|
|
1746
|
+
if (!await available()) return null;
|
|
1747
|
+
const out = await run2(root, ["rev-parse", "HEAD"]);
|
|
1748
|
+
return out.ok ? out.stdout.trim() : null;
|
|
1749
|
+
}),
|
|
1750
|
+
lsFiles: () => once("ls-files", async () => {
|
|
1751
|
+
if (!await available()) return [];
|
|
1752
|
+
const out = await run2(root, ["ls-files", "-z"]);
|
|
1753
|
+
if (!out.ok) throw failed("ls-files", out);
|
|
1754
|
+
return out.stdout.split("\0").filter((entry) => entry !== "");
|
|
1755
|
+
}),
|
|
1756
|
+
showFile: (commit, path) => once(`show\0${commit}\0${path}`, async () => {
|
|
1757
|
+
const out = await run2(root, ["show", "--end-of-options", `${commit}:./${path}`]);
|
|
1758
|
+
if (out.ok) return { text: out.stdout };
|
|
1759
|
+
if (NO_COMMIT.test(out.stderr)) return { missing: "commit" };
|
|
1760
|
+
if (NO_PATH.test(out.stderr)) {
|
|
1761
|
+
return await hasCommit(commit) ? { missing: "path" } : { missing: "commit" };
|
|
1762
|
+
}
|
|
1763
|
+
throw failed("show", out);
|
|
1764
|
+
}),
|
|
1765
|
+
subjectsSince: (commit, path) => once(`log\0${commit}\0${path}`, async () => {
|
|
1766
|
+
const out = await run2(root, ["log", "--format=%s", "--end-of-options", `${commit}..HEAD`, "--", path]);
|
|
1767
|
+
if (!out.ok) throw failed("log", out);
|
|
1768
|
+
return out.stdout.split(/\r?\n/).filter((line) => line !== "");
|
|
1769
|
+
}),
|
|
1770
|
+
diffSince: (commit, path) => once(`diff\0${commit}\0${path}`, async () => {
|
|
1771
|
+
const out = await run2(root, ["diff", "--end-of-options", commit, "--", path]);
|
|
1772
|
+
if (!out.ok) throw failed("diff", out);
|
|
1773
|
+
return out.stdout;
|
|
1774
|
+
})
|
|
1399
1775
|
};
|
|
1400
|
-
if (typeof source.lines === "number" || typeof source.lines === "string") {
|
|
1401
|
-
citation.source.lines = source.lines;
|
|
1402
|
-
}
|
|
1403
|
-
if (typeof source["commit-sha"] === "string") {
|
|
1404
|
-
citation.source["commit-sha"] = source["commit-sha"];
|
|
1405
|
-
}
|
|
1406
|
-
if (typeof entry.id === "string") citation.id = entry.id;
|
|
1407
|
-
if (isRecord(entry.claim)) {
|
|
1408
|
-
const claim = entry.claim;
|
|
1409
|
-
citation.claim = { integrity: String(claim.integrity) };
|
|
1410
|
-
if (typeof claim.lines === "number" || typeof claim.lines === "string") {
|
|
1411
|
-
citation.claim.lines = claim.lines;
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
if (typeof entry.quote === "boolean") citation.quote = entry.quote;
|
|
1415
|
-
return citation;
|
|
1416
1776
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1777
|
+
var GIT_UNAVAILABLE_HISTORY = "git is not available here, so citations are checked without history: no never-true, no diffs, no commit subjects.";
|
|
1778
|
+
var GIT_UNAVAILABLE_COMMIT = "git is not available here, so the citation records no commit.";
|
|
1779
|
+
function noGit() {
|
|
1780
|
+
return {
|
|
1781
|
+
available: () => Promise.resolve(false),
|
|
1782
|
+
head: () => Promise.resolve(null),
|
|
1783
|
+
lsFiles: () => Promise.resolve([]),
|
|
1784
|
+
showFile: () => Promise.resolve({ missing: "commit" }),
|
|
1785
|
+
subjectsSince: () => Promise.resolve([]),
|
|
1786
|
+
diffSince: () => Promise.resolve("")
|
|
1426
1787
|
};
|
|
1427
|
-
if (extra.line !== void 0) out.line = extra.line;
|
|
1428
|
-
if (extra.file !== void 0) out.file = extra.file;
|
|
1429
|
-
if (extra.id !== void 0) out.id = extra.id;
|
|
1430
|
-
if (extra.src !== void 0) out.src = extra.src;
|
|
1431
|
-
if (extra.index !== void 0) out.index = extra.index;
|
|
1432
|
-
return out;
|
|
1433
|
-
}
|
|
1434
|
-
function named(id, text) {
|
|
1435
|
-
return id === void 0 ? text : `${id}: ${text}`;
|
|
1436
|
-
}
|
|
1437
|
-
function subjectOf(entry) {
|
|
1438
|
-
const out = {};
|
|
1439
|
-
if (!isRecord(entry)) return out;
|
|
1440
|
-
if (typeof entry.id === "string") out.id = entry.id;
|
|
1441
|
-
if (isRecord(entry.source) && typeof entry.source.file === "string") out.src = entry.source.file;
|
|
1442
|
-
return out;
|
|
1443
1788
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
);
|
|
1451
|
-
}
|
|
1452
|
-
return forced;
|
|
1453
|
-
}
|
|
1454
|
-
const extension = extname2(file);
|
|
1455
|
-
const byExt = extractorForExtension(extension);
|
|
1456
|
-
if (!byExt) {
|
|
1457
|
-
throw new CiteError(
|
|
1458
|
-
`Unsupported file type "${extension}" for "${file}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
|
|
1459
|
-
);
|
|
1789
|
+
|
|
1790
|
+
// src/cite/core/classify.ts
|
|
1791
|
+
async function historyOf(git, commit, path, range, pin, key, budget) {
|
|
1792
|
+
const shown = await git.showFile(commit, path);
|
|
1793
|
+
if ("missing" in shown) {
|
|
1794
|
+
return shown.missing === "commit" ? { kind: "unavailable" } : { kind: "never-true" };
|
|
1460
1795
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
if (bodyOffset <= 0) return 1;
|
|
1468
|
-
const line = lineAt(content, bodyOffset);
|
|
1469
|
-
return content.charCodeAt(bodyOffset - 1) === 10 ? line : line + 1;
|
|
1470
|
-
}
|
|
1471
|
-
function citationInputs(file, extracted, injected) {
|
|
1472
|
-
if (injected !== void 0) return [...injected];
|
|
1473
|
-
const raw = extracted.data.citations;
|
|
1474
|
-
const list = Array.isArray(raw) ? raw : [];
|
|
1475
|
-
return list.map((entry, index) => {
|
|
1476
|
-
const origin = { kind: "frontmatter", file };
|
|
1477
|
-
const line = extracted.lineFor(`/citations/${String(index)}`);
|
|
1478
|
-
if (line !== void 0) origin.line = line;
|
|
1479
|
-
return { entry, origin };
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
|
-
function readPage(file, content, opts) {
|
|
1483
|
-
const extractor = pickExtractor(file, opts?.format);
|
|
1484
|
-
const format = extractor.name;
|
|
1485
|
-
if (FENCE_ONLY.has(format) && hasFrontmatterFence(content) && locateFrontmatter(content) === null) {
|
|
1486
|
-
throw new CiteError(
|
|
1487
|
-
`${file}: Unterminated front matter fence: the opening fence has no matching close, so the page's citations cannot be read. Add a closing fence.`
|
|
1488
|
-
);
|
|
1796
|
+
const then = splitLines(shown.text);
|
|
1797
|
+
let joined;
|
|
1798
|
+
try {
|
|
1799
|
+
joined = sliceLines2(then, range);
|
|
1800
|
+
} catch {
|
|
1801
|
+
joined = void 0;
|
|
1489
1802
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
const bodyOffset = ELEMENT_BACKED.has(format) ? 0 : locateFrontmatter(content)?.closeEnd ?? 0;
|
|
1493
|
-
const bodyLine = bodyLineOf(content, bodyOffset);
|
|
1494
|
-
const findings = [];
|
|
1495
|
-
const citations = [];
|
|
1496
|
-
const byId = /* @__PURE__ */ new Map();
|
|
1497
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1498
|
-
const markedAt = /* @__PURE__ */ new Map();
|
|
1499
|
-
for (const key of Object.keys(data)) {
|
|
1500
|
-
if (!key.startsWith("citation-")) continue;
|
|
1501
|
-
findings.push(
|
|
1502
|
-
finding(
|
|
1503
|
-
"entry-invalid",
|
|
1504
|
-
`"${key}" is not a citations key; the page root reserves the citation- prefix`,
|
|
1505
|
-
{ line: extracted.lineFor(`/${key}`) }
|
|
1506
|
-
)
|
|
1507
|
-
);
|
|
1803
|
+
if (joined !== void 0 && hashLines(joined, key) === pin) {
|
|
1804
|
+
return { kind: "original", lines: joined.split("\n") };
|
|
1508
1805
|
}
|
|
1509
|
-
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
);
|
|
1806
|
+
if (range.start === void 0) return { kind: "never-true" };
|
|
1807
|
+
const length = (range.end ?? range.start) - range.start + 1;
|
|
1808
|
+
const search = { around: range.start };
|
|
1809
|
+
if (budget !== void 0) search.budget = budget;
|
|
1810
|
+
const found = findWindows(then, length, pin, key, search);
|
|
1811
|
+
const [first] = found.starts;
|
|
1812
|
+
if (first !== void 0) {
|
|
1813
|
+
return { kind: "original", lines: then.slice(first - 1, first - 1 + length) };
|
|
1517
1814
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1815
|
+
return found.truncated ? { kind: "unknown" } : { kind: "never-true" };
|
|
1816
|
+
}
|
|
1817
|
+
async function classifyCitation(entry, opts) {
|
|
1818
|
+
const { citation } = entry;
|
|
1819
|
+
const range = sourceRange(citation.source);
|
|
1820
|
+
const commit = citation.source["commit-sha"];
|
|
1821
|
+
const result = { src: spellSource(citation.source), status: "missing" };
|
|
1822
|
+
if (commit !== void 0) result.commitSha = commit;
|
|
1823
|
+
const source = await readSource(opts.root, opts.index, range, opts.key);
|
|
1824
|
+
if (source.kind === "missing") {
|
|
1825
|
+
result.missingReason = source.reason;
|
|
1826
|
+
return result;
|
|
1524
1827
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
findings.push(finding("entry-invalid", error ?? "must be object", { ...where, ...subjectOf(entry) }));
|
|
1535
|
-
return;
|
|
1536
|
-
}
|
|
1537
|
-
const citation = toCitation(entry);
|
|
1538
|
-
const subject = { ...where, src: spellSource(citation.source) };
|
|
1539
|
-
if (citation.id !== void 0) subject.id = citation.id;
|
|
1540
|
-
const bad = ["claim", "source"].find((end) => {
|
|
1541
|
-
const lines = end === "claim" ? citation.claim?.lines : citation.source.lines;
|
|
1542
|
-
return lines !== void 0 && parseLines(lines) === void 0;
|
|
1543
|
-
});
|
|
1544
|
-
if (bad !== void 0) {
|
|
1545
|
-
const lines = bad === "claim" ? citation.claim?.lines : citation.source.lines;
|
|
1546
|
-
findings.push(
|
|
1547
|
-
finding(
|
|
1548
|
-
"entry-invalid",
|
|
1549
|
-
named(citation.id, `${bad}.lines "${String(lines)}" ends before it starts`),
|
|
1550
|
-
subject
|
|
1551
|
-
)
|
|
1552
|
-
);
|
|
1553
|
-
return;
|
|
1554
|
-
}
|
|
1555
|
-
const wide = ["claim", "source"].map((end) => {
|
|
1556
|
-
const lines = end === "claim" ? citation.claim?.lines : citation.source.lines;
|
|
1557
|
-
const parsed = lines === void 0 ? void 0 : parseLines(lines);
|
|
1558
|
-
const reason = parsed === void 0 ? void 0 : tooWide(parsed);
|
|
1559
|
-
return reason === void 0 ? void 0 : `${end}.lines "${String(lines)}" ${reason}`;
|
|
1560
|
-
}).find((message) => message !== void 0);
|
|
1561
|
-
if (wide !== void 0) {
|
|
1562
|
-
findings.push(finding("entry-invalid", named(citation.id, wide), subject));
|
|
1563
|
-
return;
|
|
1564
|
-
}
|
|
1565
|
-
const encrypted = isEncryptedValue(citation.source.file);
|
|
1566
|
-
if (encrypted !== isKeyedPin(citation.source.integrity)) {
|
|
1567
|
-
findings.push(
|
|
1568
|
-
finding(
|
|
1569
|
-
"entry-invalid",
|
|
1570
|
-
named(
|
|
1571
|
-
citation.id,
|
|
1572
|
-
encrypted ? "an encrypted source is pinned with hmac-sha256-, not sha256-." : "a plain source is pinned with sha256-, not hmac-sha256-."
|
|
1573
|
-
),
|
|
1574
|
-
subject
|
|
1575
|
-
)
|
|
1576
|
-
);
|
|
1577
|
-
return;
|
|
1578
|
-
}
|
|
1579
|
-
const page = { citation, origin };
|
|
1580
|
-
citations.push(page);
|
|
1581
|
-
if (citation.id !== void 0) {
|
|
1582
|
-
if (ids.has(citation.id)) {
|
|
1583
|
-
findings.push(finding("entry-invalid", `duplicate id "${citation.id}"`, subject));
|
|
1584
|
-
} else {
|
|
1585
|
-
ids.add(citation.id);
|
|
1586
|
-
byId.set(citation.id, page);
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
});
|
|
1590
|
-
const body = content.slice(bodyOffset);
|
|
1591
|
-
const statements = parseStatements(body, format, {
|
|
1592
|
-
offset: bodyOffset,
|
|
1593
|
-
line: bodyLine
|
|
1594
|
-
});
|
|
1595
|
-
if (statements.length > MAX_MARKERS_PER_PAGE) {
|
|
1596
|
-
findings.push(
|
|
1597
|
-
finding(
|
|
1598
|
-
"marker-invalid",
|
|
1599
|
-
`more than ${String(MAX_MARKERS_PER_PAGE)} markers on one page (${String(statements.length)}); the rest are not read`,
|
|
1600
|
-
{ line: statements[MAX_MARKERS_PER_PAGE]?.line }
|
|
1601
|
-
)
|
|
1602
|
-
);
|
|
1828
|
+
result.resolvedPath = source.resolvedPath;
|
|
1829
|
+
const key = range.encrypted ? opts.key : void 0;
|
|
1830
|
+
const pin = citation.source.integrity;
|
|
1831
|
+
const lines = splitLines(source.text);
|
|
1832
|
+
let here;
|
|
1833
|
+
try {
|
|
1834
|
+
here = hashLines(sliceLines2(lines, range), key);
|
|
1835
|
+
} catch {
|
|
1836
|
+
here = void 0;
|
|
1603
1837
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
findings.push(
|
|
1608
|
-
finding(
|
|
1609
|
-
"marker-invalid",
|
|
1610
|
-
payload.json === true ? MARKER_JSON : `invalid marker: ${payload.reason}`,
|
|
1611
|
-
{ line: statement.line }
|
|
1612
|
-
)
|
|
1613
|
-
);
|
|
1614
|
-
continue;
|
|
1615
|
-
}
|
|
1616
|
-
const target = byId.get(payload.id);
|
|
1617
|
-
if (target === void 0) {
|
|
1618
|
-
findings.push(
|
|
1619
|
-
finding("marker-orphan", `no entry has id "${payload.id}"`, {
|
|
1620
|
-
line: statement.line,
|
|
1621
|
-
id: payload.id
|
|
1622
|
-
})
|
|
1623
|
-
);
|
|
1624
|
-
continue;
|
|
1625
|
-
}
|
|
1626
|
-
const first = markedAt.get(target);
|
|
1627
|
-
if (first !== void 0) {
|
|
1628
|
-
findings.push(
|
|
1629
|
-
finding(
|
|
1630
|
-
"marker-repeated",
|
|
1631
|
-
`${payload.id} is named by markers at lines ${String(first)} and ${String(statement.line)}; the first anchors it.`,
|
|
1632
|
-
{
|
|
1633
|
-
line: statement.line,
|
|
1634
|
-
id: payload.id,
|
|
1635
|
-
index: target.origin.index,
|
|
1636
|
-
src: spellSource(target.citation.source)
|
|
1637
|
-
}
|
|
1638
|
-
)
|
|
1639
|
-
);
|
|
1640
|
-
continue;
|
|
1641
|
-
}
|
|
1642
|
-
markedAt.set(target, statement.line);
|
|
1643
|
-
target.marker = statement;
|
|
1838
|
+
if (here === pin) {
|
|
1839
|
+
result.status = "current";
|
|
1840
|
+
return result;
|
|
1644
1841
|
}
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1842
|
+
let history = { kind: "none" };
|
|
1843
|
+
if (commit !== void 0 && await opts.git.available()) {
|
|
1844
|
+
history = await historyOf(opts.git, commit, source.resolvedPath, range, pin, key, opts.budget);
|
|
1845
|
+
}
|
|
1846
|
+
if (range.start !== void 0) {
|
|
1847
|
+
const start = range.start;
|
|
1848
|
+
const end = range.end ?? start;
|
|
1849
|
+
const search = { around: start };
|
|
1850
|
+
if (history.kind === "original") search.original = history.lines;
|
|
1851
|
+
if (opts.budget !== void 0) search.budget = opts.budget;
|
|
1852
|
+
const found = findWindows(lines, end - start + 1, pin, key, search);
|
|
1853
|
+
if (found.truncated) result.truncatedSearch = true;
|
|
1854
|
+
const spell = (at2) => formatSrc({ ...range, start: at2, end: at2 + (end - start) });
|
|
1855
|
+
const [only] = found.starts;
|
|
1856
|
+
if (found.starts.length === 1 && only !== void 0) {
|
|
1857
|
+
result.status = "moved";
|
|
1858
|
+
result.newSrc = spell(only);
|
|
1859
|
+
result.newLines = String(lineSpec({ start: only, end: only + (end - start) }));
|
|
1860
|
+
return result;
|
|
1662
1861
|
}
|
|
1663
|
-
if (
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1862
|
+
if (found.starts.length > 1) {
|
|
1863
|
+
result.status = "moved-ambiguous";
|
|
1864
|
+
result.candidates = found.starts.map(spell);
|
|
1865
|
+
return result;
|
|
1667
1866
|
}
|
|
1668
1867
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1868
|
+
if (history.kind === "never-true") {
|
|
1869
|
+
result.status = "never-true";
|
|
1870
|
+
return result;
|
|
1871
|
+
}
|
|
1872
|
+
result.status = "changed";
|
|
1873
|
+
if (history.kind === "unavailable") result.historyAvailable = false;
|
|
1874
|
+
if (history.kind === "unknown") result.truncatedSearch = true;
|
|
1875
|
+
if (history.kind === "original" && commit !== void 0) {
|
|
1876
|
+
result.historyAvailable = true;
|
|
1877
|
+
result.commitsSince = await opts.git.subjectsSince(commit, source.resolvedPath);
|
|
1878
|
+
result.diff = await opts.git.diffSince(commit, source.resolvedPath);
|
|
1879
|
+
}
|
|
1880
|
+
return result;
|
|
1679
1881
|
}
|
|
1680
1882
|
|
|
1681
1883
|
// src/cite/core/shift.ts
|
|
1682
|
-
function spellAt(lines) {
|
|
1683
|
-
return lines.start === lines.end ? `line ${String(lines.start)}` : `lines ${String(lines.start)}-${String(lines.end)}`;
|
|
1684
|
-
}
|
|
1685
1884
|
function shiftedEntries(opts) {
|
|
1686
1885
|
const { citations, at: at2, delta, bodyLine, label } = opts;
|
|
1687
1886
|
const out = { frontmatter: [], manifest: /* @__PURE__ */ new Map() };
|
|
@@ -2168,10 +2367,6 @@ function short(commit) {
|
|
|
2168
2367
|
function plural(n, noun) {
|
|
2169
2368
|
return `${String(n)} ${noun}${n === 1 ? "" : "s"}`;
|
|
2170
2369
|
}
|
|
2171
|
-
function listOf(values) {
|
|
2172
|
-
if (values.length <= 1) return values.join("");
|
|
2173
|
-
return `${values.slice(0, -1).join(", ")} and ${values[values.length - 1] ?? ""}`;
|
|
2174
|
-
}
|
|
2175
2370
|
function citesEncrypted(src) {
|
|
2176
2371
|
try {
|
|
2177
2372
|
return parseSrc(src).encrypted;
|
|
@@ -2249,6 +2444,13 @@ function claimMessageFor(result) {
|
|
|
2249
2444
|
return "";
|
|
2250
2445
|
}
|
|
2251
2446
|
}
|
|
2447
|
+
function misplacedMessageFor(id, marker) {
|
|
2448
|
+
const place = marker.block ? `above the block at line ${String(marker.place)}` : `above line ${String(marker.place)}`;
|
|
2449
|
+
return named2(
|
|
2450
|
+
id,
|
|
2451
|
+
`the marker at line ${String(marker.line)} splits the paragraph at ${at(marker.unit)}. Its place is ${place}.`
|
|
2452
|
+
);
|
|
2453
|
+
}
|
|
2252
2454
|
function sourceRuleOf(status) {
|
|
2253
2455
|
if (status === "skipped" || status === "current") return void 0;
|
|
2254
2456
|
return `source-${status}`;
|
|
@@ -2372,7 +2574,7 @@ async function quoteFindings(input, entry, result) {
|
|
|
2372
2574
|
const { citation } = entry;
|
|
2373
2575
|
if (citation.quote !== true || result.anchor === null) return [];
|
|
2374
2576
|
const id = citation.id;
|
|
2375
|
-
const
|
|
2577
|
+
const named4 = (text) => id === void 0 ? text : `${id}: ${text}`;
|
|
2376
2578
|
const { page } = input;
|
|
2377
2579
|
let block;
|
|
2378
2580
|
if (result.anchor === "marker" && entry.marker !== void 0) {
|
|
@@ -2394,7 +2596,7 @@ async function quoteFindings(input, entry, result) {
|
|
|
2394
2596
|
return [
|
|
2395
2597
|
{
|
|
2396
2598
|
rule: "anchor-invalid",
|
|
2397
|
-
message:
|
|
2599
|
+
message: named4(
|
|
2398
2600
|
`the quote's claim lines ${at2.start === at2.end ? String(at2.start) : `${String(at2.start)}-${String(at2.end)}`} are no longer a fenced block.`
|
|
2399
2601
|
),
|
|
2400
2602
|
line: at2.start
|
|
@@ -2416,6 +2618,17 @@ async function quoteFindings(input, entry, result) {
|
|
|
2416
2618
|
}
|
|
2417
2619
|
];
|
|
2418
2620
|
}
|
|
2621
|
+
function markerEnd(entry, misplaced) {
|
|
2622
|
+
const { marker } = entry;
|
|
2623
|
+
if (marker === void 0) return {};
|
|
2624
|
+
if (misplaced === void 0) return { marker: { line: marker.line } };
|
|
2625
|
+
return {
|
|
2626
|
+
marker: {
|
|
2627
|
+
line: marker.line,
|
|
2628
|
+
misplaced: { unit: spellLines(misplaced.unit), to: misplaced.to }
|
|
2629
|
+
}
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2419
2632
|
function anchorOf(entry, claim, page) {
|
|
2420
2633
|
const { citation, marker } = entry;
|
|
2421
2634
|
const hasLines = citation.claim?.lines !== void 0;
|
|
@@ -2462,13 +2675,17 @@ async function checkCitations(page, opts) {
|
|
|
2462
2675
|
checkSources,
|
|
2463
2676
|
...opts.key === void 0 ? {} : { key: opts.key }
|
|
2464
2677
|
};
|
|
2678
|
+
const misplaced = misplacedMarkers(read, lines);
|
|
2679
|
+
const misplacedAt = new Map(misplaced.map((found) => [found.line, found]));
|
|
2465
2680
|
for (const entry of read.citations) {
|
|
2466
|
-
const
|
|
2681
|
+
const split = entry.marker === void 0 ? void 0 : misplacedAt.get(entry.marker.line);
|
|
2682
|
+
const claim = claimEnd(read, entry, lines, split);
|
|
2467
2683
|
const source = classifyOpts === void 0 ? skippedSource(entry) : await classifyCitation(entry, classifyOpts);
|
|
2468
2684
|
const result = {
|
|
2469
2685
|
citation: entry.citation,
|
|
2470
2686
|
origin: entry.origin,
|
|
2471
2687
|
...anchorOf(entry, claim, read),
|
|
2688
|
+
...markerEnd(entry, split),
|
|
2472
2689
|
claim,
|
|
2473
2690
|
source
|
|
2474
2691
|
};
|
|
@@ -2489,6 +2706,30 @@ async function checkCitations(page, opts) {
|
|
|
2489
2706
|
findings.push(finding2);
|
|
2490
2707
|
}
|
|
2491
2708
|
}
|
|
2709
|
+
const misplacedLevel = severity["marker-misplaced"];
|
|
2710
|
+
if (misplacedLevel !== "off") {
|
|
2711
|
+
for (const found of misplaced) {
|
|
2712
|
+
const entry = found.entry;
|
|
2713
|
+
const finding2 = {
|
|
2714
|
+
rule: "marker-misplaced",
|
|
2715
|
+
ruleId: ruleId("marker-misplaced"),
|
|
2716
|
+
severity: misplacedLevel,
|
|
2717
|
+
message: misplacedMessageFor(entry?.citation.id, {
|
|
2718
|
+
line: found.line,
|
|
2719
|
+
unit: spellLines(found.unit),
|
|
2720
|
+
place: found.place,
|
|
2721
|
+
block: found.block
|
|
2722
|
+
}),
|
|
2723
|
+
line: found.line
|
|
2724
|
+
};
|
|
2725
|
+
if (entry !== void 0) {
|
|
2726
|
+
finding2.index = entry.origin.index;
|
|
2727
|
+
finding2.src = spellSource(entry.citation.source);
|
|
2728
|
+
if (entry.citation.id !== void 0) finding2.id = entry.citation.id;
|
|
2729
|
+
}
|
|
2730
|
+
findings.push(finding2);
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2492
2733
|
findings.push(...findingsFor(results, severity));
|
|
2493
2734
|
const wantsHistory = checkSources && read.citations.some(({ citation }) => citation.source["commit-sha"] !== void 0);
|
|
2494
2735
|
if (wantsHistory && !await client.available()) notices.push(GIT_UNAVAILABLE_HISTORY);
|
|
@@ -3022,11 +3263,11 @@ var NAMED_SPANS = 3;
|
|
|
3022
3263
|
function spellElsewhere(spans) {
|
|
3023
3264
|
const first = spans[0];
|
|
3024
3265
|
const noun = spans.length === 1 && first !== void 0 && first.start === first.end ? "line" : "lines";
|
|
3025
|
-
const
|
|
3026
|
-
const rest = spans.length -
|
|
3027
|
-
if (rest > 0) return `${noun} ${
|
|
3028
|
-
const last =
|
|
3029
|
-
return
|
|
3266
|
+
const named4 = spans.slice(0, NAMED_SPANS).map((span) => spellLines(span));
|
|
3267
|
+
const rest = spans.length - named4.length;
|
|
3268
|
+
if (rest > 0) return `${noun} ${named4.join(", ")} and ${String(rest)} more`;
|
|
3269
|
+
const last = named4.pop() ?? "";
|
|
3270
|
+
return named4.length === 0 ? `${noun} ${last}` : `${noun} ${named4.join(", ")} and ${last}`;
|
|
3030
3271
|
}
|
|
3031
3272
|
async function runAdd(opts) {
|
|
3032
3273
|
const cwd = resolve4(opts.cwd ?? process.cwd());
|
|
@@ -3123,11 +3364,28 @@ async function runAdd(opts) {
|
|
|
3123
3364
|
const empty = emptyClaimRefusal(lines, pageLines, at2);
|
|
3124
3365
|
if (empty !== void 0) throw new CiteError(empty);
|
|
3125
3366
|
}
|
|
3367
|
+
const split = misplacedMarkers(page, lines);
|
|
3368
|
+
const held = split.find(
|
|
3369
|
+
(found) => found.line >= pageLines.start && found.line <= pageLines.end
|
|
3370
|
+
);
|
|
3371
|
+
if (held !== void 0) {
|
|
3372
|
+
throw new CiteError(
|
|
3373
|
+
`${at2} holds the marker at line ${String(held.line)}, which splits its paragraph. Run manni cite update first.`
|
|
3374
|
+
);
|
|
3375
|
+
}
|
|
3376
|
+
const inside = marker ? split.find(
|
|
3377
|
+
(found) => pageLines.start >= found.unit.start && pageLines.start <= found.unit.end
|
|
3378
|
+
) : void 0;
|
|
3379
|
+
if (inside !== void 0) {
|
|
3380
|
+
throw new CiteError(
|
|
3381
|
+
`${at2} is in the paragraph at ${spellAt(inside.unit)}, which the marker at line ${String(inside.line)} splits. Run manni cite update first.`
|
|
3382
|
+
);
|
|
3383
|
+
}
|
|
3126
3384
|
}
|
|
3127
3385
|
const client = opts.gitClient ?? gitClient(root);
|
|
3128
3386
|
const sourceIndex = await sourceIndexFor(root, client);
|
|
3129
3387
|
let body = content;
|
|
3130
|
-
let
|
|
3388
|
+
let markerAt2;
|
|
3131
3389
|
let claimSpan;
|
|
3132
3390
|
let claim;
|
|
3133
3391
|
let elsewhere = [];
|
|
@@ -3161,7 +3419,7 @@ async function runAdd(opts) {
|
|
|
3161
3419
|
const statement = markerIndent(content, holding.start, format, page.bodyLine) + formatStatement(format, { kind: "ref", id: opts.id ?? "" });
|
|
3162
3420
|
const insertAt = offsetOfLine(content, holding.start);
|
|
3163
3421
|
body = insertStatementBefore(content, insertAt, statement);
|
|
3164
|
-
|
|
3422
|
+
markerAt2 = holding.start;
|
|
3165
3423
|
const unit = anchoredLines(body, insertAt + statement.length, format, quote);
|
|
3166
3424
|
const pin = unit === void 0 ? void 0 : pinOfLines(splitLines(body), unit);
|
|
3167
3425
|
if (unit === void 0 || pin === void 0) {
|
|
@@ -3257,7 +3515,7 @@ async function runAdd(opts) {
|
|
|
3257
3515
|
...sourceLine === void 0 ? {} : { sourceLine },
|
|
3258
3516
|
...placed === void 0 ? {} : { manifest: placed }
|
|
3259
3517
|
};
|
|
3260
|
-
if (
|
|
3518
|
+
if (markerAt2 !== void 0) result.markerLine = markerAt2 + shift;
|
|
3261
3519
|
if (claimSpan !== void 0) {
|
|
3262
3520
|
result.claimLines = { start: claimSpan.start + shift, end: claimSpan.end + shift };
|
|
3263
3521
|
}
|
|
@@ -3268,10 +3526,10 @@ async function runAdd(opts) {
|
|
|
3268
3526
|
result.manifest.written = true;
|
|
3269
3527
|
}
|
|
3270
3528
|
if (elsewhere.length > 0) {
|
|
3271
|
-
const
|
|
3529
|
+
const named4 = elsewhere.map((span) => ({ start: span.start + shift, end: span.end + shift }));
|
|
3272
3530
|
const who = citation.id === void 0 ? "" : `${citation.id}: `;
|
|
3273
3531
|
opts.onNotice?.(
|
|
3274
|
-
`${who}the claim's text also appears at ${spellElsewhere(
|
|
3532
|
+
`${who}the claim's text also appears at ${spellElsewhere(named4)}, so a move would be ambiguous. Use --marker, or pin more lines.`
|
|
3275
3533
|
);
|
|
3276
3534
|
}
|
|
3277
3535
|
if (opts.commitSha !== false && !await client.available()) {
|
|
@@ -3374,9 +3632,9 @@ async function runRemove(opts) {
|
|
|
3374
3632
|
for (const line of ordered) {
|
|
3375
3633
|
if (!isMarkerLine(lines[line - 1] ?? "", format)) {
|
|
3376
3634
|
const id = page.statements.find((s) => s.line === line)?.payload;
|
|
3377
|
-
const
|
|
3635
|
+
const named4 = id?.kind === "ref" ? ` ${id.id}` : "";
|
|
3378
3636
|
throw new CiteError(
|
|
3379
|
-
`the marker${
|
|
3637
|
+
`the marker${named4} at ${label}:${String(line)} shares its line with text; remove it by hand.`
|
|
3380
3638
|
);
|
|
3381
3639
|
}
|
|
3382
3640
|
}
|
|
@@ -3459,13 +3717,26 @@ function settles(plan) {
|
|
|
3459
3717
|
return "claim-changed";
|
|
3460
3718
|
case "source-accepted":
|
|
3461
3719
|
return plan.result.source.status === "never-true" ? "source-never-true" : "source-changed";
|
|
3720
|
+
case "marker-moved":
|
|
3721
|
+
return "marker-misplaced";
|
|
3722
|
+
case "claim-reanchored":
|
|
3723
|
+
return "claim-moved";
|
|
3724
|
+
// A shifted entry was `current`, so it had no finding to settle.
|
|
3725
|
+
case "claim-shifted":
|
|
3726
|
+
return "";
|
|
3462
3727
|
}
|
|
3463
3728
|
}
|
|
3464
3729
|
function apply(content, format, plan) {
|
|
3465
3730
|
const index = plan.result.origin.index;
|
|
3466
3731
|
switch (plan.kind) {
|
|
3467
3732
|
case "claim-moved":
|
|
3733
|
+
case "claim-shifted":
|
|
3468
3734
|
return spliceEntryField(content, format, index, ["claim", "lines"], plan.lines);
|
|
3735
|
+
case "claim-reanchored":
|
|
3736
|
+
return spliceEntryField(content, format, index, ["claim", "integrity"], plan.pin);
|
|
3737
|
+
// The move is a page rewrite, applied to the whole page before any splice.
|
|
3738
|
+
case "marker-moved":
|
|
3739
|
+
return content;
|
|
3469
3740
|
case "source-moved":
|
|
3470
3741
|
return spliceEntryField(content, format, index, ["source", "lines"], plan.lines);
|
|
3471
3742
|
case "claim-accepted": {
|
|
@@ -3498,12 +3769,22 @@ function applyToEntry(entry, plan) {
|
|
|
3498
3769
|
if (!isRecord3(entry)) return false;
|
|
3499
3770
|
const end = (name) => isRecord3(entry[name]) ? entry[name] : void 0;
|
|
3500
3771
|
switch (plan.kind) {
|
|
3501
|
-
case "claim-moved":
|
|
3772
|
+
case "claim-moved":
|
|
3773
|
+
case "claim-shifted": {
|
|
3502
3774
|
const claim = end("claim");
|
|
3503
3775
|
if (claim === void 0) return false;
|
|
3504
3776
|
claim.lines = plan.lines;
|
|
3505
3777
|
return true;
|
|
3506
3778
|
}
|
|
3779
|
+
case "claim-reanchored": {
|
|
3780
|
+
const claim = end("claim");
|
|
3781
|
+
if (claim === void 0) return false;
|
|
3782
|
+
claim.integrity = plan.pin;
|
|
3783
|
+
return true;
|
|
3784
|
+
}
|
|
3785
|
+
// The page holds the marker, so the manifest has nothing to change.
|
|
3786
|
+
case "marker-moved":
|
|
3787
|
+
return true;
|
|
3507
3788
|
case "source-moved": {
|
|
3508
3789
|
const source = end("source");
|
|
3509
3790
|
if (source === void 0) return false;
|
|
@@ -3534,7 +3815,56 @@ function rewriteOf(plan) {
|
|
|
3534
3815
|
if (origin.line !== void 0) base.line = origin.line;
|
|
3535
3816
|
switch (plan.kind) {
|
|
3536
3817
|
case "claim-moved":
|
|
3537
|
-
return {
|
|
3818
|
+
return {
|
|
3819
|
+
...base,
|
|
3820
|
+
end: "claim",
|
|
3821
|
+
reason: "moved",
|
|
3822
|
+
status: "moved",
|
|
3823
|
+
from: plan.from,
|
|
3824
|
+
to: plan.to,
|
|
3825
|
+
fromLines: plan.from,
|
|
3826
|
+
toLines: plan.to
|
|
3827
|
+
};
|
|
3828
|
+
case "marker-moved": {
|
|
3829
|
+
const from = String(plan.from);
|
|
3830
|
+
const to = String(plan.to);
|
|
3831
|
+
return {
|
|
3832
|
+
...base,
|
|
3833
|
+
end: "marker",
|
|
3834
|
+
reason: "re-anchored",
|
|
3835
|
+
status: "misplaced",
|
|
3836
|
+
from,
|
|
3837
|
+
to,
|
|
3838
|
+
fromLines: from,
|
|
3839
|
+
toLines: to
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
case "claim-reanchored": {
|
|
3843
|
+
const was = citation.claim?.integrity ?? "";
|
|
3844
|
+
return {
|
|
3845
|
+
...base,
|
|
3846
|
+
end: "claim",
|
|
3847
|
+
reason: "re-anchored",
|
|
3848
|
+
status: "moved",
|
|
3849
|
+
from: was,
|
|
3850
|
+
to: plan.pin,
|
|
3851
|
+
fromPin: was,
|
|
3852
|
+
toPin: plan.pin,
|
|
3853
|
+
lines: plan.held,
|
|
3854
|
+
newLines: plan.now
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3857
|
+
case "claim-shifted":
|
|
3858
|
+
return {
|
|
3859
|
+
...base,
|
|
3860
|
+
end: "claim",
|
|
3861
|
+
reason: "shifted",
|
|
3862
|
+
status: "current",
|
|
3863
|
+
from: plan.from,
|
|
3864
|
+
to: plan.to,
|
|
3865
|
+
fromLines: plan.from,
|
|
3866
|
+
toLines: plan.to
|
|
3867
|
+
};
|
|
3538
3868
|
case "source-moved":
|
|
3539
3869
|
return {
|
|
3540
3870
|
...base,
|
|
@@ -3545,19 +3875,21 @@ function rewriteOf(plan) {
|
|
|
3545
3875
|
to: plan.to
|
|
3546
3876
|
};
|
|
3547
3877
|
case "claim-accepted": {
|
|
3878
|
+
const was = citation.claim?.integrity ?? "";
|
|
3548
3879
|
const out = {
|
|
3549
3880
|
...base,
|
|
3550
3881
|
end: "claim",
|
|
3551
3882
|
reason: "accepted",
|
|
3552
3883
|
status: "changed",
|
|
3553
|
-
from:
|
|
3884
|
+
from: was,
|
|
3554
3885
|
to: plan.pin,
|
|
3886
|
+
fromPin: was,
|
|
3887
|
+
toPin: plan.pin,
|
|
3555
3888
|
at: plan.unit.lines.start,
|
|
3556
3889
|
text: normalizeWhitespace(plan.unit.text.join("\n"))
|
|
3557
3890
|
};
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
}
|
|
3891
|
+
const markerLine = plan.markerLine ?? (plan.result.anchor === "marker" ? plan.result.markerLine : void 0);
|
|
3892
|
+
if (markerLine !== void 0) out.markerLine = markerLine;
|
|
3561
3893
|
return out;
|
|
3562
3894
|
}
|
|
3563
3895
|
case "source-accepted": {
|
|
@@ -3568,6 +3900,8 @@ function rewriteOf(plan) {
|
|
|
3568
3900
|
status: source.status === "never-true" ? "never-true" : "changed",
|
|
3569
3901
|
from: citation.source.integrity,
|
|
3570
3902
|
to: plan.minted.source.integrity,
|
|
3903
|
+
fromPin: citation.source.integrity,
|
|
3904
|
+
toPin: plan.minted.source.integrity,
|
|
3571
3905
|
src: source.src
|
|
3572
3906
|
};
|
|
3573
3907
|
const commit = plan.minted.source["commit-sha"];
|
|
@@ -3576,26 +3910,157 @@ function rewriteOf(plan) {
|
|
|
3576
3910
|
}
|
|
3577
3911
|
}
|
|
3578
3912
|
}
|
|
3913
|
+
function named3(id, text) {
|
|
3914
|
+
return id === void 0 ? text : `${id}: ${text}`;
|
|
3915
|
+
}
|
|
3916
|
+
function crossing(page, misplaced) {
|
|
3917
|
+
for (const other of page.citations) {
|
|
3918
|
+
const spec = other.citation.claim?.lines;
|
|
3919
|
+
const recorded = spec === void 0 ? void 0 : parseLines(spec);
|
|
3920
|
+
if (recorded === void 0) continue;
|
|
3921
|
+
const file = toFileLines(recorded, page.bodyLine);
|
|
3922
|
+
const holdsMarker = file.start <= misplaced.line && misplaced.line <= file.end;
|
|
3923
|
+
const holdsPlace = file.start < misplaced.place && misplaced.place <= file.end;
|
|
3924
|
+
if (!holdsMarker && !holdsPlace) continue;
|
|
3925
|
+
return other.citation.id === void 0 ? `the claim at ${spellAt(file)}` : `the claim of ${other.citation.id} (${spellAt(file)})`;
|
|
3926
|
+
}
|
|
3927
|
+
return void 0;
|
|
3928
|
+
}
|
|
3929
|
+
function decideMarkers(input) {
|
|
3930
|
+
const { page, report, lines, accept, only } = input;
|
|
3931
|
+
const out = { moves: [], stays: [] };
|
|
3932
|
+
for (const misplaced of misplacedMarkers(page, lines)) {
|
|
3933
|
+
const entry = misplaced.entry;
|
|
3934
|
+
const line = misplaced.line;
|
|
3935
|
+
const stay = (rule, message) => {
|
|
3936
|
+
out.stays.push({ line, rule, message });
|
|
3937
|
+
};
|
|
3938
|
+
if (entry === void 0) {
|
|
3939
|
+
if (only !== void 0) continue;
|
|
3940
|
+
const own = report.findings.find(
|
|
3941
|
+
(finding2) => finding2.line === line && finding2.rule.startsWith("marker-") && finding2.rule !== "marker-misplaced"
|
|
3942
|
+
);
|
|
3943
|
+
stay(
|
|
3944
|
+
own?.rule ?? "marker-orphan",
|
|
3945
|
+
`the marker at line ${String(line)} stays, because it names no entry update can check.`
|
|
3946
|
+
);
|
|
3947
|
+
continue;
|
|
3948
|
+
}
|
|
3949
|
+
const id = entry.citation.id;
|
|
3950
|
+
if (only !== void 0 && (id === void 0 || !only.has(id))) continue;
|
|
3951
|
+
const result = report.citations.find((r) => r.origin.index === entry.origin.index);
|
|
3952
|
+
if (result === void 0) continue;
|
|
3953
|
+
if (entry.citation.claim?.lines !== void 0) {
|
|
3954
|
+
stay(
|
|
3955
|
+
"anchor-invalid",
|
|
3956
|
+
named3(id, `the marker at line ${String(line)} stays, because the entry also has claim lines. Keep one.`)
|
|
3957
|
+
);
|
|
3958
|
+
continue;
|
|
3959
|
+
}
|
|
3960
|
+
const across = crossing(page, misplaced);
|
|
3961
|
+
if (across !== void 0) {
|
|
3962
|
+
stay(
|
|
3963
|
+
"marker-misplaced",
|
|
3964
|
+
named3(id, `the marker at line ${String(line)} stays, because moving it would change ${across}.`)
|
|
3965
|
+
);
|
|
3966
|
+
continue;
|
|
3967
|
+
}
|
|
3968
|
+
if (result.claim !== null && result.claim.status === "changed" && !accept) {
|
|
3969
|
+
stay(
|
|
3970
|
+
"marker-misplaced",
|
|
3971
|
+
named3(
|
|
3972
|
+
id,
|
|
3973
|
+
`the marker at line ${String(line)} stays, because its claim changed since it was pinned. update --accept moves it and re-pins.`
|
|
3974
|
+
)
|
|
3975
|
+
);
|
|
3976
|
+
continue;
|
|
3977
|
+
}
|
|
3978
|
+
out.moves.push({ result, entry, misplaced });
|
|
3979
|
+
}
|
|
3980
|
+
return out;
|
|
3981
|
+
}
|
|
3982
|
+
function anchoredNow(content, format, lines, markerLine, quote) {
|
|
3983
|
+
const at2 = offsetOfLine(content, markerLine) + (lines[markerLine - 1]?.length ?? 0);
|
|
3984
|
+
const span = anchoredLines(content, at2, format, quote);
|
|
3985
|
+
if (span === void 0) return void 0;
|
|
3986
|
+
const pin = pinOfLines(lines, span);
|
|
3987
|
+
return pin === void 0 ? void 0 : { span, pin };
|
|
3988
|
+
}
|
|
3989
|
+
function codeOf(error) {
|
|
3990
|
+
if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
|
|
3991
|
+
const code = error.code;
|
|
3992
|
+
return typeof code === "string" ? code : void 0;
|
|
3993
|
+
}
|
|
3994
|
+
var WrittenFiles = class {
|
|
3995
|
+
before = /* @__PURE__ */ new Map();
|
|
3996
|
+
labels = /* @__PURE__ */ new Map();
|
|
3997
|
+
/** Remember what `path` held before this run wrote it. */
|
|
3998
|
+
record(path, label, content) {
|
|
3999
|
+
if (!this.before.has(path)) {
|
|
4000
|
+
this.before.set(path, content);
|
|
4001
|
+
this.labels.set(path, label);
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
/**
|
|
4005
|
+
* Put every file back, and say what happened: `<file> could not be written
|
|
4006
|
+
* (<code>). <pages> was restored.`
|
|
4007
|
+
*/
|
|
4008
|
+
async restore(file, error) {
|
|
4009
|
+
const restored = [];
|
|
4010
|
+
const failed = [];
|
|
4011
|
+
for (const [path, content] of this.before) {
|
|
4012
|
+
const label = this.labels.get(path) ?? path;
|
|
4013
|
+
try {
|
|
4014
|
+
await writeFileAtomic(path, content);
|
|
4015
|
+
restored.push(label);
|
|
4016
|
+
} catch {
|
|
4017
|
+
failed.push(label);
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
const code = codeOf(error);
|
|
4021
|
+
const why = code === void 0 ? "" : ` (${code})`;
|
|
4022
|
+
const parts = [`${file} could not be written${why}.`];
|
|
4023
|
+
if (restored.length > 0) {
|
|
4024
|
+
parts.push(`${listOf(restored)} ${restored.length === 1 ? "was" : "were"} restored.`);
|
|
4025
|
+
}
|
|
4026
|
+
if (failed.length > 0) {
|
|
4027
|
+
parts.push(
|
|
4028
|
+
`${listOf(failed)} could not be restored, and ${failed.length === 1 ? "holds" : "hold"} an intermediate state.`
|
|
4029
|
+
);
|
|
4030
|
+
}
|
|
4031
|
+
return new CiteError(parts.join(" "));
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
4034
|
+
function citationsIn(rows) {
|
|
4035
|
+
const withMarker = new Set(rows.filter((row) => row.end === "marker").map((row) => row.index));
|
|
4036
|
+
return withMarker.size + rows.filter((row) => !withMarker.has(row.index)).length;
|
|
4037
|
+
}
|
|
3579
4038
|
async function runUpdate(opts) {
|
|
3580
4039
|
const prepared = await prepareRun(opts, "updated", "update", { require: true });
|
|
3581
4040
|
const { run: run3, files, usingStdin, forced, pageOptions, git } = prepared;
|
|
3582
4041
|
assertNoOrphans(prepared);
|
|
3583
4042
|
const hits = joinHits();
|
|
3584
4043
|
const manifests = new ManifestSet();
|
|
4044
|
+
const writes = new WrittenFiles();
|
|
3585
4045
|
const only = opts.only !== void 0 && opts.only.length > 0 ? new Set(opts.only) : void 0;
|
|
3586
4046
|
const accept = opts.accept === true;
|
|
3587
|
-
const plansFor = async (result, entry, page, lines, declined) => {
|
|
4047
|
+
const plansFor = async (result, entry, page, lines, lineNow, declined) => {
|
|
3588
4048
|
const out = [];
|
|
3589
4049
|
const claim = result.claim;
|
|
3590
|
-
if (claim !== null && claim.status === "moved" && claim.newLines !== void 0) {
|
|
4050
|
+
if (result.anchor !== "marker" && claim !== null && claim.status === "moved" && claim.newLines !== void 0) {
|
|
3591
4051
|
const at2 = parseLines(claim.newLines);
|
|
3592
|
-
|
|
4052
|
+
const shifted = at2 === void 0 ? void 0 : (() => {
|
|
4053
|
+
const file = toFileLines(at2, page.bodyLine);
|
|
4054
|
+
const to = { start: lineNow(file.start), end: lineNow(file.end) };
|
|
4055
|
+
return to.end - to.start === file.end - file.start ? toBodyLines(to, page.bodyLine) : void 0;
|
|
4056
|
+
})();
|
|
4057
|
+
if (at2 !== void 0 && shifted !== void 0) {
|
|
3593
4058
|
out.push({
|
|
3594
4059
|
kind: "claim-moved",
|
|
3595
4060
|
result,
|
|
3596
|
-
lines: lineSpec(
|
|
4061
|
+
lines: lineSpec(shifted),
|
|
3597
4062
|
from: claim.fileLines ?? "",
|
|
3598
|
-
to:
|
|
4063
|
+
to: spellLines(toFileLines(shifted, page.bodyLine))
|
|
3599
4064
|
});
|
|
3600
4065
|
}
|
|
3601
4066
|
}
|
|
@@ -3606,8 +4071,8 @@ async function runUpdate(opts) {
|
|
|
3606
4071
|
}
|
|
3607
4072
|
}
|
|
3608
4073
|
if (!accept) return out;
|
|
3609
|
-
if (claim !== null && claim.status === "changed" && entry !== void 0) {
|
|
3610
|
-
const unit =
|
|
4074
|
+
if (claim !== null && claim.status === "changed" && entry !== void 0 && result.anchor !== "marker") {
|
|
4075
|
+
const unit = (() => {
|
|
3611
4076
|
const at2 = claimLine(claim);
|
|
3612
4077
|
return at2 === void 0 ? void 0 : unitAt(page, at2, lines);
|
|
3613
4078
|
})();
|
|
@@ -3615,7 +4080,10 @@ async function runUpdate(opts) {
|
|
|
3615
4080
|
const pin = unit === void 0 ? void 0 : pinOfLines(lines, unit.lines);
|
|
3616
4081
|
const wide = unit === void 0 ? void 0 : tooWide(unit.lines);
|
|
3617
4082
|
if (unit !== void 0 && wide !== void 0) {
|
|
3618
|
-
declined.set(result.origin.index,
|
|
4083
|
+
declined.set(result.origin.index, {
|
|
4084
|
+
rule: "claim-changed",
|
|
4085
|
+
why: `Not re-pinned: the ${unit.kind} ${wide}.`
|
|
4086
|
+
});
|
|
3619
4087
|
} else if (unit !== void 0 && pin !== void 0 && (!wantsBlock || unit.kind === "block")) {
|
|
3620
4088
|
const plan = { kind: "claim-accepted", result, unit, pin };
|
|
3621
4089
|
if (entry.citation.claim?.lines !== void 0) {
|
|
@@ -3651,6 +4119,7 @@ async function runUpdate(opts) {
|
|
|
3651
4119
|
if (setup.sidecar !== void 0) hits.record(setup.sidecar, label);
|
|
3652
4120
|
const report = await checkCitations({ file: label, content, format: forced?.name }, setup.options);
|
|
3653
4121
|
const { format } = report;
|
|
4122
|
+
const severity = resolveSeverity(setup.options.severity);
|
|
3654
4123
|
const page = readPage(label, content, {
|
|
3655
4124
|
...forced === void 0 ? {} : { format: forced.name },
|
|
3656
4125
|
...setup.options.citations === void 0 ? {} : { citations: setup.options.citations },
|
|
@@ -3660,18 +4129,105 @@ async function runUpdate(opts) {
|
|
|
3660
4129
|
const rewritten2 = [];
|
|
3661
4130
|
const settled = /* @__PURE__ */ new Set();
|
|
3662
4131
|
const declined = /* @__PURE__ */ new Map();
|
|
3663
|
-
|
|
4132
|
+
const markers = decideMarkers({ page, report, lines, accept, only });
|
|
4133
|
+
const movedPage = markers.moves.length === 0 ? void 0 : applyMarkerMoves(
|
|
4134
|
+
content,
|
|
4135
|
+
markers.moves.map(({ misplaced }) => ({
|
|
4136
|
+
from: misplaced.line,
|
|
4137
|
+
place: misplaced.place
|
|
4138
|
+
})),
|
|
4139
|
+
(line) => markerIndent(content, line, format, page.bodyLine)
|
|
4140
|
+
);
|
|
4141
|
+
let after = movedPage?.content ?? content;
|
|
4142
|
+
const afterLines = movedPage === void 0 ? lines : splitLines(after);
|
|
4143
|
+
const movedIndexes = new Set(markers.moves.map(({ entry }) => entry.origin.index));
|
|
4144
|
+
const lineNow = (line) => movedPage?.line(line) ?? line;
|
|
3664
4145
|
const owner = setup.sidecar?.owner;
|
|
3665
4146
|
const entries = setup.sidecar?.citations === void 0 ? void 0 : JSON.parse(JSON.stringify(setup.sidecar.citations.map((c) => c.entry)));
|
|
3666
4147
|
let manifestDirty = false;
|
|
3667
4148
|
for (const result of report.citations) {
|
|
3668
|
-
|
|
3669
|
-
continue;
|
|
3670
|
-
}
|
|
4149
|
+
const selected = only === void 0 || result.citation.id !== void 0 && only.has(result.citation.id);
|
|
3671
4150
|
const entry = page.citations.find((c) => c.origin.index === result.origin.index);
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
4151
|
+
const plans = [];
|
|
4152
|
+
const move = selected ? markers.moves.find((m) => m.entry.origin.index === result.origin.index) : void 0;
|
|
4153
|
+
if (move !== void 0) {
|
|
4154
|
+
plans.push({
|
|
4155
|
+
kind: "marker-moved",
|
|
4156
|
+
result,
|
|
4157
|
+
from: move.misplaced.line,
|
|
4158
|
+
to: lineNow(move.misplaced.line)
|
|
4159
|
+
});
|
|
4160
|
+
}
|
|
4161
|
+
const marker = entry?.marker;
|
|
4162
|
+
const recorded = entry?.citation.claim?.integrity;
|
|
4163
|
+
if (selected && result.anchor === "marker" && marker !== void 0 && recorded !== void 0 && result.claim !== null) {
|
|
4164
|
+
const quote = entry?.citation.quote === true;
|
|
4165
|
+
const now = anchoredNow(after, format, afterLines, lineNow(marker.line), quote);
|
|
4166
|
+
const status = result.claim.status;
|
|
4167
|
+
if (now !== void 0 && now.pin !== recorded) {
|
|
4168
|
+
if (status === "moved" || status === "current" && move !== void 0) {
|
|
4169
|
+
plans.push({
|
|
4170
|
+
kind: "claim-reanchored",
|
|
4171
|
+
result,
|
|
4172
|
+
pin: now.pin,
|
|
4173
|
+
held: result.claim.fileLines ?? "",
|
|
4174
|
+
now: spellLines(now.span)
|
|
4175
|
+
});
|
|
4176
|
+
} else if (status === "changed" && accept) {
|
|
4177
|
+
const kind = quote ? "block" : "paragraph";
|
|
4178
|
+
const wide = tooWide(now.span);
|
|
4179
|
+
if (wide !== void 0) {
|
|
4180
|
+
declined.set(result.origin.index, {
|
|
4181
|
+
rule: "claim-changed",
|
|
4182
|
+
why: `Not re-pinned: the ${kind} ${wide}.`
|
|
4183
|
+
});
|
|
4184
|
+
} else {
|
|
4185
|
+
plans.push({
|
|
4186
|
+
kind: "claim-accepted",
|
|
4187
|
+
result,
|
|
4188
|
+
pin: now.pin,
|
|
4189
|
+
markerLine: lineNow(marker.line),
|
|
4190
|
+
unit: {
|
|
4191
|
+
lines: now.span,
|
|
4192
|
+
kind,
|
|
4193
|
+
text: afterLines.slice(now.span.start - 1, now.span.end)
|
|
4194
|
+
}
|
|
4195
|
+
});
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
if (selected) {
|
|
4201
|
+
plans.push(...await plansFor(result, entry, page, lines, lineNow, declined));
|
|
4202
|
+
}
|
|
4203
|
+
const spec = entry?.citation.claim?.lines;
|
|
4204
|
+
const wentElsewhere = result.claim?.status === "moved";
|
|
4205
|
+
if (movedPage !== void 0 && spec !== void 0 && !wentElsewhere && !plans.some((p) => p.kind === "claim-moved")) {
|
|
4206
|
+
const at2 = parseLines(spec);
|
|
4207
|
+
const file = at2 === void 0 ? void 0 : toFileLines(at2, page.bodyLine);
|
|
4208
|
+
const to = file === void 0 ? void 0 : { start: lineNow(file.start), end: lineNow(file.end) };
|
|
4209
|
+
if (file !== void 0 && to !== void 0 && to.end - to.start === file.end - file.start && (to.start !== file.start || to.end !== file.end)) {
|
|
4210
|
+
plans.push({
|
|
4211
|
+
kind: "claim-shifted",
|
|
4212
|
+
result,
|
|
4213
|
+
lines: lineSpec(toBodyLines(to, page.bodyLine)),
|
|
4214
|
+
from: spellLines(file),
|
|
4215
|
+
to: spellLines(to)
|
|
4216
|
+
});
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
for (const plan of plans) {
|
|
4220
|
+
if (plan.kind === "marker-moved") {
|
|
4221
|
+
} else if (result.origin.kind === "manifest") {
|
|
4222
|
+
const held = entries?.[result.origin.index];
|
|
4223
|
+
if (held === void 0) {
|
|
4224
|
+
declined.set(result.origin.index, {
|
|
4225
|
+
rule: settles(plan),
|
|
4226
|
+
why: `Not rewritten: ${owner?.file ?? "the manifest"} has no entry at index ${String(result.origin.index)}.`
|
|
4227
|
+
});
|
|
4228
|
+
continue;
|
|
4229
|
+
}
|
|
4230
|
+
if (!applyToEntry(held, plan)) continue;
|
|
3675
4231
|
manifestDirty = true;
|
|
3676
4232
|
} else {
|
|
3677
4233
|
after = apply(after, format, plan);
|
|
@@ -3680,6 +4236,7 @@ async function runUpdate(opts) {
|
|
|
3680
4236
|
settled.add(`${String(result.origin.index)}\0${settles(plan)}`);
|
|
3681
4237
|
}
|
|
3682
4238
|
}
|
|
4239
|
+
for (const index of movedIndexes) settled.add(`${String(index)}\0marker-misplaced`);
|
|
3683
4240
|
if (manifestDirty && owner !== void 0 && entries !== void 0) {
|
|
3684
4241
|
if (setup.sidecar?.entry === void 0) {
|
|
3685
4242
|
throw new CiteError(
|
|
@@ -3688,15 +4245,34 @@ async function runUpdate(opts) {
|
|
|
3688
4245
|
}
|
|
3689
4246
|
await manifests.write(owner, setup.sidecar.entry, entries, 0);
|
|
3690
4247
|
}
|
|
4248
|
+
const stayAt = new Map(markers.stays.map((stay) => [stay.line, stay]));
|
|
3691
4249
|
const skipped2 = report.findings.filter(
|
|
3692
4250
|
(finding2) => (only === void 0 || finding2.id !== void 0 && only.has(finding2.id)) && !settled.has(`${String(finding2.index ?? -1)}\0${finding2.rule}`)
|
|
3693
4251
|
).map((finding2) => {
|
|
3694
|
-
|
|
4252
|
+
if (finding2.rule === "marker-misplaced") {
|
|
4253
|
+
const stay = stayAt.get(finding2.line ?? -1);
|
|
4254
|
+
if (stay === void 0) return finding2;
|
|
4255
|
+
const level = severity[stay.rule];
|
|
4256
|
+
return {
|
|
4257
|
+
...finding2,
|
|
4258
|
+
message: stay.message,
|
|
4259
|
+
...level === "off" ? {} : { severity: level }
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
const turned = finding2.index === void 0 ? void 0 : declined.get(finding2.index);
|
|
4263
|
+
const why = turned?.rule === finding2.rule ? turned.why : void 0;
|
|
3695
4264
|
return why === void 0 ? finding2 : { ...finding2, message: `${finding2.message} ${why}` };
|
|
3696
4265
|
});
|
|
3697
4266
|
const diff = after === content ? "" : unifiedDiff(label, content, after);
|
|
3698
4267
|
const written = after !== content && path !== void 0 && opts.dryRun !== true;
|
|
3699
|
-
if (written)
|
|
4268
|
+
if (written) {
|
|
4269
|
+
try {
|
|
4270
|
+
await writeFileAtomic(path, after);
|
|
4271
|
+
} catch (error) {
|
|
4272
|
+
throw await writes.restore(label, error);
|
|
4273
|
+
}
|
|
4274
|
+
writes.record(path, label, content);
|
|
4275
|
+
}
|
|
3700
4276
|
const out = { file: label, rewritten: rewritten2, skipped: skipped2, diff, written };
|
|
3701
4277
|
if (path === void 0) out.content = after;
|
|
3702
4278
|
pages.push(out);
|
|
@@ -3712,14 +4288,21 @@ async function runUpdate(opts) {
|
|
|
3712
4288
|
const rewrittenManifests = [];
|
|
3713
4289
|
for (const changed of changedManifests) {
|
|
3714
4290
|
const write = opts.dryRun !== true;
|
|
3715
|
-
if (write)
|
|
4291
|
+
if (write) {
|
|
4292
|
+
try {
|
|
4293
|
+
await writeFileAtomic(changed.path, changed.text);
|
|
4294
|
+
} catch (error) {
|
|
4295
|
+
throw await writes.restore(changed.file, error);
|
|
4296
|
+
}
|
|
4297
|
+
writes.record(changed.path, changed.file, changed.before);
|
|
4298
|
+
}
|
|
3716
4299
|
rewrittenManifests.push({ file: changed.file, diff: changed.diff, written: write });
|
|
3717
4300
|
}
|
|
3718
4301
|
const reminted = pages.some(
|
|
3719
4302
|
(page) => page.rewritten.some((r) => r.reason === "accepted" && r.end === "source")
|
|
3720
4303
|
);
|
|
3721
4304
|
sayNotices(reports, opts.onNotice, reminted && !await git.available() ? [GIT_UNAVAILABLE_COMMIT] : []);
|
|
3722
|
-
const rewritten = pages.reduce((n, page) => n + page.rewritten
|
|
4305
|
+
const rewritten = pages.reduce((n, page) => n + citationsIn(page.rewritten), 0);
|
|
3723
4306
|
const skipped = pages.reduce((n, page) => n + page.skipped.length, 0);
|
|
3724
4307
|
const undone = pages.some((page) => page.skipped.some((finding2) => finding2.severity === "error"));
|
|
3725
4308
|
return {
|
|
@@ -3907,13 +4490,15 @@ function citesEncrypted2(src) {
|
|
|
3907
4490
|
}
|
|
3908
4491
|
}
|
|
3909
4492
|
function claimColumn(result) {
|
|
3910
|
-
const { claim } = result;
|
|
3911
|
-
const
|
|
4493
|
+
const { claim, marker } = result;
|
|
4494
|
+
const anchoredByMarker = result.anchor === "marker" && result.markerLine !== void 0;
|
|
4495
|
+
const moves = marker?.misplaced === void 0 ? "" : ` -> :${String(marker.misplaced.to)}`;
|
|
4496
|
+
const where = anchoredByMarker ? `marker :${String(result.markerLine ?? 0)}${moves}` : claim?.fileLines === void 0 ? "" : `:${claim.fileLines}`;
|
|
3912
4497
|
if (claim === null) return where;
|
|
3913
4498
|
let status;
|
|
3914
4499
|
switch (claim.status) {
|
|
3915
4500
|
case "moved":
|
|
3916
|
-
status = `moved -> :${claim.newFileLines ?? "?"}`;
|
|
4501
|
+
status = anchoredByMarker ? "moved" : `moved -> :${claim.newFileLines ?? "?"}`;
|
|
3917
4502
|
break;
|
|
3918
4503
|
case "moved-ambiguous": {
|
|
3919
4504
|
const at2 = (claim.candidateFileLines ?? []).map((lines) => `:${lines}`);
|
|
@@ -4003,7 +4588,7 @@ function renderCheckPretty(run3, opts) {
|
|
|
4003
4588
|
if (own.length === 0 && quiet) continue;
|
|
4004
4589
|
const worst = live.find((f) => f.severity === "error") ?? live.find((f) => f.severity === "warning") ?? live[0];
|
|
4005
4590
|
const mark = worst !== void 0 ? severityMark(worst.severity, c) : own.length > 0 ? c.dim("\xB7") : result.source.status === "skipped" && result.claim === null ? c.dim("\xB7") : c.green("\u2713");
|
|
4006
|
-
const endRules = /* @__PURE__ */ new Set(["claim-", "source-"]);
|
|
4591
|
+
const endRules = /* @__PURE__ */ new Set(["claim-", "source-", "marker-misplaced"]);
|
|
4007
4592
|
const under = [];
|
|
4008
4593
|
for (const finding2 of own) {
|
|
4009
4594
|
const isEnd = [...endRules].some((prefix) => finding2.rule.startsWith(prefix));
|
|
@@ -4062,9 +4647,45 @@ function renderCheckPretty(run3, opts) {
|
|
|
4062
4647
|
}
|
|
4063
4648
|
return lines.join("\n");
|
|
4064
4649
|
}
|
|
4650
|
+
function spellAtSpec(spec) {
|
|
4651
|
+
const span = spanOf(spec);
|
|
4652
|
+
return span === void 0 ? `lines ${spec}` : spellAt(span);
|
|
4653
|
+
}
|
|
4654
|
+
function spanOf(spec) {
|
|
4655
|
+
const [first, second] = spec.split("-");
|
|
4656
|
+
const start = Number(first);
|
|
4657
|
+
const end = second === void 0 ? start : Number(second);
|
|
4658
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return void 0;
|
|
4659
|
+
return { start, end };
|
|
4660
|
+
}
|
|
4661
|
+
function widening(held, now) {
|
|
4662
|
+
const was = spanOf(held);
|
|
4663
|
+
const is = spanOf(now);
|
|
4664
|
+
if (was === void 0 || is === void 0) return "";
|
|
4665
|
+
const parts = [];
|
|
4666
|
+
if (is.start < was.start) {
|
|
4667
|
+
parts.push(spellAt({ start: is.start, end: Math.min(was.start - 1, is.end) }));
|
|
4668
|
+
}
|
|
4669
|
+
if (is.end > was.end) {
|
|
4670
|
+
parts.push(spellAt({ start: Math.max(was.end + 1, is.start), end: is.end }));
|
|
4671
|
+
}
|
|
4672
|
+
if (parts.length === 0) return ", which held a marker line";
|
|
4673
|
+
return `, ${parts.join(" and ")} newly pinned`;
|
|
4674
|
+
}
|
|
4065
4675
|
function rewriteLine(rewrite) {
|
|
4066
4676
|
const word = rewrite.from.includes("-") ? "lines" : "line";
|
|
4067
4677
|
const status = rewrite.status === "never-true" ? "never true" : rewrite.status;
|
|
4678
|
+
if (rewrite.end === "marker") {
|
|
4679
|
+
return `marker line ${rewrite.from} -> ${rewrite.to} (misplaced)`;
|
|
4680
|
+
}
|
|
4681
|
+
if (rewrite.reason === "shifted") {
|
|
4682
|
+
return `claim ${word} ${rewrite.from} -> ${rewrite.to} (shifted by a marker)`;
|
|
4683
|
+
}
|
|
4684
|
+
if (rewrite.reason === "re-anchored") {
|
|
4685
|
+
const held = rewrite.lines ?? "";
|
|
4686
|
+
const now = rewrite.newLines ?? "";
|
|
4687
|
+
return `claim re-pinned over ${spellAtSpec(now)} (moved; was ${spellAtSpec(held)}${widening(held, now)})`;
|
|
4688
|
+
}
|
|
4068
4689
|
if (rewrite.reason === "moved") {
|
|
4069
4690
|
return rewrite.end === "claim" ? `claim ${word} ${rewrite.from} -> ${rewrite.to} (moved)` : `source ${shortSrc(rewrite.from)} -> ${shortSrc(rewrite.to)} (moved)`;
|
|
4070
4691
|
}
|
|
@@ -4118,8 +4739,8 @@ function spellLineList(at2, noun) {
|
|
|
4118
4739
|
function removalLine(removal, c) {
|
|
4119
4740
|
const markers = spellLineList(removal.markerLines, "line");
|
|
4120
4741
|
if (removal.origin === void 0) {
|
|
4121
|
-
const
|
|
4122
|
-
return `removed ${
|
|
4742
|
+
const named4 = removal.markerLines.length === 1 ? "the marker" : "the markers";
|
|
4743
|
+
return `removed ${named4} ${c.cyan(removal.id ?? "")} at ${markers}, which named no entry`;
|
|
4123
4744
|
}
|
|
4124
4745
|
const name = removal.id ?? `/citations/${String(removal.index ?? 0)}`;
|
|
4125
4746
|
const where = removal.origin.kind === "manifest" ? `${removal.origin.file}${removal.origin.line === void 0 ? "" : `:${String(removal.origin.line)}`}` : "frontmatter";
|
|
@@ -4183,6 +4804,7 @@ function publicCitation(result) {
|
|
|
4183
4804
|
...result.citation.id === void 0 ? {} : { id: result.citation.id },
|
|
4184
4805
|
origin,
|
|
4185
4806
|
anchor: result.anchor,
|
|
4807
|
+
...result.marker === void 0 ? {} : { marker: result.marker },
|
|
4186
4808
|
claim,
|
|
4187
4809
|
source
|
|
4188
4810
|
};
|
|
@@ -4664,13 +5286,13 @@ export {
|
|
|
4664
5286
|
isSeverity,
|
|
4665
5287
|
meetsSeverity,
|
|
4666
5288
|
reencryptMetadata,
|
|
4667
|
-
codeRegions,
|
|
4668
|
-
codeEndAt,
|
|
4669
5289
|
CiteError,
|
|
4670
5290
|
asCiteError,
|
|
5291
|
+
spellSource,
|
|
4671
5292
|
RULE_ID_PREFIX,
|
|
4672
5293
|
isCiteRule,
|
|
4673
|
-
|
|
5294
|
+
codeRegions,
|
|
5295
|
+
codeEndAt,
|
|
4674
5296
|
shortPin,
|
|
4675
5297
|
shortCommit,
|
|
4676
5298
|
shortSrc,
|
|
@@ -4693,4 +5315,4 @@ export {
|
|
|
4693
5315
|
runKeyRotate,
|
|
4694
5316
|
runKeySet
|
|
4695
5317
|
};
|
|
4696
|
-
//# sourceMappingURL=chunk-
|
|
5318
|
+
//# sourceMappingURL=chunk-NFCH7HCA.js.map
|