@hawkeyexl/manni 2.12.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,10 +12,12 @@ import {
12
12
  STDIN_LABEL,
13
13
  STDIN_TOKEN,
14
14
  ToolError,
15
+ __default,
15
16
  __export,
16
17
  assertNonEmpty,
17
18
  classifyRef,
18
19
  committedKeyWarning,
20
+ configMarks,
19
21
  decryptValue,
20
22
  encryptValue,
21
23
  ensureEncryptionKey,
@@ -50,11 +52,12 @@ import {
50
52
  loadExternalMetadata,
51
53
  locateFrontmatter,
52
54
  memberOf,
53
- mergeExternalMetadata,
55
+ mergeWithMarks,
54
56
  normalizeText,
55
57
  orphanEntries,
56
58
  orphanError,
57
59
  orphanJoins,
60
+ ownsKey,
58
61
  pageManifestPath,
59
62
  palette,
60
63
  parseLines,
@@ -81,7 +84,7 @@ import {
81
84
  withValueAt,
82
85
  writeEncryptionKey,
83
86
  writeFileAtomic
84
- } from "./chunk-P4V5IMHM.js";
87
+ } from "./chunk-I6PYC7EP.js";
85
88
 
86
89
  // src/meta/core/reencrypt.ts
87
90
  import { extname } from "node:path";
@@ -577,123 +580,6 @@ async function readTracked(root, path) {
577
580
  import { extname as extname2 } from "node:path";
578
581
  import * as Ajv2020Ns from "ajv/dist/2020.js";
579
582
 
580
- // src/cite/schema/citations.json
581
- var citations_default = {
582
- $schema: "https://json-schema.org/draft/2020-12/schema",
583
- $id: "manni:citations:1.0.0-proposal.4",
584
- title: "manni citations vocabulary (1.0.0-proposal.4)",
585
- 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.",
586
- type: "object",
587
- additionalProperties: true,
588
- properties: {
589
- citations: {
590
- type: "array",
591
- minItems: 1,
592
- items: {
593
- $ref: "#/$defs/citationEntry"
594
- },
595
- 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.",
596
- "x-manni-location": "external"
597
- }
598
- },
599
- patternProperties: {
600
- "^citation-": false
601
- },
602
- $defs: {
603
- citationEntry: {
604
- type: "object",
605
- additionalProperties: false,
606
- required: [
607
- "source"
608
- ],
609
- 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.",
610
- properties: {
611
- id: {
612
- type: "string",
613
- pattern: "^[a-z0-9][a-z0-9-]*$",
614
- 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."
615
- },
616
- claim: {
617
- $ref: "#/$defs/claim"
618
- },
619
- source: {
620
- $ref: "#/$defs/source"
621
- },
622
- quote: {
623
- type: "boolean",
624
- default: false,
625
- 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."
626
- }
627
- }
628
- },
629
- claim: {
630
- type: "object",
631
- additionalProperties: false,
632
- required: [
633
- "integrity"
634
- ],
635
- 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.",
636
- properties: {
637
- lines: {
638
- $ref: "#/$defs/lines",
639
- 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."
640
- },
641
- integrity: {
642
- type: "string",
643
- pattern: "^sha256-[0-9a-f]{64}$",
644
- 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."
645
- }
646
- }
647
- },
648
- source: {
649
- type: "object",
650
- additionalProperties: false,
651
- required: [
652
- "file",
653
- "integrity"
654
- ],
655
- description: "The lines the claim rests on: a file, an optional line range, a pin, and the commit the pin was taken at.",
656
- properties: {
657
- file: {
658
- $ref: "#/$defs/fileRef"
659
- },
660
- lines: {
661
- $ref: "#/$defs/lines",
662
- description: "File lines. Absent, the whole file is pinned. The lines stay readable even when `file` is encrypted."
663
- },
664
- integrity: {
665
- type: "string",
666
- pattern: "^(?:sha256|hmac-sha256)-[0-9a-f]{64}$",
667
- 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."
668
- },
669
- "commit-sha": {
670
- type: "string",
671
- pattern: "^[0-9a-f]{7,64}$",
672
- 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."
673
- }
674
- }
675
- },
676
- lines: {
677
- oneOf: [
678
- {
679
- type: "integer",
680
- minimum: 1
681
- },
682
- {
683
- type: "string",
684
- pattern: "^[1-9][0-9]*-[1-9][0-9]*$"
685
- }
686
- ],
687
- 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.'
688
- },
689
- fileRef: {
690
- type: "string",
691
- pattern: "^(?:~[A-Za-z0-9_-]{82,}|(?!~)(?:(?!\\.\\.?(?:/|$))[^/\\\\:\\r\\n\\t]+)(?:/(?:(?!\\.\\.?(?:/|$))[^/\\\\:\\r\\n\\t]+))*)$",
692
- 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."
693
- }
694
- }
695
- };
696
-
697
583
  // src/shared/code-regions.ts
698
584
  var SKIP_MARKDOWN_FENCE = /^[ \t]*(`{3,}|~{3,})/;
699
585
  var SKIP_ASCIIDOC_FENCE = /^(-{4,})/;
@@ -1215,9 +1101,9 @@ var entryValidator;
1215
1101
  function compileEntryValidator() {
1216
1102
  if (entryValidator) return entryValidator;
1217
1103
  const ajv = new Ajv2020({ allErrors: false, strict: false });
1218
- ajv.addSchema(citations_default);
1104
+ ajv.addSchema(__default);
1219
1105
  entryValidator = ajv.compile({
1220
- $ref: `${citations_default.$id}#/$defs/citationEntry`
1106
+ $ref: `${__default.$id}#/$defs/citationEntry`
1221
1107
  });
1222
1108
  return entryValidator;
1223
1109
  }
