okf-kit 0.8.0 → 0.10.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.
package/dist/cli.js CHANGED
@@ -28,6 +28,14 @@ export function runCheck(bundleDir, options = {}) {
28
28
  if (options.requireAnchors) {
29
29
  ctx.requireAnchors = { allow: options.requireAnchorsAllow ?? [] };
30
30
  }
31
+ if (options.proseLineReferences) {
32
+ ctx.proseLineReferences = {
33
+ strict: Boolean(options.proseLineReferencesStrict),
34
+ };
35
+ }
36
+ if (options.futureSkewMinutes !== undefined) {
37
+ ctx.freshnessFutureSkewSeconds = Math.round(options.futureSkewMinutes * 60);
38
+ }
31
39
  const findings = allRules.flatMap((rule) => rule.run(ctx));
32
40
  const summary = summarize(findings);
33
41
  const exitCode = summary.errors > 0 || (Boolean(options.strict) && summary.warnings > 0)
@@ -58,14 +66,35 @@ program
58
66
  "a string anchor lands uniquely on the last line of its range (opt-in, see README)")
59
67
  .option("--require-anchors-allow <patterns...>", "citations-resolve: citedPath glob/exact patterns (e.g. README.md) exempt from " +
60
68
  "--require-anchors' anchor-required check")
69
+ .option("--prose-line-references", "prose-line-references: flag a drifted, unresolvable, or ambiguous prose-embedded line " +
70
+ 'reference outside citations-resolve\'s own backtick grammar, e.g. "lines 129-132" (opt-in, see README)')
71
+ .option("--prose-line-references-strict", "prose-line-references: also flag every prose line reference, not only a drifted one, with " +
72
+ "the remedy to lift it into a backtick citation or a symbol name (ignored unless --prose-line-references is also passed)")
73
+ .option("--future-skew-minutes <n>", "sources-fresh-future: clock-skew allowance in minutes before a doc `timestamp` later than " +
74
+ "the doc's own last commit is flagged as future-dated (default 10)")
61
75
  .exitOverride()
