@hawkeyexl/manni 2.3.10 → 2.4.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.
@@ -55,6 +55,7 @@ import {
55
55
  pinOfLines,
56
56
  readConfigFile,
57
57
  readFamilyConfigFile,
58
+ removeManifestKey,
58
59
  resolveBaselineRequest,
59
60
  resolveEncryptionKey,
60
61
  resolveTargetSet,
@@ -70,7 +71,7 @@ import {
70
71
  withValueAt,
71
72
  writeEncryptionKey,
72
73
  writeFileAtomic
73
- } from "./chunk-TNT3ZROC.js";
74
+ } from "./chunk-LYLNOJ32.js";
74
75
 
75
76
  // src/meta/core/reencrypt.ts
76
77
  import { extname } from "node:path";
@@ -196,6 +197,7 @@ __export(cite_exports, {
196
197
  bodyLineOf: () => bodyLineOf,
197
198
  buildSourceIndex: () => buildSourceIndex,
198
199
  checkCitations: () => checkCitations,
200
+ citationInputs: () => citationInputs,
199
201
  claimEnd: () => claimEnd,
200
202
  claimLine: () => claimLine,
201
203
  claimMessageFor: () => claimMessageFor,
@@ -247,9 +249,14 @@ __export(cite_exports, {
247
249
  readSource: () => readSource,
248
250
  reencryptCitationEntries: () => reencryptCitationEntries,
249
251
  reencryptCitations: () => reencryptCitations,
252
+ removalLine: () => removalLine,
253
+ removeFrontmatterCitations: () => removeFrontmatterCitations,
254
+ removeLine: () => removeLine,
250
255
  renderCheckGithub: () => renderCheckGithub,
251
256
  renderCheckJson: () => renderCheckJson,
252
257
  renderCheckPretty: () => renderCheckPretty,
258
+ renderRemoveJson: () => renderRemoveJson,
259
+ renderRemovePretty: () => renderRemovePretty,
253
260
  renderUpdateJson: () => renderUpdateJson,
254
261
  renderUpdatePretty: () => renderUpdatePretty,
255
262
  resolveCiteRun: () => resolveCiteRun,
@@ -258,7 +265,9 @@ __export(cite_exports, {
258
265
  ruleId: () => ruleId,
259
266
  runAdd: () => runAdd,
260
267
  runCheck: () => runCheck,
268
+ runRemove: () => runRemove,
261
269
  runUpdate: () => runUpdate,
270
+ shiftedEntries: () => shiftedEntries,
262
271
  shortCommit: () => shortCommit,
263
272
  shortLine: () => shortLine,
264
273
  shortPin: () => shortPin,
@@ -266,6 +275,7 @@ __export(cite_exports, {
266
275
  sidecarsFor: () => sidecarsFor,
267
276
  sliceLines: () => sliceLines2,
268
277
  sourceRange: () => sourceRange,
278
+ spellAt: () => spellAt,
269
279
  spellLines: () => spellLines,
270
280
  spellSource: () => spellSource,
271
281
  splice: () => splice,
@@ -279,7 +289,8 @@ __export(cite_exports, {
279
289
  unifiedDiff: () => unifiedDiff,
280
290
  unitAt: () => unitAt,
281
291
  urlManifestRefusal: () => urlManifestRefusal,
282
- validateEntry: () => validateEntry
292
+ validateEntry: () => validateEntry,
293
+ withClaimLines: () => withClaimLines
283
294
  });
284
295
 
285
296
  // src/cite/types.ts
@@ -1056,7 +1067,7 @@ var MAX_BUFFER = 32 * 1024 * 1024;
1056
1067
  var NO_COMMIT = /invalid object name|bad revision|unknown revision|bad object/i;
1057
1068
  var NO_PATH = /does not exist in|exists on disk, but not in|path .* does not exist/i;
1058
1069
  function run2(root, args) {
1059
- return new Promise((resolve8) => {
1070
+ return new Promise((resolve9) => {
1060
1071
  execFile(
1061
1072
  "git",
1062
1073
  ["-c", "core.quotepath=false", ...args],
@@ -1065,8 +1076,8 @@ function run2(root, args) {
1065
1076
  if (error) {
1066
1077
  const failure = { ok: false, stderr, message: error.message };
1067
1078
  if (typeof error.code === "number") failure.code = error.code;
1068
- resolve8(failure);
1069
- } else resolve8({ ok: true, stdout });
1079
+ resolve9(failure);
1080
+ } else resolve9({ ok: true, stdout });
1070
1081
  }
1071
1082
  );
1072
1083
  });
@@ -1457,6 +1468,17 @@ function bodyLineOf(content, bodyOffset) {
1457
1468
  const line = lineAt(content, bodyOffset);
1458
1469
  return content.charCodeAt(bodyOffset - 1) === 10 ? line : line + 1;
1459
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
+ }
1460
1482
  function readPage(file, content, opts) {
1461
1483
  const extractor = pickExtractor(file, opts?.format);
1462
1484
  const format = extractor.name;
@@ -1500,12 +1522,7 @@ function readPage(file, content, opts) {
1500
1522
  })
1501
1523
  );
1502
1524
  }
1503
- const inputs = injected !== void 0 ? [...injected] : (Array.isArray(rawCitations) ? rawCitations : []).map((entry, index) => {
1504
- const origin = { kind: "frontmatter", file };
1505
- const line = extracted.lineFor(`/citations/${String(index)}`);
1506
- if (line !== void 0) origin.line = line;
1507
- return { entry, origin };
1508
- });
1525
+ const inputs = citationInputs(file, extracted, injected);
1509
1526
  inputs.forEach((input, index) => {
1510
1527
  const origin = { ...input.origin, index };
1511
1528
  const where = { index };
@@ -1661,6 +1678,45 @@ function readPage(file, content, opts) {
1661
1678
  };
1662
1679
  }
1663
1680
 
1681
+ // 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
+ function shiftedEntries(opts) {
1686
+ const { citations, at: at2, delta, bodyLine, label } = opts;
1687
+ const out = { frontmatter: [], manifest: /* @__PURE__ */ new Map() };
1688
+ if (at2.length === 0) return out;
1689
+ for (const { citation, origin } of citations) {
1690
+ const spec = citation.claim?.lines;
1691
+ const recorded = spec === void 0 ? void 0 : parseLines(spec);
1692
+ if (recorded === void 0) continue;
1693
+ const file = toFileLines(recorded, bodyLine);
1694
+ const inside = at2.find(
1695
+ (line) => delta === 1 ? line > file.start && line <= file.end : line >= file.start && line <= file.end
1696
+ );
1697
+ if (inside !== void 0) {
1698
+ const whose = citation.id === void 0 ? `the claim at ${spellAt(file)}` : `the claim of ${citation.id} (${spellAt(file)})`;
1699
+ const why = delta === 1 ? "A marker there would change its pin." : "Removing the marker there would change its pin.";
1700
+ throw new CiteError(`${label}:${String(inside)} is inside ${whose}. ${why}`);
1701
+ }
1702
+ const lines = at2.filter((line) => delta === 1 ? line <= file.start : line < file.start).length;
1703
+ if (lines === 0) continue;
1704
+ const moved = lineSpec({
1705
+ start: recorded.start + lines * delta,
1706
+ end: recorded.end + lines * delta
1707
+ });
1708
+ if (origin.kind === "manifest") out.manifest.set(origin.index, moved);
1709
+ else out.frontmatter.push({ index: origin.index, lines: moved });
1710
+ }
1711
+ return out;
1712
+ }
1713
+ function withClaimLines(entry, lines) {
1714
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return entry;
1715
+ const claim = entry.claim;
1716
+ if (typeof claim !== "object" || claim === null || Array.isArray(claim)) return entry;
1717
+ return { ...entry, claim: { ...claim, lines } };
1718
+ }
1719
+
1664
1720
  // src/cite/core/sidecar.ts
1665
1721
  import { isAbsolute, relative, resolve, sep as sep2 } from "node:path";
1666
1722
  var CITATIONS_KEY2 = "citations";
@@ -1822,6 +1878,42 @@ function appendFrontmatterCitation(content, format, citation, filePath) {
1822
1878
  throw e;
1823
1879
  }
1824
1880
  }
1881
+ function removeFrontmatterCitations(content, format, drop, filePath) {
1882
+ const label = filePath ?? "the page";
1883
+ const extractor = extractorFor(format);
1884
+ if (!extractor.apply || !readsFencedFrontmatter(extractor, label)) {
1885
+ throw new CiteError(
1886
+ `${label} has no frontmatter to write to; keep its citations in a manifest instead.`
1887
+ );
1888
+ }
1889
+ if (locateFrontmatter(content)?.flavor === "toml") {
1890
+ throw new CiteError(
1891
+ `${label} has TOML frontmatter; manni cannot rewrite a citations table without re-emitting the whole block, which would drop its comments. Remove the entry by hand.`
1892
+ );
1893
+ }
1894
+ const raw = extractor.extract(content, label).data.citations ?? [];
1895
+ if (!Array.isArray(raw)) {
1896
+ throw new CiteError(`${label}: \`citations\` is not a list; edit it by hand.`);
1897
+ }
1898
+ const existing = raw;
1899
+ const kept = existing.filter((_entry, index) => !drop.has(index));
1900
+ try {
1901
+ return kept.length === 0 ? extractor.apply(content, {}, { deletions: ["citations"] }) : extractor.apply(content, { citations: kept });
1902
+ } catch (e) {
1903
+ if (e instanceof DocmetaError) throw new CiteError(`${label}: ${e.message}`);
1904
+ throw e;
1905
+ }
1906
+ }
1907
+ function removeLine(content, line) {
1908
+ const start = offsetOfLine(content, line);
1909
+ const nl = content.indexOf("\n", start);
1910
+ if (nl === -1) {
1911
+ let above = start > 0 ? content.lastIndexOf("\n", start - 1) : -1;
1912
+ if (above > 0 && content.charAt(above - 1) === "\r") above--;
1913
+ return content.slice(0, above === -1 ? 0 : above);
1914
+ }
1915
+ return content.slice(0, start) + content.slice(nl + 1);
1916
+ }
1825
1917
  function entryObject(citation) {
1826
1918
  const source = { file: citation.source.file };
1827
1919
  if (citation.source.lines !== void 0) source.lines = citation.source.lines;
@@ -2013,6 +2105,25 @@ var ManifestSet = class {
2013
2105
  held.text = spliced.text;
2014
2106
  return { file: manifest.file, line: itemLine(spliced.text, entry, index, manifest.join) ?? spliced.line };
2015
2107
  }
2108
+ /**
2109
+ * Take a page's `citations` key out of the manifest, for the removal that
2110
+ * leaves it with no entries. The entry itself goes when that key was all it
2111
+ * had, so a manifest never keeps a document nothing is recorded about.
2112
+ */
2113
+ async remove(manifest, entry) {
2114
+ const held = await this.hold(manifest);
2115
+ try {
2116
+ held.text = removeManifestKey(held.text, {
2117
+ entry,
2118
+ key: CITATIONS_KEY2,
2119
+ join: manifest.join,
2120
+ file: manifest.file
2121
+ }).text;
2122
+ } catch (error) {
2123
+ if (error instanceof DocmetaError) throw new CiteError(error.message);
2124
+ throw error;
2125
+ }
2126
+ }
2016
2127
  /** The manifests this run rewrote, with the diff of each. */
2017
2128
  changed() {
2018
2129
  return [...this.held.values()].filter((m) => m.text !== m.before).map((m) => ({ ...m, diff: unifiedDiff(m.file, m.before, m.text) }));
@@ -2629,7 +2740,9 @@ async function resolveCiteRun(opts) {
2629
2740
  root = gitRoot;
2630
2741
  } else {
2631
2742
  root = cwd;
2632
- opts.onNotice?.(`No git root found; resolving src: paths from ${cwd}`);
2743
+ if (opts.resolvesSources !== false) {
2744
+ opts.onNotice?.(`No git root found; resolving src: paths from ${cwd}`);
2745
+ }
2633
2746
  }
2634
2747
  }
2635
2748
  const { key, source: keySource } = resolveEncryptionKey({
@@ -2678,7 +2791,8 @@ function joinHits() {
2678
2791
  }
2679
2792
  };
2680
2793
  }
2681
- async function prepareRun(opts, action, verb, requireSources = false) {
2794
+ async function prepareRun(opts, action, verb, sources = {}) {
2795
+ const requireSources = sources.require === true;
2682
2796
  const cwd = resolve3(opts.cwd ?? process.cwd());
2683
2797
  const run3 = await resolveCiteRun({
2684
2798
  cwd,
@@ -2689,7 +2803,8 @@ async function prepareRun(opts, action, verb, requireSources = false) {
2689
2803
  root: opts.root,
2690
2804
  onConfigLoaded: opts.onConfigLoaded,
2691
2805
  onNotice: opts.onNotice,
2692
- env: opts.env
2806
+ env: opts.env,
2807
+ ...sources.resolve === false ? { resolvesSources: false } : {}
2693
2808
  });
2694
2809
  const { config, inputs, base } = run3;
2695
2810
  const checkSources = opts.checkSources ?? config?.checkSources ?? true;
@@ -2869,9 +2984,6 @@ async function citedText(root, index, src, key) {
2869
2984
  }
2870
2985
  return sliceLines2(splitLines(source.text), range, range.path);
2871
2986
  }
2872
- function spellAt(lines) {
2873
- return lines.start === lines.end ? `line ${String(lines.start)}` : `lines ${String(lines.start)}-${String(lines.end)}`;
2874
- }
2875
2987
  function entryName(entry) {
2876
2988
  return entry.citation.id ?? `/citations/${String(entry.origin.index)}`;
2877
2989
  }
@@ -2916,32 +3028,6 @@ function spellElsewhere(spans) {
2916
3028
  const last = named3.pop() ?? "";
2917
3029
  return named3.length === 0 ? `${noun} ${last}` : `${noun} ${named3.join(", ")} and ${last}`;
2918
3030
  }
2919
- function shiftedEntries(citations, insertLine, bodyLine, label) {
2920
- const out = { frontmatter: [], manifest: /* @__PURE__ */ new Map() };
2921
- for (const { citation, origin } of citations) {
2922
- const spec = citation.claim?.lines;
2923
- const recorded = spec === void 0 ? void 0 : parseLines(spec);
2924
- if (recorded === void 0) continue;
2925
- const file = toFileLines(recorded, bodyLine);
2926
- if (file.start < insertLine) {
2927
- if (file.end < insertLine) continue;
2928
- const whose = citation.id === void 0 ? `the claim at ${spellAt(file)}` : `the claim of ${citation.id} (${spellAt(file)})`;
2929
- throw new CiteError(
2930
- `${label}:${String(insertLine)} is inside ${whose}. A marker there would change its pin.`
2931
- );
2932
- }
2933
- const moved = lineSpec({ start: recorded.start + 1, end: recorded.end + 1 });
2934
- if (origin.kind === "manifest") out.manifest.set(origin.index, moved);
2935
- else out.frontmatter.push({ index: origin.index, lines: moved });
2936
- }
2937
- return out;
2938
- }
2939
- function withClaimLines(entry, lines) {
2940
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return entry;
2941
- const claim = entry.claim;
2942
- if (typeof claim !== "object" || claim === null || Array.isArray(claim)) return entry;
2943
- return { ...entry, claim: { ...claim, lines } };
2944
- }
2945
3031
  async function runAdd(opts) {
2946
3032
  const cwd = resolve4(opts.cwd ?? process.cwd());
2947
3033
  const run3 = await resolveCiteRun({
@@ -3065,7 +3151,13 @@ async function runAdd(opts) {
3065
3151
  `${at2} is in a ${holding.kind} at ${spellAt(holding)} that ${unitWide}. A marker anchors the whole ${holding.kind}.`
3066
3152
  );
3067
3153
  }
3068
- shifted = shiftedEntries(page.citations, holding.start, page.bodyLine, label);
3154
+ shifted = shiftedEntries({
3155
+ citations: page.citations,
3156
+ at: [holding.start],
3157
+ delta: 1,
3158
+ bodyLine: page.bodyLine,
3159
+ label
3160
+ });
3069
3161
  const statement = markerIndent(content, holding.start, format, page.bodyLine) + formatStatement(format, { kind: "ref", id: opts.id ?? "" });
3070
3162
  const insertAt = offsetOfLine(content, holding.start);
3071
3163
  body = insertStatementBefore(content, insertAt, statement);
@@ -3188,8 +3280,175 @@ async function runAdd(opts) {
3188
3280
  return result;
3189
3281
  }
3190
3282
 
3191
- // src/cite/commands/update.ts
3283
+ // src/cite/commands/remove.ts
3192
3284
  import { resolve as resolve5 } from "node:path";
3285
+ var POINTER = /^\/citations\/(0|[1-9][0-9]*)$/;
3286
+ function entryCount(n) {
3287
+ return `${String(n)} ${n === 1 ? "entry" : "entries"}`;
3288
+ }
3289
+ function idOf(entry) {
3290
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return void 0;
3291
+ const id = entry.id;
3292
+ return typeof id === "string" ? id : void 0;
3293
+ }
3294
+ async function runRemove(opts) {
3295
+ const only = [...new Set(opts.only)];
3296
+ if (only.length === 0) {
3297
+ throw new CiteError("remove needs --only <id>; it never removes every citation on a page.");
3298
+ }
3299
+ const prepared = await prepareRun({ ...opts, checkSources: false }, "removed", "remove", {
3300
+ resolve: false
3301
+ });
3302
+ const { run: run3, files, usingStdin, forced } = prepared;
3303
+ assertNoOrphans(prepared);
3304
+ const hits = joinHits();
3305
+ const manifests = new ManifestSet();
3306
+ const matched = /* @__PURE__ */ new Set();
3307
+ const pageCount = files.length + (usingStdin ? 1 : 0);
3308
+ const planned = [];
3309
+ const removeOne = async (label, content, path) => {
3310
+ const setup = prepared.setupFor(label, content);
3311
+ if (setup.sidecar !== void 0) hits.record(setup.sidecar, label);
3312
+ const owner = setup.sidecar?.owner;
3313
+ const page = readPage(label, content, {
3314
+ ...forced === void 0 ? {} : { format: forced.name },
3315
+ ...setup.options.citations === void 0 ? {} : { citations: setup.options.citations },
3316
+ ...owner === void 0 ? {} : { owned: { file: owner.file, collection: owner.collection } }
3317
+ });
3318
+ const { format } = page;
3319
+ const extractor = pickExtractor(label, forced?.name);
3320
+ const inputs = citationInputs(
3321
+ label,
3322
+ extractor.extract(content, label),
3323
+ setup.sidecar?.citations
3324
+ );
3325
+ const lines = splitLines(content);
3326
+ const entriesFor = (value) => {
3327
+ const [, digits] = POINTER.exec(value) ?? [];
3328
+ if (digits === void 0) {
3329
+ return inputs.flatMap((input, index2) => idOf(input.entry) === value ? [index2] : []);
3330
+ }
3331
+ const index = Number(digits);
3332
+ if (index < inputs.length) return [index];
3333
+ if (pageCount === 1) {
3334
+ throw new CiteError(
3335
+ `"${value}" is past the last entry of ${label} (${entryCount(inputs.length)}).`
3336
+ );
3337
+ }
3338
+ return [];
3339
+ };
3340
+ const markersFor = (id) => id === void 0 ? [] : page.statements.filter((s) => s.payload.kind === "ref" && s.payload.id === id).map((s) => s.line);
3341
+ const removed = [];
3342
+ const indices = /* @__PURE__ */ new Set();
3343
+ const markerLines = [];
3344
+ for (const value of only) {
3345
+ const found = entriesFor(value);
3346
+ for (const index of found) {
3347
+ const input = inputs[index];
3348
+ const id = idOf(input?.entry);
3349
+ const markers2 = markersFor(id);
3350
+ indices.add(index);
3351
+ markerLines.push(...markers2);
3352
+ removed.push({
3353
+ ...id === void 0 ? {} : { id },
3354
+ index,
3355
+ ...input === void 0 ? {} : { origin: input.origin },
3356
+ markerLines: markers2
3357
+ });
3358
+ matched.add(value);
3359
+ }
3360
+ if (found.length > 0) continue;
3361
+ const markers = markersFor(value);
3362
+ if (markers.length === 0) continue;
3363
+ markerLines.push(...markers);
3364
+ removed.push({ id: value, markerLines: markers });
3365
+ matched.add(value);
3366
+ }
3367
+ const nothing = { file: label, removed: [], diff: "", written: false };
3368
+ if (removed.length === 0) {
3369
+ if (path === void 0) nothing.content = content;
3370
+ planned.push({ page: nothing, ...path === void 0 ? {} : { path } });
3371
+ return;
3372
+ }
3373
+ const ordered = [...new Set(markerLines)].sort((a, b) => a - b);
3374
+ for (const line of ordered) {
3375
+ if (!isMarkerLine(lines[line - 1] ?? "", format)) {
3376
+ const id = page.statements.find((s) => s.line === line)?.payload;
3377
+ const named3 = id?.kind === "ref" ? ` ${id.id}` : "";
3378
+ throw new CiteError(
3379
+ `the marker${named3} at ${label}:${String(line)} shares its line with text; remove it by hand.`
3380
+ );
3381
+ }
3382
+ }
3383
+ const survivors = page.citations.filter((c) => !indices.has(c.origin.index));
3384
+ const shifted = shiftedEntries({
3385
+ citations: survivors,
3386
+ at: ordered,
3387
+ delta: -1,
3388
+ bodyLine: page.bodyLine,
3389
+ label
3390
+ });
3391
+ let after = content;
3392
+ for (const { index, lines: moved } of shifted.frontmatter) {
3393
+ after = spliceEntryField(after, format, index, ["claim", "lines"], moved);
3394
+ }
3395
+ for (const line of [...ordered].sort((a, b) => b - a)) after = removeLine(after, line);
3396
+ if (owner === void 0) {
3397
+ after = removeFrontmatterCitations(after, format, indices, label);
3398
+ } else {
3399
+ if (setup.sidecar?.entry === void 0) {
3400
+ throw new CiteError(
3401
+ `${label} carries no ${owner.join}: value, so its citations cannot be keyed in ${owner.file}.`
3402
+ );
3403
+ }
3404
+ const kept = inputs.map((input, index) => {
3405
+ const moved = shifted.manifest.get(index);
3406
+ return moved === void 0 ? input.entry : withClaimLines(input.entry, moved);
3407
+ }).filter((_entry, index) => !indices.has(index));
3408
+ if (kept.length === 0) await manifests.remove(owner, setup.sidecar.entry);
3409
+ else void await manifests.write(owner, setup.sidecar.entry, kept, 0);
3410
+ }
3411
+ const out = {
3412
+ file: label,
3413
+ removed,
3414
+ diff: after === content ? "" : unifiedDiff(label, content, after),
3415
+ written: false
3416
+ };
3417
+ if (path === void 0) out.content = after;
3418
+ planned.push({ page: out, ...after === content ? {} : { content: after }, ...path === void 0 ? {} : { path } });
3419
+ };
3420
+ if (usingStdin) await removeOne(STDIN_LABEL, opts.stdinContent ?? "");
3421
+ for (const file of files) {
3422
+ await removeOne(file, await readTarget(run3, file), resolve5(run3.base, file));
3423
+ }
3424
+ assertNoOrphanJoins(prepared, hits);
3425
+ const missing = only.find((value) => !matched.has(value));
3426
+ if (missing !== void 0) {
3427
+ const where = pageCount === 1 ? `${planned[0]?.page.file ?? "the page"} has no` : `none of ${String(pageCount)} pages has an`;
3428
+ const what = POINTER.test(missing) ? `entry ${missing}` : `entry or marker ${missing}`;
3429
+ throw new CiteError(`${where} ${what}.`);
3430
+ }
3431
+ const write = opts.dryRun !== true;
3432
+ for (const item of planned) {
3433
+ if (item.content === void 0 || item.path === void 0) continue;
3434
+ if (write) await writeFileAtomic(item.path, item.content);
3435
+ item.page.written = write;
3436
+ }
3437
+ const rewritten = [];
3438
+ for (const changed of manifests.changed()) {
3439
+ if (write) await writeFileAtomic(changed.path, changed.text);
3440
+ rewritten.push({ file: changed.file, diff: changed.diff, written: write });
3441
+ }
3442
+ const pages = planned.map((item) => item.page);
3443
+ return {
3444
+ pages,
3445
+ removed: pages.reduce((n, page) => n + page.removed.length, 0),
3446
+ ...rewritten.length > 0 ? { manifests: rewritten } : {}
3447
+ };
3448
+ }
3449
+
3450
+ // src/cite/commands/update.ts
3451
+ import { resolve as resolve6 } from "node:path";
3193
3452
  function settles(plan) {
3194
3453
  switch (plan.kind) {
3195
3454
  case "claim-moved":
@@ -3318,7 +3577,7 @@ function rewriteOf(plan) {
3318
3577
  }
3319
3578
  }
3320
3579
  async function runUpdate(opts) {
3321
- const prepared = await prepareRun(opts, "updated", "update", true);
3580
+ const prepared = await prepareRun(opts, "updated", "update", { require: true });
3322
3581
  const { run: run3, files, usingStdin, forced, pageOptions, git } = prepared;
3323
3582
  assertNoOrphans(prepared);
3324
3583
  const hits = joinHits();
@@ -3446,7 +3705,7 @@ async function runUpdate(opts) {
3446
3705
  const reports = [];
3447
3706
  if (usingStdin) reports.push(await updateOne(STDIN_LABEL, opts.stdinContent ?? ""));
3448
3707
  for (const file of files) {
3449
- reports.push(await updateOne(file, await readTarget(run3, file), resolve5(run3.base, file)));
3708
+ reports.push(await updateOne(file, await readTarget(run3, file), resolve6(run3.base, file)));
3450
3709
  }
3451
3710
  assertNoOrphanJoins(prepared, hits);
3452
3711
  const changedManifests = manifests.changed();
@@ -3849,6 +4108,50 @@ function renderUpdatePretty(run3, opts) {
3849
4108
  lines.push(run3.exitCode === 0 ? c.green(summary) : c.red(summary));
3850
4109
  return lines.join("\n");
3851
4110
  }
4111
+ function spellLineList(at2, noun) {
4112
+ if (at2.length === 0) return noun;
4113
+ const spelled = at2.map((line) => String(line));
4114
+ if (spelled.length === 1) return `${noun} ${spelled[0] ?? ""}`;
4115
+ const last = spelled[spelled.length - 1] ?? "";
4116
+ return `${noun}s ${spelled.slice(0, -1).join(", ")} and ${last}`;
4117
+ }
4118
+ function removalLine(removal, c) {
4119
+ const markers = spellLineList(removal.markerLines, "line");
4120
+ if (removal.origin === void 0) {
4121
+ const named3 = removal.markerLines.length === 1 ? "the marker" : "the markers";
4122
+ return `removed ${named3} ${c.cyan(removal.id ?? "")} at ${markers}, which named no entry`;
4123
+ }
4124
+ const name = removal.id ?? `/citations/${String(removal.index ?? 0)}`;
4125
+ const where = removal.origin.kind === "manifest" ? `${removal.origin.file}${removal.origin.line === void 0 ? "" : `:${String(removal.origin.line)}`}` : "frontmatter";
4126
+ const also = removal.markerLines.length === 0 ? "" : `, and ${removal.markerLines.length === 1 ? "its marker" : "its markers"} at ${markers}`;
4127
+ return `removed ${c.cyan(name)} from ${where}${also}`;
4128
+ }
4129
+ function renderRemovePretty(run3, opts) {
4130
+ const c = palette(opts.color);
4131
+ const lines = [];
4132
+ let files = 0;
4133
+ for (const page of run3.pages) {
4134
+ if (page.removed.length > 0) files += 1;
4135
+ if (opts.showDiff && page.diff !== "") {
4136
+ for (const line of page.diff.split(/\r?\n/)) {
4137
+ if (line !== "") lines.push(c.dim(line));
4138
+ }
4139
+ }
4140
+ for (const removal of page.removed) {
4141
+ lines.push(`${page.file}: ${removalLine(removal, c)}`);
4142
+ }
4143
+ }
4144
+ if (opts.showDiff) {
4145
+ for (const manifest of run3.manifests ?? []) {
4146
+ for (const line of manifest.diff.split(/\r?\n/)) {
4147
+ if (line !== "") lines.push(c.dim(line));
4148
+ }
4149
+ }
4150
+ }
4151
+ const verb = opts.dryRun === true ? "would be removed" : "removed";
4152
+ lines.push(c.green(`${plural2(run3.removed, "citation")} ${verb} from ${plural2(files, "file")}`));
4153
+ return lines.join("\n");
4154
+ }
3852
4155
 
3853
4156
  // src/cite/reporters/json.ts
3854
4157
  function publicCitation(result) {
@@ -3890,6 +4193,9 @@ function publicPage(page) {
3890
4193
  function renderCheckJson(run3) {
3891
4194
  return JSON.stringify({ summary: run3.summary, pages: run3.pages.map(publicPage) }, null, 2);
3892
4195
  }
4196
+ function renderRemoveJson(run3) {
4197
+ return JSON.stringify(run3, null, 2);
4198
+ }
3893
4199
  function renderUpdateJson(run3) {
3894
4200
  const pages = run3.pages.map((page) => ({
3895
4201
  ...page,
@@ -3944,7 +4250,7 @@ var KeyError = class extends ToolError {
3944
4250
  // src/key/commands/rotate.ts
3945
4251
  import { existsSync as existsSync2 } from "node:fs";
3946
4252
  import { readFile as readFile5 } from "node:fs/promises";
3947
- import { isAbsolute as isAbsolute3, join as join2, relative as relative3, resolve as resolve6, sep as sep3 } from "node:path";
4253
+ import { isAbsolute as isAbsolute3, join as join2, relative as relative3, resolve as resolve7, sep as sep3 } from "node:path";
3948
4254
  import { parse as parseYaml } from "yaml";
3949
4255
 
3950
4256
  // src/key/core/config.ts
@@ -3980,9 +4286,9 @@ function citeSection(file) {
3980
4286
  return parseCiteConfig(doc.cite, file.source);
3981
4287
  }
3982
4288
  function rootFor(flag, cite, file, cwd) {
3983
- if (flag !== void 0) return { root: resolve6(cwd, flag), fellBack: false };
4289
+ if (flag !== void 0) return { root: resolve7(cwd, flag), fellBack: false };
3984
4290
  if (file !== null && cite.root !== void 0) {
3985
- return { root: resolve6(file.dir, cite.root), fellBack: false };
4291
+ return { root: resolve7(file.dir, cite.root), fellBack: false };
3986
4292
  }
3987
4293
  const gitRoot = findGitRoot(cwd);
3988
4294
  return gitRoot === null ? { root: cwd, fellBack: true } : { root: gitRoot, fellBack: false };
@@ -4044,7 +4350,7 @@ function reportedPath2(abs, base) {
4044
4350
  function manifestCollections(collections, opts) {
4045
4351
  if (opts.fromCollections) return [...collections];
4046
4352
  const rels = opts.files.map(
4047
- (rel) => reportedPath2(resolve6(opts.base, rel), opts.configDir)
4353
+ (rel) => reportedPath2(resolve7(opts.base, rel), opts.configDir)
4048
4354
  );
4049
4355
  return collections.filter((c) => rels.some((rel) => isMember(c, rel)));
4050
4356
  }
@@ -4073,7 +4379,7 @@ async function planManifests(opts) {
4073
4379
  for (const c of local) {
4074
4380
  for (const m of c.externalMetadata) {
4075
4381
  const join4 = externalMetadataJoin(m);
4076
- const abs = isAbsolute3(m.file) ? m.file : resolve6(opts.configDir, m.file);
4382
+ const abs = isAbsolute3(m.file) ? m.file : resolve7(opts.configDir, m.file);
4077
4383
  const file = reportedPath2(abs, opts.base);
4078
4384
  located.set(`${c.name}\0${file}\0${join4}`, abs);
4079
4385
  if (plans.has(abs)) continue;
@@ -4118,7 +4424,7 @@ async function planManifests(opts) {
4118
4424
  );
4119
4425
  }
4120
4426
  async function runKeyRotate(opts) {
4121
- const cwd = resolve6(opts.cwd ?? process.cwd());
4427
+ const cwd = resolve7(opts.cwd ?? process.cwd());
4122
4428
  const env = opts.env ?? process.env;
4123
4429
  if (opts.to !== void 0 && !isValidEncryptionKey(opts.to)) throw new KeyError(BAD_TO);
4124
4430
  if (opts.inputs.includes(STDIN_TOKEN)) throw new KeyError(NO_STDIN);
@@ -4202,7 +4508,7 @@ async function runKeyRotate(opts) {
4202
4508
  const planned = [];
4203
4509
  let wantsHistory = false;
4204
4510
  for (const rel of files) {
4205
- const path = resolve6(base, rel);
4511
+ const path = resolve7(base, rel);
4206
4512
  const before = await readFile5(path, "utf8");
4207
4513
  const page = { file: rel, content: before, ...format === void 0 ? {} : { format } };
4208
4514
  const sidecarWork = await rotateManifestCitations(sidecars2, manifests, page, {
@@ -4293,7 +4599,7 @@ async function runKeyRotate(opts) {
4293
4599
  else outcome = previous === void 0 ? "written" : "finished";
4294
4600
  }
4295
4601
  const wrote = outcome !== "skipped" && outcome !== "dry-run";
4296
- const baseline = file !== null && cite.baseline !== void 0 ? resolve6(file.dir, cite.baseline) : join2(file?.dir ?? cwd, DEFAULT_CITE_BASELINE_PATH);
4602
+ const baseline = file !== null && cite.baseline !== void 0 ? resolve7(file.dir, cite.baseline) : join2(file?.dir ?? cwd, DEFAULT_CITE_BASELINE_PATH);
4297
4603
  return {
4298
4604
  pages: planned.map((p) => p.page),
4299
4605
  manifests: valueManifests.map((m) => m.manifest),
@@ -4309,7 +4615,7 @@ async function runKeyRotate(opts) {
4309
4615
 
4310
4616
  // src/key/commands/set.ts
4311
4617
  import { existsSync as existsSync3 } from "node:fs";
4312
- import { basename, dirname, join as join3, relative as relative4, resolve as resolve7 } from "node:path";
4618
+ import { basename, dirname, join as join3, relative as relative4, resolve as resolve8 } from "node:path";
4313
4619
  var BAD_VALUE = "The key must be at least 32 hex or base64url characters. Run `manni key set` with no value to generate one.";
4314
4620
  var ENV_SET = `${ENCRYPTION_KEY_ENV} is set, so a key written to config would never be read. Unset it, or keep the key in the secret.`;
4315
4621
  var alreadyConfigured = (source) => `An encryption key is already configured in ${source}. Run \`manni key rotate\` to replace it and re-encrypt every value.`;
@@ -4326,7 +4632,7 @@ function refuseBesideLegacy(path, cwd) {
4326
4632
  }
4327
4633
  }
4328
4634
  async function runKeySet(opts) {
4329
- const cwd = resolve7(opts.cwd ?? process.cwd());
4635
+ const cwd = resolve8(opts.cwd ?? process.cwd());
4330
4636
  const env = opts.env ?? process.env;
4331
4637
  if (opts.value !== void 0 && !isValidEncryptionKey(opts.value)) {
4332
4638
  throw new KeyError(BAD_VALUE);
@@ -4372,16 +4678,19 @@ export {
4372
4678
  runAdd,
4373
4679
  errorSite,
4374
4680
  runCheck,
4681
+ runRemove,
4375
4682
  runUpdate,
4376
4683
  renderCheckPretty,
4377
4684
  renderUpdatePretty,
4685
+ renderRemovePretty,
4378
4686
  findingMessage,
4379
4687
  renderCheckGithub,
4380
4688
  renderCheckJson,
4689
+ renderRemoveJson,
4381
4690
  renderUpdateJson,
4382
4691
  cite_exports,
4383
4692
  KeyError,
4384
4693
  runKeyRotate,
4385
4694
  runKeySet
4386
4695
  };
4387
- //# sourceMappingURL=chunk-LORLZO3J.js.map
4696
+ //# sourceMappingURL=chunk-P4KR3XFD.js.map