@@ -1900,6 +1786,19 @@ function gitClient(root) {
1900
1786
  });
1901
1787
  return {
1902
1788
  available,
1789
+ shallow,
1790
+ // `merge-base --is-ancestor` exits 0 for yes and 1 for no. For a commit
1791
+ // it does not have it exits 128, `Not a valid commit name`, which reads
1792
+ // as git failing. So `hasCommit` answers first, and is required rather
1793
+ // than an optimization: a commit git lacks is in no history it holds,
1794
+ // which is a no, and exactly the pin a deleted branch leaves behind.
1795
+ isAncestor: (commit, of) => once(`ancestor\0${commit}\0${of}`, async () => {
1796
+ if (!await hasCommit(commit)) return false;
1797
+ const out = await run2(root, ["merge-base", "--is-ancestor", "--end-of-options", commit, of]);
1798
+ if (out.ok) return true;
1799
+ if (out.code === 1) return false;
1800
+ throw failed("merge-base", out);
1801
+ }),
1903
1802
  head: () => once("head", async () => {
1904
1803
  if (!await available()) return null;
1905
1804
  const out = await run2(root, ["rev-parse", "HEAD"]);
@@ -1966,6 +1865,7 @@ function gitClient(root) {
1966
1865
  var UNIT = "";
1967
1866
  var GIT_UNAVAILABLE_HISTORY = "git is not available here, so citations are checked without history: no never-true, no reanchored claims, no diffs, no commit subjects.";
1968
1867
  var PAGE_HISTORY_UNAVAILABLE = "the page history ends before the pin held; use fetch-depth: 0 to tell reanchored claims from changed ones";
1868
+ var SHALLOW_RECOMMIT = "this clone is shallow, so --recommit re-recorded only commits that do not contain their lines. Fetch full history to check the rest.";
1969
1869
  var GIT_UNAVAILABLE_COMMIT = "git is not available here, so the citation records no commit.";
1970
1870
  function noGit() {
1971
1871
  return {
@@ -2502,52 +2402,80 @@ function twoManifestsRefusal(label, a, b) {
2502
2402
  return `${label} is in collections ${a} and ${b}, and both keep citations in a manifest.`;
2503
2403
  }
2504
2404
  function ownsCitations(manifest) {
2505
- return manifest.keys.includes(CITATIONS_KEY2);
2405
+ return manifest.keys === void 0 || manifest.keys.includes(CITATIONS_KEY2);
2406
+ }
2407
+ function emptyIndex() {
2408
+ return { owners: /* @__PURE__ */ new Map(), byPath: /* @__PURE__ */ new Map(), byField: /* @__PURE__ */ new Map(), entries: [] };
2506
2409
  }
2507
2410
  async function loadCitationSidecars(opts) {
2508
2411
  const toError = opts.toError ?? ((message) => new CiteError(message));
2509
2412
  const manifests = [];
2413
+ const urls = [];
2510
2414
  const scoped = [];
2511
2415
  for (const collection of opts.collections) {
2512
2416
  const owning = collection.externalMetadata.filter(ownsCitations);
2513
2417
  if (owning.length === 0) continue;
2418
+ const loaded = [];
2514
2419
  for (const manifest of owning) {
2420
+ const implied = manifest.keys === void 0 ? { implied: { collection, manifest } } : {};
2515
2421
  if (classifyRef(manifest.file).kind === "url") {
2516
- throw toError(urlManifestRefusal(opts.configSource, collection.name));
2422
+ if (manifest.keys !== void 0) {
2423
+ throw toError(urlManifestRefusal(opts.configSource, collection.name));
2424
+ }
2425
+ urls.push({ collection: collection.name, path: manifest.file, file: manifest.file, join: externalMetadataJoin(manifest), ...implied });
2426
+ continue;
2517
2427
  }
2428
+ loaded.push(manifest);
2518
2429
  const path = isAbsolute(manifest.file) ? manifest.file : resolve(opts.configDir, manifest.file);
2519
2430
  manifests.push({
2520
2431
  collection: collection.name,
2521
2432
  path,
2522
2433
  file: reportedPath(path, opts.base),
2523
2434
  join: externalMetadataJoin(manifest),
2524
- ...hasPagePlaceholder(manifest.file) ? { pattern: manifest.file } : {}
2435
+ ...hasPagePlaceholder(manifest.file) ? { pattern: manifest.file } : {},
2436
+ ...implied
2525
2437
  });
2526
2438
  }
2527
- scoped.push({ ...collection, externalMetadata: owning });
2439
+ if (loaded.length > 0) scoped.push({ ...collection, externalMetadata: loaded });
2528
2440
  }
2529
- if (manifests.length === 0) return null;
2441
+ if (manifests.length === 0 && urls.length === 0) return null;
2530
2442
  const index = await loadExternalMetadata(scoped, {
2531
2443
  configDir: opts.configDir,
2532
2444
  base: opts.base,
2533
2445
  ...opts.pages === void 0 ? {} : { pages: opts.pages }
2534
- });
2446
+ }) ?? (urls.length > 0 ? emptyIndex() : null);
2535
2447
  if (index === null) return null;
2536
- return sidecars(index, manifests, scoped, opts);
2448
+ return sidecars(index, manifests, urls, scoped, opts);
2537
2449
  }
2538
- function sidecars(index, manifests, collections, opts) {
2450
+ function sidecars(index, manifests, urls, collections, opts) {
2539
2451
  const toError = opts.toError ?? ((message) => new CiteError(message));
2540
2452
  const { configDir, base } = opts;
2453
+ let marks;
2454
+ const marksOnce = () => marks ??= opts.marks?.() ?? Promise.resolve(void 0);
2541
2455
  const pageOwner = (owner, label) => {
2542
2456
  if (owner.pattern === void 0) return owner;
2543
2457
  const resolved = pageManifestPath(owner.pattern, configDir, resolve(base, label));
2544
2458
  if ("outside" in resolved) return void 0;
2545
2459
  return { ...owner, path: resolved.abs, file: reportedPath(resolved.abs, base) };
2546
2460
  };
2547
- const forPage = (label, content, format) => {
2461
+ const forPage = async (label, content, format) => {
2462
+ const declaredMembers = memberOf(opts.collections, configDir, base, label);
2463
+ if (declaredMembers.length === 0) return { joins: [] };
2548
2464
  const members = memberOf(collections, configDir, base, label);
2549
- if (members.length === 0) return { joins: [] };
2550
- const mine = manifests.filter((m) => members.includes(m.collection));
2465
+ const memberOfCollection = (m) => declaredMembers.includes(m.collection);
2466
+ let extracted;
2467
+ const extract = () => extracted ??= pickExtractor(label, format).extract(content, label);
2468
+ const candidates2 = [...manifests, ...urls].filter((m) => m.implied !== void 0 && memberOfCollection(m));
2469
+ let marked;
2470
+ if (candidates2.length > 0) {
2471
+ const read = await marksOnce();
2472
+ const data = extract().data;
2473
+ marked = read === void 0 ? void 0 : await read(label, Object.hasOwn(data, CITATIONS_KEY2) ? data : { ...data, [CITATIONS_KEY2]: null }, declaredMembers);
2474
+ }
2475
+ const keeps = (m) => m.implied === void 0 || ownsKey(m.implied.collection, m.implied.manifest, CITATIONS_KEY2, marked);
2476
+ const url = urls.find((m) => memberOfCollection(m) && keeps(m));
2477
+ if (url !== void 0) throw toError(urlManifestRefusal(opts.configSource, url.collection));
2478
+ const mine = manifests.filter((m) => members.includes(m.collection) && keeps(m));
2551
2479
  const [declared, second] = mine;
2552
2480
  if (declared === void 0) return { joins: [] };
2553
2481
  if (second !== void 0) {
@@ -2555,17 +2483,17 @@ function sidecars(index, manifests, collections, opts) {
2555
2483
  }
2556
2484
  const owner = pageOwner(declared, label);
2557
2485
  if (owner === void 0) return { joins: [] };
2558
- const extractor = pickExtractor(label, format);
2559
- const extracted = extractor.extract(content, label);
2560
- const merged = mergeExternalMetadata(label, extracted, index, members, base, {
2561
- encryptionKey: () => opts.key
2486
+ const readMarks = await marksOnce();
2487
+ const merged = await mergeWithMarks(label, extract(), index, members, base, {
2488
+ encryptionKey: () => opts.key,
2489
+ ...readMarks === void 0 ? {} : { marks: readMarks }
2562
2490
  });
2563
2491
  const out = { owner, joins: merged.joins };
2564
2492
  if (owner.join === PATH_JOIN) {
2565
2493
  out.entry = toPosix(relative(configDir, resolve(base, label)));
2566
2494
  } else {
2567
2495
  const value = merged.joins.find((j) => j.field === owner.join)?.value;
2568
- const raw = value ?? extracted.data[owner.join];
2496
+ const raw = value ?? extract().data[owner.join];
2569
2497
  if (typeof raw === "string" && raw !== "") out.entry = raw;
2570
2498
  else if (typeof raw === "number" || typeof raw === "boolean") out.entry = String(raw);
2571
2499
  }
@@ -2624,6 +2552,7 @@ function duplicateJoinRefusal(field, value, file, labels) {
2624
2552
  function sidecarsFor(run3, over = {}) {
2625
2553
  const declared = run3.configFile?.collections ?? [];
2626
2554
  if (run3.configDir === void 0 || declared.length === 0) return Promise.resolve(null);
2555
+ const { configPath } = run3;
2627
2556
  return loadCitationSidecars({
2628
2557
  // Every declared collection, whatever `--collection` or the paths chose.
2629
2558
  collections: declared,
@@ -2632,7 +2561,10 @@ function sidecarsFor(run3, over = {}) {
2632
2561
  configSource: run3.configSource ?? run3.configPath ?? "the config",
2633
2562
  ...run3.key === void 0 ? {} : { key: run3.key },
2634
2563
  ...over.toError === void 0 ? {} : { toError: over.toError },
2635
- ...over.pages === void 0 ? {} : { pages: over.pages }
2564
+ ...over.pages === void 0 ? {} : { pages: over.pages },
2565
+ // A keyless manifest (proposal 0068) keeps what a page's schemas mark,
2566
+ // read from meta's section of the same file.
2567
+ ...configPath === void 0 ? {} : { marks: () => configMarks(configPath, run3.base) }
2636
2568
  });
2637
2569
  }
2638
2570
 
@@ -3400,7 +3332,7 @@ async function checkCitations(page, opts) {
3400
3332
  page: read,
3401
3333
  lines,
3402
3334
  root: opts.root,
3403
- index: index ?? emptyIndex,
3335
+ index: index ?? emptyIndex2,
3404
3336
  checkSources,
3405
3337
  ...opts.key === void 0 ? {} : { key: opts.key }
3406
3338
  };
@@ -3511,7 +3443,7 @@ function skippedSource(entry) {
3511
3443
  if (source["commit-sha"] !== void 0) out.commitSha = source["commit-sha"];
3512
3444
  return out;
3513
3445
  }
3514
- var emptyIndex = { files: () => [], has: () => false };
3446
+ var emptyIndex2 = { files: () => [], has: () => false };
3515
3447
 
3516
3448
  // src/cite/core/mint.ts
3517
3449
  var COMMIT_PATTERN = /^[0-9a-f]{7,64}$/;
@@ -3868,8 +3800,8 @@ async function prepareRun(opts, action, verb, sources = {}) {
3868
3800
  };
3869
3801
  if (checkSources) pageOptions.sourceIndex = await sourceIndexFor(run3.root, git);
3870
3802
  const sidecars2 = await sidecarsFor(run3, { pages: files.map((file) => resolve3(base, file)) });
3871
- const setupFor = (label, content) => {
3872
- const page = sidecars2?.forPage(label, content, forced?.name);
3803
+ const setupFor = async (label, content) => {
3804
+ const page = await sidecars2?.forPage(label, content, forced?.name);
3873
3805
  if (page?.owner === void 0) return { options: pageOptions };
3874
3806
  return {
3875
3807
  sidecar: page,
@@ -3933,7 +3865,7 @@ async function runCheck(opts) {
3933
3865
  const hits = joinHits();
3934
3866
  const pages = [];
3935
3867
  const checkOne = async (label, content) => {
3936
- const setup = prepared.setupFor(label, content);
3868
+ const setup = await prepared.setupFor(label, content);
3937
3869
  if (setup.sidecar !== void 0) hits.record(setup.sidecar, label);
3938
3870
  pages.push(await checkCitations({ file: label, content, format: forced?.name }, setup.options));
3939
3871
  };
@@ -4055,7 +3987,7 @@ async function runAdd(opts) {
4055
3987
  `A page read from stdin has no path, and its citations live in ${firstManifest.file}, which is keyed by ${keyedBy}.`
4056
3988
  );
4057
3989
  }
4058
- const sidecar = usingStdin ? void 0 : sidecars2?.forPage(label, content, opts.as);
3990
+ const sidecar = usingStdin ? void 0 : await sidecars2?.forPage(label, content, opts.as);
4059
3991
  const owner = sidecar?.owner;
4060
3992
  const page = readPage(label, content, {
4061
3993
  ...opts.as === void 0 ? {} : { format: opts.as },
@@ -4351,7 +4283,7 @@ async function runRemove(opts) {
4351
4283
  const pageCount = files.length + (usingStdin ? 1 : 0);
4352
4284
  const planned = [];
4353
4285
  const removeOne = async (label, content, path) => {
4354
- const setup = prepared.setupFor(label, content);
4286
+ const setup = await prepared.setupFor(label, content);
4355
4287
  if (setup.sidecar !== void 0) hits.record(setup.sidecar, label);
4356
4288
  const owner = setup.sidecar?.owner;
4357
4289
  const page = readPage(label, content, {
@@ -4806,6 +4738,7 @@ function rewriteOf(plan) {
4806
4738
  reason: "recommitted",
4807
4739
  // The source itself held. Only its date was wrong.
4808
4740
  status: "current",
4741
+ because: plan.because,
4809
4742
  from: plan.from,
4810
4743
  to: plan.commitSha,
4811
4744
  fromCommit: plan.from,
@@ -5017,19 +4950,29 @@ async function runUpdate(opts) {
5017
4950
  "--recommit needs git to verify recorded commits, and git is not available here."
5018
4951
  );
5019
4952
  }
4953
+ const outsideHistory = async (commit, head) => {
4954
+ if (git.shallow === void 0 || git.isAncestor === void 0) return false;
4955
+ if (await git.shallow()) return false;
4956
+ return !await git.isAncestor(commit, head);
4957
+ };
5020
4958
  const recommitFor = async (result) => {
5021
4959
  const recorded = result.citation.source["commit-sha"];
5022
4960
  const path = result.source.resolvedPath;
5023
4961
  if (recorded === void 0 || path === void 0) return void 0;
4962
+ const head = await git.head();
4963
+ if (head === null || head === recorded) return void 0;
5024
4964
  const range = sourceRange(result.citation.source);
5025
4965
  const pin = result.citation.source.integrity;
5026
4966
  const key = range.encrypted ? run3.key : void 0;
5027
- const was = await historyOf(git, recorded, path, range, pin, key, void 0);
5028
- if (was.kind !== "never-true") return void 0;
5029
- const head = await git.head();
5030
- if (head === null || head === recorded) return void 0;
4967
+ let because;
4968
+ if (await outsideHistory(recorded, head)) because = "not-in-history";
4969
+ else {
4970
+ const was = await historyOf(git, recorded, path, range, pin, key, void 0);
4971
+ if (was.kind !== "never-true") return void 0;
4972
+ because = "not-contained";
4973
+ }
5031
4974
  const now = await historyOf(git, head, path, range, pin, key, void 0);
5032
- return now.kind === "original" ? head : void 0;
4975
+ return now.kind === "original" ? { commit: head, because } : void 0;
5033
4976
  };
5034
4977
  const plansFor = async (result, entry, page, lines, lineNow, declined) => {
5035
4978
  const out = [];
@@ -5129,7 +5072,8 @@ async function runUpdate(opts) {
5129
5072
  kind: "source-recommitted",
5130
5073
  result,
5131
5074
  from: result.citation.source["commit-sha"] ?? "",
5132
- commitSha: to
5075
+ commitSha: to.commit,
5076
+ because: to.because
5133
5077
  });
5134
5078
  }
5135
5079
  }
@@ -5227,7 +5171,7 @@ async function runUpdate(opts) {
5227
5171
  };
5228
5172
  const pages = [];
5229
5173
  const updateOne = async (label, content, path) => {
5230
- const setup = prepared.setupFor(label, content);
5174
+ const setup = await prepared.setupFor(label, content);
5231
5175
  if (setup.sidecar !== void 0) hits.record(setup.sidecar, label);
5232
5176
  const report = await checkCitations({ file: label, content, format: forced?.name }, setup.options);
5233
5177
  const { format } = report;
@@ -5444,7 +5388,12 @@ async function runUpdate(opts) {
5444
5388
  const reminted = pages.some(
5445
5389
  (page) => page.rewritten.some((r) => r.reason === "accepted" && r.end === "source")
5446
5390
  );
5447
- sayNotices(reports, opts.onNotice, reminted && !await git.available() ? [GIT_UNAVAILABLE_COMMIT] : []);
5391
+ const also = [];
5392
+ if (reminted && !await git.available()) also.push(GIT_UNAVAILABLE_COMMIT);
5393
+ if (recommit && git.shallow !== void 0 && git.isAncestor !== void 0 && await git.shallow()) {
5394
+ also.push(SHALLOW_RECOMMIT);
5395
+ }
5396
+ sayNotices(reports, opts.onNotice, also);
5448
5397
  const rewritten = pages.reduce((n, page) => n + citationsIn(page.rewritten), 0);
5449
5398
  const skipped = pages.reduce((n, page) => n + page.skipped.length + page.refused.length, 0);
5450
5399
  const undone = pages.some(
@@ -5871,6 +5820,15 @@ function refusalLine(rewrite) {
5871
5820
  }
5872
5821
  return `${where} that line now holds different text than the claim at ${at2}. Re-add it with cite add.`;
5873
5822
  }
5823
+ function recommitWhy(because) {
5824
+ switch (because) {
5825
+ case "not-in-history":
5826
+ return "not in this branch's history";
5827
+ case "not-contained":
5828
+ case void 0:
5829
+ return "did not contain the pinned lines";
5830
+ }
5831
+ }
5874
5832
  function rewriteLine(rewrite) {
5875
5833
  const word = rewrite.from.includes("-") ? "lines" : "line";
5876
5834
  const status = rewrite.status === "never-true" ? "never true" : rewrite.status;
@@ -5880,7 +5838,7 @@ function rewriteLine(rewrite) {
5880
5838
  if (rewrite.reason === "recommitted") {
5881
5839
  const from = shortCommit(rewrite.fromCommit ?? "");
5882
5840
  const to = shortCommit(rewrite.toCommit ?? "");
5883
- return `source ${shortSrc(rewrite.src ?? "")} commit ${from} -> ${to} (did not contain the pinned lines)`;
5841
+ return `source ${shortSrc(rewrite.src ?? "")} commit ${from} -> ${to} (${recommitWhy(rewrite.because)})`;
5884
5842
  }
5885
5843
  if (rewrite.reason === "shifted") {
5886
5844
  return `claim ${word} ${rewrite.from} -> ${rewrite.to} (shifted by a marker)`;
@@ -6164,7 +6122,7 @@ function encryptedCitations(file, content, format) {
6164
6122
  async function rotateManifestCitations(sidecars2, manifests, page, opts) {
6165
6123
  const none = { rewritten: [], skipped: [], wantsHistory: false };
6166
6124
  if (sidecars2 === null) return none;
6167
- const sidecar = sidecars2.forPage(page.file, page.content, page.format);
6125
+ const sidecar = await sidecars2.forPage(page.file, page.content, page.format);
6168
6126
  const owner = sidecar.owner;
6169
6127
  if (owner === void 0 || sidecar.citations === void 0) return none;
6170
6128
  if (!sidecar.citations.some((input) => ANY_CIPHERTEXT.test(JSON.stringify(input.entry)))) {
@@ -6384,6 +6342,8 @@ async function runKeyRotate(opts) {
6384
6342
  configSource: file.source,
6385
6343
  key: current,
6386
6344
  toError: toKeyError,
6345
+ // A keyless manifest (proposal 0068) keeps what a page's schemas mark.
6346
+ marks: () => configMarks(file.path, file.dir),
6387
6347
  // A `{page}` manifest (0058) is read for exactly the pages rotated.
6388
6348
  pages: files.map((rel) => resolve7(base, rel))
6389
6349
  });
@@ -6578,4 +6538,4 @@ export {
6578
6538
  runKeyRotate,
6579
6539
  runKeySet
6580
6540
  };
6581
- //# sourceMappingURL=chunk-UKRYPLZS.js.map
6541
+ //# sourceMappingURL=chunk-W75SLU7Z.js.map