62
76
  .action((bundleDir, opts) => {
63
77
  try {
78
+ let futureSkewMinutes;
79
+ if (opts.futureSkewMinutes !== undefined) {
80
+ const trimmed = opts.futureSkewMinutes.trim();
81
+ // `Number("")` and `Number(" ")` both resolve to 0, which would
82
+ // otherwise silently accept an empty/whitespace-only value as
83
+ // "0 minutes" instead of rejecting it as the usage error it is.
84
+ const n = trimmed === "" ? Number.NaN : Number(trimmed);
85
+ if (!Number.isFinite(n) || n < 0) {
86
+ throw new UsageError(`--future-skew-minutes must be a non-negative number, got \`${opts.futureSkewMinutes}\``);
87
+ }
88
+ futureSkewMinutes = n;
89
+ }
64
90
  const result = runCheck(bundleDir, {
65
91
  repoRoot: opts.repoRoot,
66
92
  strict: opts.strict,
67
93
  requireAnchors: opts.requireAnchors,
68
94
  requireAnchorsAllow: opts.requireAnchorsAllow,
95
+ proseLineReferences: opts.proseLineReferences,
96
+ proseLineReferencesStrict: opts.proseLineReferencesStrict,
97
+ futureSkewMinutes,
69
98
  });
70
99
  const output = opts.json
71
100
  ? renderJson(result.bundleDir, result.findings)
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/git.d.ts CHANGED
@@ -3,8 +3,8 @@ import type { RunGit } from "./types.js";
3
3
  * Default RunGit implementation: shells out to the real `git` binary.
4
4
  * stderr is discarded (git's own "fatal: not a git repository" etc. text is
5
5
  * an expected, silent signal here, not something to surface), and any
6
- * failure (non-zero exit, git missing) resolves to null instead of
7
- * throwing.
6
+ * failure (non-zero exit, git missing, output past MAX_GIT_OUTPUT_BYTES)
7
+ * resolves to null instead of throwing.
8
8
  */
9
9
  export declare const runGit: RunGit;
10
10
  /**
package/dist/git.js CHANGED
@@ -1,10 +1,23 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ /**
3
+ * Output cap (bytes) for a single git invocation. Node's own default for
4
+ * `execFileSync` is 1 MiB, and exceeding it does not throw a distinguishable
5
+ * error here -- it lands in the catch below and resolves to null, i.e. the
6
+ * caller sees "git failed" for a perfectly healthy repository. `sources-fresh`
7
+ * reads whole doc blobs (`git show <sha>:<path>`) to compare frontmatter
8
+ * timestamps, and an OKF doc larger than 1 MiB is unusual but entirely legal,
9
+ * so the cap is raised well past any plausible doc size. It is deliberately
10
+ * still a cap and not `Infinity`: a runaway git invocation should fail loudly
11
+ * (as null, which every caller treats as "not assessable") rather than grow
12
+ * the process heap without bound.
13
+ */
14
+ const MAX_GIT_OUTPUT_BYTES = 16 * 1024 * 1024;
2
15
  /**
3
16
  * Default RunGit implementation: shells out to the real `git` binary.
4
17
  * stderr is discarded (git's own "fatal: not a git repository" etc. text is
5
18
  * an expected, silent signal here, not something to surface), and any
6
- * failure (non-zero exit, git missing) resolves to null instead of
7
- * throwing.
19
+ * failure (non-zero exit, git missing, output past MAX_GIT_OUTPUT_BYTES)
20
+ * resolves to null instead of throwing.
8
21
  */
9
22
  export const runGit = (args, cwd) => {
10
23
  try {
@@ -12,6 +25,7 @@ export const runGit = (args, cwd) => {
12
25
  cwd,
13
26
  encoding: "utf8",
14
27
  stdio: ["ignore", "pipe", "ignore"],
28
+ maxBuffer: MAX_GIT_OUTPUT_BYTES,
15
29
  }).trim();
16
30
  }
17
31
  catch {
package/dist/git.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAAW,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAC1C,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;YAC/B,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;SACpC,CAAC,CAAC,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAgB,EAChB,MAAc,MAAM;IAEpB,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD;;;;;;;;;;;GAWG;AACH,MAAM,oBAAoB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAAW,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAC1C,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;YAC/B,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,SAAS,EAAE,oBAAoB;SAChC,CAAC,CAAC,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAgB,EAChB,MAAc,MAAM;IAEpB,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACrC,CAAC"}
@@ -1,2 +1,107 @@
1
1
  import type { Rule } from "../types.js";
2
+ export declare const CITATION_RE: RegExp;
3
+ /**
4
+ * Per-root basename index, see findByBasename. Exported so
5
+ * `prose-line-references` can build its own cache instance for
6
+ * `resolveCitation` calls, matching this rule's own scoping (fresh per
7
+ * `run(ctx)` invocation, never held at module scope).
8
+ */
9
+ export type BasenameCache = Map<string, Map<string, string[]>>;
10
+ export type Resolution = {
11
+ skip: true;
12
+ } | {
13
+ path: string;
14
+ } | {
15
+ ambiguous: true;
16
+ candidates: string[];
17
+ };
18
+ /**
19
+ * True when citedPath has a literal `..` path segment. Exported so
20
+ * `prose-line-references` rejects the same shape before ever calling
21
+ * `resolveCitation` on a file-mention token, matching this rule's own
22
+ * `path-traversal-rejected` posture.
23
+ */
24
+ export declare function hasParentSegment(citedPath: string): boolean;
25
+ /**
26
+ * Resolves a citation's path to a single real file. Returns `{ skip: true }`
27
+ * for a citedPath out of scope (leading `/`), `{ path }` on a definitive
28
+ * single resolution, `{ ambiguous: true, candidates }` when more than one
29
+ * plausible target exists, or `null` when nothing matches. Callers must
30
+ * reject a citedPath with a `..` segment (see hasParentSegment) before
31
+ * calling this; it is not re-checked here.
32
+ *
33
+ * Exported so `prose-line-references` (see
34
+ * src/rules/prose-line-references.ts) reuses this exact resolution order
35
+ * for binding a bare prose line reference to the file mention nearest it,
36
+ * rather than re-implementing (and risking drifting from) this rule's own
37
+ * path-resolution rules.
38
+ */
39
+ export declare function resolveCitation(cache: BasenameCache, root: string, docAbsPath: string, docContent: string, docSources: string[], citedPath: string, matchIndex: number): Resolution | null;
40
+ /**
41
+ * Char spans of every fenced code block in `content` (```` ``` ```` or
42
+ * `~~~`, optionally with a trailing language tag), each span running from
43
+ * the start of the opening fence line to the end of the closing fence line
44
+ * inclusive. An unterminated fence (no matching close before end of doc) is
45
+ * treated as running to the end of the content -- conservative, since an
46
+ * unterminated fence is itself a doc problem outside this rule's scope, not
47
+ * a reason to scan its contents for short-form citations. Derived from
48
+ * `scanFenceLines` (see there): per-line fenced/opens/closes state is
49
+ * converted to char-offset spans by tracking each line's `[start, end)`
50
+ * offset in `content` alongside it.
51
+ *
52
+ * Exported (with computeIndentedCodeSpans and computeTableRowSpans below)
53
+ * so `prose-line-references` can build its OWN excluded-span set for file
54
+ * mentions that leaves out computeInlineCodeSpans -- a bare backtick-
55
+ * wrapped filename (`` `src/cli.ts` ``) is the normal, encouraged way to
56
+ * write a file mention in prose, unlike a short-form bare number, so it
57
+ * must NOT be excluded from mention detection the way it is excluded from
58
+ * short-form citation matching here.
59
+ */
60
+ export declare function computeFencedSpans(content: string): Array<[number, number]>;
61
+ /**
62
+ * Char spans of every CommonMark-style indented code block in `content`: a
63
+ * maximal run of consecutive non-blank lines, each indented by at least
64
+ * four spaces or a leading tab, whose first line is preceded by a blank
65
+ * line or the start of the document (an indented code block cannot
66
+ * interrupt a paragraph). A blank line inside the run does not itself end
67
+ * it, matching CommonMark. Simplified relative to the full CommonMark
68
+ * spec (no list-item-context awareness); adequate for this mechanical,
69
+ * warn-only rule.
70
+ */
71
+ export declare function computeIndentedCodeSpans(content: string): Array<[number, number]>;
72
+ /**
73
+ * Char spans of every Markdown table row in `content`: a line whose
74
+ * trimmed form starts and ends with `|`. Decision (documented in the
75
+ * README): a short-form citation inside a table cell is never recognised,
76
+ * the same way one inside a code span is not -- excluded here rather than
77
+ * left to the plausibility gate, since a table cell's content is prose-like
78
+ * and can otherwise carry a range shape the gate would not reject (e.g.
79
+ * `| col (5-9) |`).
80
+ */
81
+ export declare function computeTableRowSpans(content: string): Array<[number, number]>;
82
+ /**
83
+ * All char spans short-form matching must never fire inside: fenced code,
84
+ * indented code, inline code spans, and Markdown table rows. Computed once
85
+ * per doc and combined with fullSpans (see scanDoc) via the existing
86
+ * isWithinAnySpan helper -- the same mechanism a full citation's own span
87
+ * already uses, not a second one.
88
+ *
89
+ * Exported so `prose-line-references` excludes the identical set of spans
90
+ * from its own extraction (a code-fenced or inline-code "line N" is a code
91
+ * example, not a live prose reference), rather than maintaining a second,
92
+ * possibly-drifting copy of "what counts as excluded prose".
93
+ */
94
+ export declare function computeExcludedSpans(content: string): Array<[number, number]>;
95
+ /**
96
+ * Paragraph-start offsets in `content`, ascending, always including 0. A
97
+ * paragraph boundary is a blank (empty or whitespace-only) line.
98
+ *
99
+ * Exported (with paragraphStartFor below) so `prose-line-references` binds
100
+ * against the same notion of "paragraph" this rule already uses for
101
+ * short-form citation binding, instead of a second, possibly-inconsistent
102
+ * definition.
103
+ */
104
+ export declare function computeParagraphStarts(content: string): number[];
105
+ /** The start offset of the paragraph containing `index` (see computeParagraphStarts). */
106
+ export declare function paragraphStartFor(starts: number[], index: number): number;
2
107
  export declare const citationsResolveRule: Rule;
@@ -101,8 +101,8 @@ const RULE_ID = "citations-resolve";
101
101
  * citation, prose habitually repeats just the line (or range) for a later
102
102
  * reference in the same sentence rather than retyping the path, in three
103
103
  * forms:
104
- * - `` `:N` `` or `` `:N-M` `` -- a bare colon-prefixed line/range
105
- * - `` -`M` `` / `` –`M` `` -- a hyphen- or en-dash-led bare line, the
104
+ * - `` `:N` ``/`` `:N-M` `` -- a bare colon-prefixed line/range
105
+ * - `` -`M` ``/`` –`M` `` -- a hyphen- or en-dash-led bare line, the
106
106
  * tail half of a `` `path:N`-`M` `` split range
107
107
  * - `` (`N`) `` -- a parenthesized bare line
108
108
  * Each of these resolves against `governing`: the nearest *preceding*
@@ -198,6 +198,11 @@ const RULE_ID = "citations-resolve";
198
198
  * nowhere natural to hang an anchor on one without inventing a second,
199
199
  * detached syntax; the migration this rule was built for (CHANGELOG.md
200
200
  * citations) is written as full citations throughout the corpus it targets.
201
+ * Under `--require-anchors`, this remains true (no second anchor syntax is
202
+ * added), but a continuation or short-form that chains off an in-repo full
203
+ * citation is instead flagged, `anchor-required-continuation`, so it does
204
+ * not drift silently -- see the "Anchor strictness (opt-in)" doc block
205
+ * below for that check.
201
206
  *
202
207
  * Rejected alternatives (see the PR/CHANGELOG for the fuller writeup):
203
208
  * - Embedding the literal heading markup itself, e.g.
@@ -291,6 +296,30 @@ const RULE_ID = "citations-resolve";
291
296
  * `checkShortFormTarget`'s own doc comment already gives for the Markdown
292
297
  * half of the block-boundary check: a full citation into a Markdown target
293
298
  * legitimately cites a couple of arbitrary lines all the time.
299
+ *
300
+ * A fifth check, `anchor-required-continuation` (warning), closes a gap the
301
+ * four checks above leave open: every one of them is gated on reaching a
302
+ * "full" atom, so a continuation (` :N`/`:N-M` `, `` -`M` ``/`` –`M` ``,
303
+ * `` (`N`) ``) or a paragraph-bound short-form `:N-M` chained off an
304
+ * ALREADY-ANCHORED full citation was previously invisible to
305
+ * `--require-anchors` entirely -- it carries no path of its own
306
+ * (see the "Continuation citations" / "Short-form citations" doc blocks),
307
+ * so it was never itself an in-repo full citation `anchor-required` could
308
+ * reach, and the anchor on the governing citation it chains off does not
309
+ * (and structurally cannot) cover it: a line-shift that only affects the
310
+ * continuation's own start/end line is exactly the drift this rule's base
311
+ * checks (blank-start-line and friends) already catch when it lands on
312
+ * unusable content, but a shift that lands the continuation on still
313
+ * non-blank, in-bounds, unrelated content is invisible to any of them --
314
+ * see `pushAnchorRequiredContinuation` for where this is pushed, from all
315
+ * three places a continuation or short-form atom is processed in `scanDoc`.
316
+ * Unlike `anchor-required`, there is no "already carries one" escape here:
317
+ * a continuation is structurally anchor-less regardless of whether its
318
+ * governing full citation has an anchor, so this fires unconditionally
319
+ * once the same exemptions (reserved citing doc, `requireAnchors.allow`
320
+ * matched against the GOVERNING citation's citedPath) clear. The remedy is
321
+ * always the same: lift the continuation into its own full,
322
+ * `path:N-M#anchor` citation.
294
323
  */
295
324
  /**
296
325
  * True when `citedPath` (the citation's raw, as-written path text) matches
@@ -523,7 +552,10 @@ function checkAnchor(anchor, startLine, endLine, lines, requireAnchors) {
523
552
  }
524
553
  return null;
525
554
  }
526
- const CITATION_RE = /([\w./-]+\.(?:ts|js|mjs|md|yml|yaml|json)):(\d+)(?:-(\d+))?(?:#(\[?\w(?:[\w.-]*\w)?\]?|"[^"\n`]*"))?/g;
555
+ // Exported so `prose-line-references` (see src/rules/prose-line-references.ts)
556
+ // can exclude a real citations-resolve citation span from its own
557
+ // extraction, rather than re-deriving this grammar itself.
558
+ export const CITATION_RE = /([\w./-]+\.(?:ts|js|mjs|md|yml|yaml|json)):(\d+)(?:-(\d+))?(?:#(\[?\w(?:[\w.-]*\w)?\]?|"[^"\n`]*"))?/g;
527
559
  /**
528
560
  * Heading-section citations. A CHANGELOG.md that grows by insertion at the
529
561
  * top forces every later `path:N-M#anchor` citation to be re-pointed on
@@ -666,8 +698,13 @@ function isFile(p) {
666
698
  return false;
667
699
  }
668
700
  }
669
- /** True when citedPath has a literal `..` path segment. */
670
- function hasParentSegment(citedPath) {
701
+ /**
702
+ * True when citedPath has a literal `..` path segment. Exported so
703
+ * `prose-line-references` rejects the same shape before ever calling
704
+ * `resolveCitation` on a file-mention token, matching this rule's own
705
+ * `path-traversal-rejected` posture.
706
+ */
707
+ export function hasParentSegment(citedPath) {
671
708
  return citedPath.split("/").includes("..");
672
709
  }
673
710
  /**
@@ -770,8 +807,14 @@ function resolveViaAncestorClimb(root, docAbsPath, citedPath) {
770
807
  * plausible target exists, or `null` when nothing matches. Callers must
771
808
  * reject a citedPath with a `..` segment (see hasParentSegment) before
772
809
  * calling this; it is not re-checked here.
810
+ *
811
+ * Exported so `prose-line-references` (see
812
+ * src/rules/prose-line-references.ts) reuses this exact resolution order
813
+ * for binding a bare prose line reference to the file mention nearest it,
814
+ * rather than re-implementing (and risking drifting from) this rule's own
815
+ * path-resolution rules.
773
816
  */
774
- function resolveCitation(cache, root, docAbsPath, docContent, docSources, citedPath, matchIndex) {
817
+ export function resolveCitation(cache, root, docAbsPath, docContent, docSources, citedPath, matchIndex) {
775
818
  if (citedPath.startsWith("/")) {
776
819
  return { skip: true };
777
820
  }
@@ -1368,6 +1411,7 @@ function collectContinuationAtoms(content) {
1368
1411
  kind: "cont-ext",
1369
1412
  index: m.index,
1370
1413
  value: m[2] ? Number(m[2]) : Number(m[1]),
1414
+ raw: m[0],
1371
1415
  });
1372
1416
  }
1373
1417
  else {
@@ -1376,12 +1420,18 @@ function collectContinuationAtoms(content) {
1376
1420
  index: m.index,
1377
1421
  startLine: Number(m[1]),
1378
1422
  endLine: m[2] ? Number(m[2]) : null,
1423
+ raw: m[0],
1379
1424
  });
1380
1425
  }
1381
1426
  }
1382
1427
  const dashRe = new RegExp(CONT_DASH_RE.source, "g");
1383
1428
  while ((m = dashRe.exec(content)) !== null) {
1384
- atoms.push({ kind: "cont-ext", index: m.index, value: Number(m[1]) });
1429
+ atoms.push({
1430
+ kind: "cont-ext",
1431
+ index: m.index,
1432
+ value: Number(m[1]),
1433
+ raw: m[0],
1434
+ });
1385
1435
  }
1386
1436
  const parenRe = new RegExp(CONT_PAREN_RE.source, "g");
1387
1437
  while ((m = parenRe.exec(content)) !== null) {
@@ -1390,6 +1440,7 @@ function collectContinuationAtoms(content) {
1390
1440
  index: m.index,
1391
1441
  startLine: Number(m[1]),
1392
1442
  endLine: null,
1443
+ raw: m[0],
1393
1444
  });
1394
1445
  }
1395
1446
  return atoms;
@@ -1443,8 +1494,16 @@ function collectShortFormMatches(content, excludedSpans) {
1443
1494
  * `scanFenceLines` (see there): per-line fenced/opens/closes state is
1444
1495
  * converted to char-offset spans by tracking each line's `[start, end)`
1445
1496
  * offset in `content` alongside it.
1497
+ *
1498
+ * Exported (with computeIndentedCodeSpans and computeTableRowSpans below)
1499
+ * so `prose-line-references` can build its OWN excluded-span set for file
1500
+ * mentions that leaves out computeInlineCodeSpans -- a bare backtick-
1501
+ * wrapped filename (`` `src/cli.ts` ``) is the normal, encouraged way to
1502
+ * write a file mention in prose, unlike a short-form bare number, so it
1503
+ * must NOT be excluded from mention detection the way it is excluded from
1504
+ * short-form citation matching here.
1446
1505
  */
1447
- function computeFencedSpans(content) {
1506
+ export function computeFencedSpans(content) {
1448
1507
  const spans = [];
1449
1508
  const lines = content.split("\n");
1450
1509
  const states = scanFenceLines(lines);
@@ -1475,7 +1534,7 @@ function computeFencedSpans(content) {
1475
1534
  * spec (no list-item-context awareness); adequate for this mechanical,
1476
1535
  * warn-only rule.
1477
1536
  */
1478
- function computeIndentedCodeSpans(content) {
1537
+ export function computeIndentedCodeSpans(content) {
1479
1538
  const spans = [];
1480
1539
  const lines = content.split("\n");
1481
1540
  let offset = 0;
@@ -1546,7 +1605,7 @@ function computeInlineCodeSpans(content) {
1546
1605
  * and can otherwise carry a range shape the gate would not reject (e.g.
1547
1606
  * `| col (5-9) |`).
1548
1607
  */
1549
- function computeTableRowSpans(content) {
1608
+ export function computeTableRowSpans(content) {
1550
1609
  const spans = [];
1551
1610
  const lines = content.split("\n");
1552
1611
  let offset = 0;
@@ -1567,8 +1626,13 @@ function computeTableRowSpans(content) {
1567
1626
  * per doc and combined with fullSpans (see scanDoc) via the existing
1568
1627
  * isWithinAnySpan helper -- the same mechanism a full citation's own span
1569
1628
  * already uses, not a second one.
1629
+ *
1630
+ * Exported so `prose-line-references` excludes the identical set of spans
1631
+ * from its own extraction (a code-fenced or inline-code "line N" is a code
1632
+ * example, not a live prose reference), rather than maintaining a second,
1633
+ * possibly-drifting copy of "what counts as excluded prose".
1570
1634
  */
1571
- function computeExcludedSpans(content) {
1635
+ export function computeExcludedSpans(content) {
1572
1636
  return [
1573
1637
  ...computeFencedSpans(content),
1574
1638
  ...computeIndentedCodeSpans(content),
@@ -1579,8 +1643,13 @@ function computeExcludedSpans(content) {
1579
1643
  /**
1580
1644
  * Paragraph-start offsets in `content`, ascending, always including 0. A
1581
1645
  * paragraph boundary is a blank (empty or whitespace-only) line.
1646
+ *
1647
+ * Exported (with paragraphStartFor below) so `prose-line-references` binds
1648
+ * against the same notion of "paragraph" this rule already uses for
1649
+ * short-form citation binding, instead of a second, possibly-inconsistent
1650
+ * definition.
1582
1651
  */
1583
- function computeParagraphStarts(content) {
1652
+ export function computeParagraphStarts(content) {
1584
1653
  const starts = [0];
1585
1654
  const re = /\n[ \t]*\n+/g;
1586
1655
  let m;
@@ -1590,7 +1659,7 @@ function computeParagraphStarts(content) {
1590
1659
  return starts;
1591
1660
  }
1592
1661
  /** The start offset of the paragraph containing `index` (see computeParagraphStarts). */
1593
- function paragraphStartFor(starts, index) {
1662
+ export function paragraphStartFor(starts, index) {
1594
1663
  let result = starts[0];
1595
1664
  for (const s of starts) {
1596
1665
  if (s > index)
@@ -1641,6 +1710,40 @@ function pushUnreadable(findings, file, citation, resolvedTo, code) {
1641
1710
  detail: `resolvedTo: ${resolvedTo}, errorCode: ${code}`,
1642
1711
  });
1643
1712
  }
1713
+ /**
1714
+ * `anchor-required-continuation` (opt-in, `--require-anchors`, warning):
1715
+ * see the "Anchor strictness (opt-in)" doc block above. A continuation
1716
+ * atom (cont-fresh or cont-ext, any of the three backtick forms, or a
1717
+ * paragraph-bound short-form `:N-M`) is structurally anchor-less by
1718
+ * construction -- see the "Continuation citations" doc block -- so
1719
+ * unlike `anchor-required` there is no "already carries one" escape: this
1720
+ * fires once its governing citation resolved in-repo, REGARDLESS of
1721
+ * whether that governing full citation itself carries an anchor (an
1722
+ * anchor on the full citation does not extend to a later continuation of
1723
+ * it). Exemptions mirror `anchor-required`'s exactly: the caller already
1724
+ * folds the reserved-citing-doc carve-out into `requireAnchorsForDoc`
1725
+ * (undefined for a reserved doc), and `requireAnchors.allow` is matched
1726
+ * here against `governing.citedPath` -- the path the continuation is
1727
+ * chained to, since a continuation carries no path of its own to match
1728
+ * against.
1729
+ */
1730
+ function pushAnchorRequiredContinuation(findings, file, citation, raw, governing, requireAnchorsForDoc, root) {
1731
+ if (!requireAnchorsForDoc ||
1732
+ matchesAllowPattern(governing.citedPath, requireAnchorsForDoc.allow)) {
1733
+ return;
1734
+ }
1735
+ const governingRange = `${governing.citedPath}:${governing.startLine}${governing.endLine ? "-" + governing.endLine : ""}`;
1736
+ // "chained to" rather than "extends": for a cont-ext that itself chains
1737
+ // off an earlier cont-fresh (not the original full citation), the range
1738
+ // it literally extends is the cont-fresh's own range, not `governing`'s
1739
+ // -- `governing` (see the loop above) always carries the ORIGINAL full
1740
+ // citation's own startLine/endLine, unchanged across intervening
1741
+ // cont-fresh atoms. Naming it "the governing citation" is honest for
1742
+ // every caller (cont-ext, cont-fresh, and the short-form call site
1743
+ // below) without having to thread the true immediate antecedent range
1744
+ // through here.
1745
+ pushDrift(findings, file, citation, "anchor-required-continuation", `continuation ${raw} is chained to the governing citation \`${governingRange}\`; a continuation cannot carry its own #anchor, lift it into a full \`path:N-M#anchor\` citation (--require-anchors is on)`, path.relative(root, governing.resolvedPath));
1746
+ }
1644
1747
  /**
1645
1748
  * True when a `CITATION_RE` match at `matchIndex` is the phantom tail of a
1646
1749
  * filename hard-wrapped across a line break (see the "Hard-wrapped prose"
@@ -1771,9 +1874,13 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
1771
1874
  const atoms = [...fullAtoms, ...collectContinuationAtoms(content)].sort((a, b) => a.index - b.index);
1772
1875
  // `governing`: nearest preceding citation (full or continuation) that
1773
1876
  // resolved to a real file; see the "Continuation citations" doc block
1774
- // above for the reset rules. `lastStartLine`: the start line a "cont-ext"
1775
- // atom extends into a range; tracks the most recent full or cont-fresh
1776
- // atom's own startLine, scoped together with `governing`.
1877
+ // above for the reset rules. Carries the ORIGINAL full citation's own
1878
+ // startLine/endLine (not the extended range a later cont-ext might grow
1879
+ // into) so `anchor-required-continuation` (see `pushAnchorRequiredContinuation`
1880
+ // above) can name "the governing citation" the same way it was actually
1881
+ // written. `lastStartLine`: the start line a "cont-ext" atom extends into
1882
+ // a range; tracks the most recent full or cont-fresh atom's own
1883
+ // startLine, scoped together with `governing`.
1777
1884
  let governing = null;
1778
1885
  let lastStartLine = null;
1779
1886
  for (const atom of atoms) {
@@ -1781,6 +1888,7 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
1781
1888
  if (!governing || lastStartLine === null)
1782
1889
  continue; // nothing to extend
1783
1890
  const citation = `${governing.citedPath}:${lastStartLine}-${atom.value} (continuation)`;
1891
+ pushAnchorRequiredContinuation(findings, doc.relPath, citation, atom.raw, governing, requireAnchorsForDoc, root);
1784
1892
  const problem = checkRangeBoundOnly(lastStartLine, atom.value, governing.resolvedPath);
1785
1893
  if (problem?.rule === "unreadable-target") {
1786
1894
  pushUnreadable(findings, doc.relPath, citation, path.relative(root, governing.resolvedPath), problem.code ?? "UNKNOWN");
@@ -1795,6 +1903,7 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
1795
1903
  continue; // nothing to validate a bare continuation against
1796
1904
  const { startLine, endLine } = atom;
1797
1905
  const citation = `${governing.citedPath}:${startLine}${endLine ? "-" + endLine : ""} (continuation)`;
1906
+ pushAnchorRequiredContinuation(findings, doc.relPath, citation, atom.raw, governing, requireAnchorsForDoc, root);
1798
1907
  const problem = checkTarget(governing.citedPath, startLine, endLine, governing.resolvedPath);
1799
1908
  if (problem?.rule === "unreadable-target") {
1800
1909
  pushUnreadable(findings, doc.relPath, citation, path.relative(root, governing.resolvedPath), problem.code ?? "UNKNOWN");
@@ -1869,7 +1978,12 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
1869
1978
  pushDrift(findings, doc.relPath, citation, problem.rule, problem.message, path.relative(root, resolution.path));
1870
1979
  }
1871
1980
  }
1872
- governing = { citedPath, resolvedPath: resolution.path };
1981
+ governing = {
1982
+ citedPath,
1983
+ resolvedPath: resolution.path,
1984
+ startLine,
1985
+ endLine,
1986
+ };
1873
1987
  lastStartLine = startLine;
1874
1988
  }
1875
1989
  // Heading-section citations -- see the "Heading-section citations" doc
@@ -1945,6 +2059,8 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
1945
2059
  namedFullAtoms.push({
1946
2060
  index: a.index,
1947
2061
  citedPath: a.citedPath,
2062
+ startLine: a.startLine,
2063
+ endLine: a.endLine,
1948
2064
  });
1949
2065
  }
1950
2066
  }
@@ -1974,6 +2090,27 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
1974
2090
  pushAmbiguous(findings, doc.relPath, citation, resolution.candidates);
1975
2091
  continue;
1976
2092
  }
2093
+ // anchor-required-continuation (opt-in, warning) also applies to a
2094
+ // bound short-form citation: it is just as anchor-less as a
2095
+ // backtick continuation (see the "Short-form citations" doc block
2096
+ // above), for the identical reason -- a bound short form cannot
2097
+ // carry an anchor either, so it is flagged as well, rather than
2098
+ // left as a silent gap in `--require-anchors`.
2099
+ //
2100
+ // `raw` here is synthesized from the parsed startLine/endLine
2101
+ // (`:${rangeLabel}`) rather than carried from the match itself --
2102
+ // `ShortFormMatch` (see there) does not retain the matched text.
2103
+ // This is exact for `SHORT_FORM_COLON_RE` (`/:(\d+)-(\d+)/`) as
2104
+ // written today, since re-stringifying the two captured numbers
2105
+ // reproduces the source bytes; it would stop being exact if that
2106
+ // regex ever tolerated something re-stringifying can't round-trip
2107
+ // (e.g. a leading zero).
2108
+ pushAnchorRequiredContinuation(findings, doc.relPath, citation, `:${rangeLabel}`, {
2109
+ citedPath: targetPath,
2110
+ resolvedPath: resolution.path,
2111
+ startLine: target.startLine,
2112
+ endLine: target.endLine,
2113
+ }, requireAnchorsForDoc, root);
1977
2114
  const problem = checkShortFormTarget(targetPath, sf.startLine, sf.endLine, resolution.path);
1978
2115
  if (problem?.rule === "unreadable-target") {
1979
2116
  pushUnreadable(findings, doc.relPath, citation, path.relative(root, resolution.path), problem.code ?? "UNKNOWN");