okf-kit 0.7.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -7,6 +7,219 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.0] - 2026-09-01
11
+
12
+ ### Added
13
+
14
+ - A new opt-in rule, `prose-line-references` (`--prose-line-references`,
15
+ strict sub-flag `--prose-line-references-strict`), closes a gap
16
+ `citations-resolve` leaves open: a prose-embedded line reference written
17
+ outside its backtick grammar ("lines 496-498",
18
+ "generate-codex-config.ts lines 129-132") is structurally invisible to
19
+ `citations-resolve`'s `CITATION_RE`, which requires a literal `:` between
20
+ the path and the digits. A doc can be re-verified, re-stamped, and pass
21
+ `check` with 0 findings while its prose line numbers are drifted, because
22
+ nothing ever looked at them -- exactly what happened in harness task
23
+ ad66c43f (2026-08-30/31): review round 1 of that OKF sweep found 9 wrong
24
+ prose references behind a fresh `citations-resolve`-clean stamp, the fix
25
+ round's own sweep found more (including one citing the wrong file
26
+ entirely), and the verification round a third residue; both reviewers
27
+ named the missing mechanical guard as the structural cause. Extracts
28
+ `line N`/`lines N-M`/`lines N to M` (deliberately not `L N`, a
29
+ comma-separated list of several numbers, or a second unlabelled range
30
+ chained by "vs"; see the README for the full, deliberately conservative
31
+ grammar), binds each to the nearest named file (same sentence first,
32
+ then same paragraph, reusing `citations-resolve`'s own path-resolution
33
+ rules verbatim rather than a second, drift-prone copy), and reports
34
+ `out-of-bounds`, `blank-start-line`, `unresolvable`, or `ambiguous`.
35
+ `--prose-line-references-strict` additionally flags EVERY extracted
36
+ reference (resolved or not) with the remedy: lift it into a backtick
37
+ anchored citation, or de-precise it to a symbol name. Off by default;
38
+ see "Prose line references (opt-in, `--prose-line-references`)" in the
39
+ README for the full grammar, binding rule, and finding table.
40
+
41
+ - `prose-line-references` review-round fixes, found by a reviewer pass on
42
+ the rule above before it shipped: a start line of `0` no longer indexes
43
+ `lines[-1]` and is now its own `out-of-bounds` finding rather than a
44
+ misclassified `blank-start-line`; `LINE_REF_RE`'s leading `\b` no longer
45
+ matches right after a hyphen, so "in-line 999", "multi-line 999", and
46
+ "command-line 999" are no longer mis-extracted as citations to line 999;
47
+ a reference inside an HTML comment (`` <!-- see line 5 above --> ``) or
48
+ on a line that is itself a Markdown ATX heading (`## Line 3 semantics`)
49
+ is now excluded from extraction, the same way a fenced code block's
50
+ example already was; and a finding now quotes the doc's own matched text
51
+ verbatim (an en-dash range stays an en-dash range) instead of a
52
+ re-rendered, always-hyphenated approximation, with the normalised
53
+ hyphen-ranged form folded into the message body only when it actually
54
+ differs from what the doc wrote. Also closes a coverage gap the
55
+ reviewer found: the binding rule's paragraph-level fallback
56
+ (`nearestPrecedingMentionInParagraph`) and its same-sentence
57
+ "following mention" branch had no test that could tell them apart from
58
+ a hypothetical "always bind to the first mention in the paragraph"
59
+ mutant; see "Verification" below for the fixture and the mutation
60
+ check.
61
+
62
+ - `citations-resolve`: a fifth `--require-anchors` check,
63
+ `anchor-required-continuation` (warning), closes a gap the four checks
64
+ added in 0.8.0 left open: they all fire only for a "full" citation, so a
65
+ continuation (`` `:N` ``/`` `:N-M` ``, `` -`M` ``/`` –`M` ``, `` (`N`) ``)
66
+ or a bound paragraph-bound short-form `:N-M` chained off an
67
+ ALREADY-anchored full citation was invisible to `--require-anchors`
68
+ entirely: it carries no path of its own to hang an anchor on (see
69
+ "Anchored citations" in the README), so it was never itself an in-repo
70
+ full citation `anchor-required` could reach, and the anchor on its
71
+ governing citation does not (and structurally cannot) extend to a later
72
+ continuation of it. A line-shift that lands the continuation on still
73
+ non-blank, in-bounds, unrelated content -- exactly the drift class this
74
+ rule exists to catch -- was previously silent for a continuation the
75
+ same way it was for an unanchored full citation before 0.8.0. Fires once
76
+ the continuation's governing citation resolves in-repo, mirroring
77
+ `anchor-required`'s own exemptions (a reserved citing doc; a
78
+ `requireAnchors.allow` pattern, matched against the GOVERNING citation's
79
+ raw citedPath, since a continuation has no path of its own to match).
80
+ The paragraph-bound short form is included under the identical
81
+ reasoning. See "Anchor strictness (opt-in, `--require-anchors`)" in the
82
+ README for the full rationale.
83
+
84
+ ### Verification
85
+
86
+ - `prose-line-references`: default mode (no `--prose-line-references`) is
87
+ byte-identical before and after this change: verified by building the
88
+ CLI at both the pre-change commit and this change, then running `check
89
+ --json` in default mode against the same three real bundles as below
90
+ (each repo's full real tree as `--repo-root`, not a narrow `docs/okf`-
91
+ only extraction, since this rule's own file-mention resolution needs the
92
+ rest of the repo present) and diffing the JSON output byte for byte --
93
+ 0 bytes of diff on all three.
94
+ - `prose-line-references` migration backlog, run with
95
+ `--prose-line-references` after this change against the same three
96
+ bundles, full real repo tree as `--repo-root`: agent-dx's own
97
+ orchestrator-workflow bundle 3 findings (2 `out-of-bounds`, 1
98
+ `blank-start-line`), harness 5 (2 `ambiguous`, 1 `blank-start-line`, 2
99
+ `unresolvable`), agent-grounding 0. Spot-checked, not exhaustively
100
+ audited: the harness `ambiguous` pair is a real collision (`intercept.ts`
101
+ resolves to both `src/cli/policy/intercept.ts` and
102
+ `src/runtime/intercept.ts`); the agent-dx `out-of-bounds`/
103
+ `blank-start-line` trio is a real false positive of the binding rule's
104
+ own stated limitation (a "test lines N-M" phrase bound to a file named
105
+ elsewhere in the same sentence for an unrelated reason, not the file the
106
+ line numbers actually belong to -- once before the reference, once
107
+ after) -- see the README's closing paragraph on "Prose line references"
108
+ for that known category. No
109
+ consumer CI currently selects on `[prose-line-references]` findings by
110
+ rule id, so no consumer pin bump is required before this rule starts
111
+ reporting; enabling `--prose-line-references` anywhere is itself the
112
+ opt-in.
113
+ - `prose-line-references` review-round fixes above: default mode is still
114
+ byte-identical, re-verified the same way as above (build both commits,
115
+ `check --json` on the same three real bundles, diff byte for byte -- 0
116
+ bytes of diff on all three). The migration backlog counts are unchanged
117
+ by these fixes: agent-dx 3, harness 5, agent-grounding 0, identical
118
+ reasons to the counts above -- none of the three real bundles contained
119
+ a `line 0`, a hyphen-joined "-line N" word, an ATX heading naming a
120
+ line, or an HTML comment naming one. Four mutation probes verified
121
+ by hand against the new tests: replacing nearest-in-sentence binding
122
+ with first-in-paragraph binding, deleting the en-dash/em-dash/"to"
123
+ alternation from `LINE_REF_RE`, deleting the inverted-range branch in
124
+ `checkTarget`, and disabling the reserved-doc skip each fail a
125
+ dedicated new test, and pass again once reverted.
126
+ - Regression test added for a latent `FILE_MENTION_RE` extension-matching
127
+ bug found while measuring the migration backlog above: the extension
128
+ alternation lists `js` before `json`, and `js` is a strict prefix of
129
+ `json`; without forcing the regex to reject a truncated match, a real
130
+ `config.json` mention resolved (or failed to resolve) as `config.js`
131
+ instead. Fixed with a trailing `(?!\w)` on the match; `citations-resolve`'s
132
+ own `CITATION_RE` does not have this problem, since its mandatory
133
+ trailing `:` already forces the same backtracking.
134
+ - Default mode (no `--require-anchors`) is byte-identical before and
135
+ after this change: verified by building the CLI at both the pre-change
136
+ commit and this change, then running `check --json` in default mode
137
+ against three real bundles (this repo's own
138
+ `packages/orchestrator-workflow/docs/okf`, harness's `docs/okf`, and
139
+ agent-grounding's `docs/okf`, each extracted read-only at a pinned
140
+ commit) and diffing the sorted finding lists -- 0 lines of diff on all
141
+ three, both against a narrow `docs/okf`-only extraction and against the
142
+ full real repo tree as `--repo-root`.
143
+ - Migration backlog, run with `--require-anchors` after this change
144
+ against the same three bundles with their full real repo tree as
145
+ `--repo-root` (a narrow `docs/okf`-only extraction under-counts this,
146
+ since most citations fail to resolve without the rest of the repo
147
+ present): agent-dx's own orchestrator-workflow bundle 70
148
+ `anchor-required-continuation` findings, harness 24, agent-grounding
149
+ 24. Consumer CI pins (agent-dx's and agent-grounding's own
150
+ `okf-anchor-guard` jobs, which select findings by the trailing
151
+ `[rule-id]` bracket) must be bumped to this version before
152
+ `anchor-required-continuation` starts gating; that bump is out of scope
153
+ here.
154
+
155
+ ## [0.8.0] - 2026-08-27
156
+
157
+ ### Added
158
+
159
+ - `citations-resolve`: a new backtick-delimited `` `path:#heading` `` citation
160
+ form (`.md` targets only) resolves to a whole Markdown section instead of
161
+ a line range, so it is immune to a line-number shift caused by an edit
162
+ anywhere above the cited section -- unlike even a heading-anchored
163
+ `path:N-M#anchor` citation, whose line range still drifts on every such
164
+ edit even though the anchor catches it landing in the wrong section. The
165
+ heading text must match exactly one level 1 or 2 heading in the target
166
+ (`heading-section-not-found` / `heading-section-ambiguous`), and the
167
+ resolved section must have at least one non-blank line before the next
168
+ heading of the same or shallower level (`heading-section-empty`). An
169
+ optional content anchor, `` `path:#heading#"text"` ``, must occur on
170
+ exactly one line inside the resolved section
171
+ (`heading-section-content-anchor-not-found` /
172
+ `-content-anchor-ambiguous`); a malformed attempt is reported rather than
173
+ silently dropped (`heading-section-malformed`). See the doc comment above
174
+ `HEADING_SECTION_CITATION_RE` in `src/rules/citations-resolve.ts` for the
175
+ full rationale, including why the grammar requires the `:#` colon-hash
176
+ rather than a bare `#`. Existing bundles that already use the older
177
+ `` `path#heading` `` shape in ordinary prose (a Markdown link's target
178
+ written in backticks) are unaffected: they no longer parse as a citation
179
+ at all, producing the same findings as before this form existed.
180
+ - `citations-resolve`: a new `--require-anchors` opt-in (and
181
+ `--require-anchors-allow <patterns...>` for exempting specific citedPath
182
+ globs/exact matches) adds four stricter, `warning`-severity checks, off
183
+ by default so an existing bundle's plain `check` findings are unaffected:
184
+ `anchor-required` (an in-repo full citation carries no `#anchor` at all,
185
+ except a reserved citing doc or an allowlisted target),
186
+ `anchor-not-on-last-line` (a string anchor was found in its cited range,
187
+ but not on the range's own last content line -- an anchor on the first
188
+ line survives a small insertion above the range, since the shifted
189
+ window still contains its original content just at a different offset;
190
+ the last content line falls out of the window on any insertion at all),
191
+ `anchor-not-unique-in-range` (a string anchor occurs on more than one
192
+ line of its cited range; a count of zero stays
193
+ `anchor-not-found-in-range` as before, unconditionally), and
194
+ `test-range-straddles-block` (a FULL citation's own range into a
195
+ `.test.`/`.spec.` target (`.ts`, `.js`, `.mjs`) contains a
196
+ `describe`/`it`/`test` block-head line, at the same or a shallower
197
+ indent than the range's own first line, on any line other than that
198
+ first line -- its own new rule, not a reuse of the existing
199
+ `test-range-start-not-head`/`test-range-end-not-closing` pair, which
200
+ stay short-form-only). See "Anchor strictness (opt-in,
201
+ `--require-anchors`)" in the README for the full rationale; verified
202
+ byte-identical findings on existing bundles before and after this
203
+ change, off by default and additive only.
204
+ - `citations-resolve`: four details of the `--require-anchors` checks
205
+ added above, worth knowing when adopting them:
206
+ `test-range-straddles-block` no longer flags a block-head line nested
207
+ strictly deeper than the range's own start line, so citing a whole
208
+ `describe` in full (including every `it(` nested inside it) is no
209
+ longer misreported as straddling into another block; only a sibling or
210
+ outer block-head line still counts. `anchor-not-on-last-line` now
211
+ anchors against a range's last CONTENT line rather than its literal
212
+ last line, so a range that (correctly) ends on bare closing boilerplate
213
+ (`});`, `]);`, `}),`, and similar) can anchor on the real content line
214
+ before it instead of being forced onto the boilerplate itself. Both of
215
+ the opt-in's per-atom checks (`test-range-straddles-block` and the
216
+ string-anchor checks) are now also exempt for a reserved citing doc
217
+ (`index.md`/`log.md`), matching `anchor-required`'s existing exemption
218
+ rather than only that one check having it; and a citation that both
219
+ straddles a block boundary and carries a missing/misplaced anchor now
220
+ gets both findings instead of the anchor problem silently vanishing
221
+ behind the straddle one.
222
+
10
223
  ## [0.7.0] - 2026-08-26
11
224
 
12
225
  ### Added
package/README.md CHANGED
@@ -70,7 +70,8 @@ Every template doc except `benchmark-template.md` ships with `sources: [path/to/
70
70
  | `no-absolute-links` | warning | Link targets should not start with `/`. GitHub resolves a leading slash against the repository root, not the bundle root, so an absolute link 404s once the bundle is viewed outside its own repository. Use a same-directory relative link instead. |
71
71
  | `sources-shape` | error | Frontmatter `sources`, when present, must be a non-empty array of non-empty strings. With a repo root (explicit or auto-detected), each listed path (file or directory) must also exist under it. |
72
72
  | `sources-fresh` | warning / notice | For docs with a `sources` list and a repo root, flags a source path whose last git commit is newer than both the doc's `timestamp` and the doc file's own last commit. See "Staleness (sources-fresh)" below. |
73
- | `citations-resolve` | warning / notice | For docs with a repo root, flags a `` `path:N`/`path:N-M` `` citation (and its `` `:N` ``/`` -`M` ``/`` (`N`) `` continuations, and bare paragraph-bound short forms `:N-M`/`(N-M)`) whose target file is missing, whose range is inverted or exceeds the file, or whose start line is blank or (for a non-markdown target) only a closing brace. A full citation may also carry an optional `#anchor` (e.g. `` `CHANGELOG.md:50-144#0.24.0` ``), checked against the target's own structure/content instead of just its line numbers. A short-form citation's range into a test file is also checked for a describe/it block boundary. See "Citation resolution (citations-resolve)" below. |
73
+ | `citations-resolve` | warning / notice | For docs with a repo root, flags a `` `path:N`/`path:N-M` `` citation (and its `` `:N` ``/`` -`M` ``/`` (`N`) `` continuations, and bare paragraph-bound short forms `:N-M`/`(N-M)`) whose target file is missing, whose range is inverted or exceeds the file, or whose start line is blank or (for a non-markdown target) only a closing brace. A full citation may also carry an optional `#anchor` (e.g. `` `CHANGELOG.md:50-144#0.24.0` ``), checked against the target's own structure/content instead of just its line numbers. A backtick-delimited `` `path:#heading` `` citation (`.md` targets only) resolves to a whole Markdown section instead of a line range, immune to every line-number shift above it; see "Heading-section citations" below. A short-form citation's range into a test file is also checked for a describe/it block boundary. `--require-anchors` opts into five additional checks; see "Anchor strictness (opt-in, `--require-anchors`)" below. See "Citation resolution (citations-resolve)" below. |
74
+ | `prose-line-references` | (opt-in, `--prose-line-references`) warning / notice | Off by default. Flags a prose-embedded line reference outside `citations-resolve`'s own backtick grammar (`line N`, `lines N-M`, `lines N to M`) that is drifted, unresolvable, or ambiguous once bound to the nearest named file. `--prose-line-references-strict` additionally flags every such reference as a formatting policy violation. See "Prose line references (opt-in, `--prose-line-references`)" below. |
74
75
 
75
76
  ## repo-root auto-detection
76
77
 
@@ -124,19 +125,32 @@ Known limitation: the doc-commit comparison suppresses staleness for every sourc
124
125
  | `anchor-heading-not-found` | A heading-anchored citation has no heading (level 1 or 2) anywhere before its start line to anchor against; use a string anchor (`#"..."`) instead against a target with no heading structure. **Warning**. |
125
126
  | `anchor-not-found-in-range` | A string-anchored citation's (see below) anchor text does not occur, verbatim, on any line of the cited range. **Warning**. |
126
127
  | `anchor-malformed` | A `#` immediately follows a citation's range, but the text after it does not parse as either anchor form (unbalanced quotes, a backtick inside a quoted anchor, or nothing at all after the `#`). The citation is still checked as an ordinary anchorless citation; reported as a `notice` (never counted toward `--strict`) so a typo in an anchor does not silently turn off the very check it was written for. |
128
+ | `heading-section-not-found` | A heading-section citation's (see below) heading text does not match any level 1 or 2 heading in the target. **Warning**. |
129
+ | `heading-section-ambiguous` | A heading-section citation's heading text matches more than one level 1 or 2 heading in the target; reported rather than silently resolved to the first match. **Warning**. |
130
+ | `heading-section-empty` | A heading-section citation resolved to a single heading, but that heading's section has no non-blank content before the next heading of the same or shallower level. **Warning**. |
131
+ | `heading-section-content-anchor-not-found` | A heading-section citation's optional content anchor (`` `path:#heading#"text"` ``) does not occur on any line of the resolved section. **Warning**. |
132
+ | `heading-section-content-anchor-ambiguous` | A heading-section citation's content anchor occurs on more than one line of the resolved section; expected exactly one. **Warning**. |
133
+ | `heading-section-malformed` | A backtick-delimited `` `path:#...` `` opener does not parse as a well-formed heading-section citation (an unterminated or empty content-anchor quote, an unquoted third segment, or a non-`.md` target, including a non-lowercase `.MD` extension); reported as a `notice` (never counted toward `--strict`) so a typo does not silently vanish. |
134
+ | `anchor-required` | (opt-in, `--require-anchors`) An in-repo full citation carries no `#anchor` at all. **Warning**. |
135
+ | `anchor-not-on-last-line` | (opt-in, `--require-anchors`) A string-anchored citation's anchor text was found in the cited range, but not on the range's own last CONTENT line (see below). **Warning**. |
136
+ | `anchor-not-unique-in-range` | (opt-in, `--require-anchors`) A string-anchored citation's anchor text occurs on more than one line of the cited range. **Warning**. |
137
+ | `test-range-straddles-block` | (opt-in, `--require-anchors`) A full citation's own range into a `.test.`/`.spec.` target (`.ts`, `.js`, `.mjs`) contains a `describe`/`it`/`test` block-head line, at the same or a shallower indent than the range's own first line, on any line other than that first line. **Warning**. |
138
+ | `anchor-required-continuation` | (opt-in, `--require-anchors`) A continuation (any of `` `:N` ``/`` -`M` ``/`` (`N`) ``) or a bound paragraph-bound short-form `:N-M` citation whose governing citation resolves in-repo -- it cannot carry a `#anchor` of its own. **Warning**. |
127
139
 
128
140
  **Anchored citations.** A full citation may carry an anchor directly after its range: `` `path:N-M#anchor` ``, e.g. `` `CHANGELOG.md:50-144#0.24.0` ``. This closes a gap the checks above cannot: a CHANGELOG.md that grows by inserting each new release at the top shifts every later entry's absolute line numbers, so a citation that lands 15 lines off in the *wrong* release section is exactly as green as before the shift -- none of `missing-file`/`inverted-range`/`range-exceeds-file`/`blank-start-line`/`closing-brace-start-line` can tell the difference. An anchor pins the citation to a piece of the target's own structure or content that a pure line-shift does not preserve. Two forms, told apart by the anchor text itself:
129
141
 
130
142
  - **Heading form** (bare/unquoted, e.g. `#0.24.0` or `#[0.24.0]`): the citation's nearest *enclosing* Markdown heading (level 1 or 2 only -- see below) must contain the anchor text, and the range must not run past that heading's own section (no heading of the same or shallower level may start before the range's end line). Capped at heading level 2 deliberately: a Keep-a-Changelog `CHANGELOG.md` nests `## [x.y.z]` release headings around identically-named `### Added`/`### Changed`/`### Fixed` subsections repeated in every release, so matching "the nearest heading of any level" would make this check nearly useless there (it would match the wrong release's own `### Changed` just as readily as the right one's); deeper subsection headings are transparent to the search instead. The anchor text itself may contain word characters, `.`, and `-`, but never starts or ends on a `.` or `-`, so a trailing sentence period or a following `,`/`)` never becomes part of it (e.g. `` `path:7-8#0.24.0.` `` at the end of a sentence captures `0.24.0`, not `0.24.0.`); a hyphenated token like `0.24.0-rc1` is captured whole. A fenced code block inside the *target* (e.g. a `` ```bash `` example containing a `#`-led comment) is excluded from the heading search the same way a citing doc's own fences are already excluded from short-form matching (see below): a comment line inside an example is never mistaken for a real heading, on either end of the enclosure check.
131
143
  - **String form** (double-quoted, e.g. `#"reproduction requirement"`): the anchor text must occur, verbatim, on at least one line of the cited range itself -- "occurs inside it" rather than "encloses it", so this form also works against a non-Markdown target (`.ts`/`.js`/...) where "enclosing heading" has no meaning. The quoted text cannot cross a line break or a backtick: an unterminated opening quote (a typo) fails to parse as an anchor at all (the citation is then checked as if no anchor had been written, with no diagnostic) rather than greedily consuming the rest of the document up to some unrelated later quote character, which would otherwise silently hide every citation in between from this rule entirely.
132
144
 
133
- Anchors are checked only once the base checks above (blank-start-line, closing-brace-start-line, inverted-range, range-exceeds-file) already came back clean for that citation. Anchors are full-citation-only: a continuation or a short-form citation never carries its own path, so there is nowhere natural to hang one on. The `#anchor` suffix is entirely optional and strictly additive: an existing anchorless citation matches and is checked exactly as it was before this feature existed.
145
+ Anchors are checked only once the base checks above (blank-start-line, closing-brace-start-line, inverted-range, range-exceeds-file) already came back clean for that citation. Anchors are full-citation-only: a continuation or a short-form citation never carries its own path, so there is nowhere natural to hang one on -- under `--require-anchors`, such a continuation or bound short-form is instead flagged `anchor-required-continuation` (see below) rather than silently exempted. The `#anchor` suffix is entirely optional and strictly additive: an existing anchorless citation matches and is checked exactly as it was before this feature existed.
134
146
 
135
147
  **Anchor syntax note.** Any `#` immediately following a citation's range is read as the start of an anchor, with no other signal required -- there is no way to write a bare range immediately followed by an unrelated `#` and have it ignored. This means a Markdown link like `` [note](path:1-2#x) `` or an editor-style fragment such as `path:1-2#L7` is interpreted as an anchored citation (`x` and `L7` respectively), whether that was intended or not. The recommended, unambiguous form is the one used throughout this doc: the whole citation, range and anchor together, inside a single pair of backticks, e.g. `` `path:1-2#L7` ``.
136
148
 
137
149
  **Known limitations.** The heading form's `heading.text.includes(anchor.text)` check is a plain substring match against the heading's raw text, not a token-boundary match: an anchor `0.1` matches a heading containing `[0.10.0]` (and, by the same construction, `[0.24.0]` "contains" `24.0`). This is a deliberate simplification, not implemented as token-boundary matching in this round; treat a heading-anchored citation as a strong drift signal, not a semantic guarantee, the same way the rest of this rule's checks are mechanical rather than semantic. Separately, the heading form runs `MD_HEADING_RE` (a bare `` `#{1,6}\s+...` `` match) against the target's raw lines regardless of the target's own file type, so a `# comment` line in a `.yml` or `.json` target is matched as if it were a Markdown heading; restricting the heading form to `.md` targets is left as a known limitation rather than implemented here. Both limitations stand as documented rather than implemented; both remain easy to tighten later without touching anchor syntax or backward compatibility.
138
150
 
139
- **Continuation citations.** Once a sentence states a full `path:N` citation, prose commonly repeats just the line (or range) for a later reference in the same sentence: `` `:N` ``/`` `:N-M` `` (bare colon-prefixed), `` -`M` ``/`` –`M` `` (hyphen- or en-dash-led, the tail of a split range like `` `path:N`-`M` ``), or `` (`N`) `` (parenthesized). Each resolves against the nearest preceding citation in the same doc that resolved to a real file; a continuation right after an unresolved, ambiguous, or out-of-scope citation is silently skipped, not misattributed to an earlier, unrelated file.
151
+ **Heading-section citations.** `` `path:#heading` `` resolves to a whole Markdown section instead of a line range, so an edit anywhere above the cited section (e.g. inserting a new release at the top of a CHANGELOG) changes nothing about the citation -- see the doc comment above `HEADING_SECTION_CITATION_RE` in `src/rules/citations-resolve.ts` for the full rationale, including why the grammar requires the `:#` colon-hash and is restricted to `.md` targets. Grammar: `` `path:#heading` `` or, with an optional content anchor, `` `path:#heading#"text"` `` (always the quoted form). The heading text must match exactly one level 1 or 2 heading in the target (same containment check and same level cap as the line-range anchor's heading form above); the resolved section must be non-empty; the content anchor, when given, must occur on exactly one line inside that section. A malformed attempt (an unterminated or empty content-anchor quote, an unquoted third segment, or a non-`.md` target) is reported rather than silently dropped -- see `heading-section-malformed` in the table above. An unbalanced bracket in the heading position (`` `path:#[unclosed` ``) is not malformed by this definition: it parses as a heading named `[unclosed` and is reported as `heading-section-not-found`, the same way the line-range anchor form treats it. A bare `` `path#heading` `` without the colon is never a citation (it is the shape of an ordinary Markdown link target written in prose). See the rule table above for the exact finding names.
152
+
153
+ **Continuation citations.** Once a sentence states a full `path:N` citation, prose commonly repeats just the line (or range) for a later reference in the same sentence: `` `:N` ``/`` `:N-M` `` (bare colon-prefixed), `` -`M` ``/`` –`M` `` (hyphen- or en-dash-led, the tail of a split range like `` `path:N`-`M` ``), or `` (`N`) `` (parenthesized). Each resolves against the nearest preceding citation in the same doc that resolved to a real file; a continuation right after an unresolved, ambiguous, or out-of-scope citation is silently skipped, not misattributed to an earlier, unrelated file. A heading-section citation (see above) carries no line number and is never a continuation's target: it never sets or clears which citation a later continuation resolves against.
140
154
 
141
155
  **Short-form citations.** Distinct from a continuation above: a *bare* (no backtick, no file name at all) colon-range `:N-M`, e.g. `:580-588` in running prose. A short-form citation binds to **the last full `path:N-M` citation named earlier in the same paragraph** -- not the nearest preceding citation anywhere in the doc (that is what a continuation does); a paragraph boundary is a blank (empty or whitespace-only) line. A short-form citation with no full citation earlier in its own paragraph is reported `short-form-unbound`, not silently skipped. Only a range is recognised, never a bare single number (`:5`): a bare number is too easily an unrelated enumeration marker (e.g. a numbered list item) to detect mechanically without a large false-positive cost. A resolved short-form citation gets every check a full citation gets, plus the test-file/Markdown block-boundary check above (`test-range-*` / `markdown-range-boundary-bracket-or-fence`).
142
156
 
@@ -156,6 +170,53 @@ The colon form does not have this problem, but still needs a gate: a candidate `
156
170
 
157
171
  Like `sources-fresh`, this rule requires a repo root (explicit `--repo-root` or auto-detected): without one, it emits a single bundle-level notice (`citation resolution skipped: not inside a git work tree`) rather than silently reporting nothing.
158
172
 
173
+ ### Anchor strictness (opt-in, `--require-anchors`)
174
+
175
+ Everything above is on by default. `--require-anchors` opts into five additional, stricter checks against `citations-resolve`, all `warning`-severity, off unless the flag is passed -- an existing bundle's findings under a plain `check` are byte-for-byte unaffected by this section existing. `--require-anchors` (and its sibling `--require-anchors-allow`) is a CLI flag only; okf-kit has no bundle-level config file today (no `.okf.yml`, no `okf-kit.config`, no frontmatter field), so there is no other way to turn this on. All five checks are also exempt for a citing doc that is a reserved file (`index.md`, `log.md`), the same carve-out `citations-resolve` already gives reserved docs for short-form matching elsewhere in this doc -- a reserved doc's citations still get the unconditional base checks (`missing-file`, `inverted-range`, `anchor-not-found-in-range`, and so on), just none of the five opt-in ones:
176
+
177
+ - **`anchor-required`.** An in-repo full citation (resolved to a real target -- an unresolved one already gets its own `missing-file`/`unresolved-ambiguous` finding, not this one) carrying no `#anchor` at all is flagged. Also exempt (beyond the reserved-doc carve-out above): a citedPath matching one of `--require-anchors-allow`'s patterns (repeatable/space-separated, exact string or a `*`/`?` glob against the citation's raw, as-written path text), for a doc category not meant to be anchor-checked, e.g. `--require-anchors-allow README.md INSTALL-AGENT.md`. A target cited under several different spellings in the same bundle (`init.ts` in one paragraph, `src/init.ts` in another) needs one allow pattern per spelling, or a `*basename` glob that covers all of them -- the match is against the citation's own raw text, not the path it resolves to. `--require-anchors-allow` is commander-variadic: its list stops at the next `-`-prefixed token, so flags placed after it parse normally, but it swallows the positional bundle path if placed before it (`check --require-anchors-allow README.md <bundleDir>` fails with a missing-argument error); pass it after the bundle path.
178
+ - **`anchor-not-on-last-line`.** A string-anchored citation (`` `path:N-M#"text"` ``) whose anchor text was found in the cited range, but not on the range's own last CONTENT line. A content line is any line whose trimmed text is not empty and not composed only of closing brackets/braces/parens plus an optional trailing `,`/`;` (`}`, `});`, `]);`, `}),`, and similar); a range ending on one or more such boilerplate lines (the common shape of a `});` that closes a whole cited block) resolves to the real content line before them instead. An anchor sitting on the range's first line survives a small insertion above the range (the shifted window still contains the anchor's original content, just at a different offset); anchoring on the last content line closes that, since the original last content line falls out of the shifted window on any insertion at all.
179
+ - **`anchor-not-unique-in-range`.** A string-anchored citation whose anchor text occurs on more than one line of the cited range -- ambiguous evidence for which occurrence is the one actually pinning the citation. A count of zero is unaffected (already `anchor-not-found-in-range`, unconditionally).
180
+ - **`test-range-straddles-block`.** A FULL citation's own range into a `.test.`/`.spec.` target (`.ts`, `.js`, `.mjs`) must not run into a sibling or outer `describe`/`it`/`test` block (including `.only`/`.skip`/`.each` variants): a block-head line at the same or a shallower indent than the range's own start line, found on any line of the range OTHER than that start line, means the citation straddled out into a sibling or outer block. A block-head line indented STRICTLY DEEPER than the start line is a nested block (e.g. every `it(` inside a `describe(` the range cites in full) and is not a straddle -- citing a whole block is expected to contain every head line nested inside it. The start line itself is never checked here -- it is either a legitimate block head (the range correctly starts a block) or legitimately inside a block's body (a deliberate partial citation), and both are fine. A range that leaves its block without a later block-head line inside it (ending on an outer block's closing line) is not detected by this line-based check. Deliberately scoped to test-file targets only, and to this same opt-in: a full citation into a Markdown target legitimately cites a couple of arbitrary lines all the time (the same reasoning that keeps the Markdown half of `test-range-start-not-head`/`test-range-end-not-closing` scoped to short-form citations only, see above), and turning this on unconditionally for every existing full citation would silently regress an already-green bundle for every consumer, not just one that opted in.
181
+ - **`anchor-required-continuation`.** A continuation (`` `:N` ``/`` `:N-M` ``, `` -`M` ``/`` –`M` ``, `` (`N`) ``) or a bound paragraph-bound short-form `:N-M` citation whose governing citation (the full citation it is chained to, or the paragraph's last-named full range for a short form) resolves in-repo is flagged: it structurally cannot carry a `#anchor` of its own (see "Anchored citations" above), so it is invisible to every other check in this section, including when its governing full citation is itself already anchored -- an anchor on the full citation does not cover a later continuation of it. Same exemptions as `anchor-required`: a reserved citing doc, and `requireAnchors.allow`, matched against the GOVERNING citation's raw citedPath (a continuation carries no path of its own to match). The remedy is always the same: lift the continuation into its own full `path:N-M#anchor` citation. This closes a gap the other four checks leave open: they all fire only for a "full" citation, so a continuation chained off an ALREADY-anchored full citation was previously invisible to `--require-anchors` -- a line-shift landing it on still non-blank, in-bounds content is exactly the drift class `anchor-required` exists to catch for a full citation, just unreachable through it for a continuation.
182
+
183
+ `--require-anchors` is off by default and, once on, surfaces a real backlog the first time it is run against an existing, unaudited bundle -- the same posture `blank-start-line` already documents for `citations-resolve` itself. `anchor-required` in particular is only useful once a bundle's citations have actually been anchored; running it against a bundle that predates anchoring will flag most of that bundle's full citations.
184
+
185
+ ## Prose line references (opt-in, `--prose-line-references`)
186
+
187
+ `citations-resolve` only sees a citation written in its own backtick grammar (`` `path:N` ``). Prose habitually names a line number a different way instead -- "lines 496-498", "generate-codex-config.ts lines 129-132" -- and those shapes are structurally invisible to `citations-resolve`'s `CITATION_RE`, which requires a literal `:` between the path and the digits. A doc can be re-verified, re-stamped, and pass `check` with 0 findings while its prose line numbers are drifted, because nothing ever looked at them. `--prose-line-references` closes that gap. Off by default; every finding below is `prose-line-references`, off unless the flag is passed, so an existing bundle's `check` output is byte-for-byte unaffected by this section existing.
188
+
189
+ **Extraction grammar** (conservative -- deliberately narrower than everything actually seen in the wild): `line N`, `lines N-M` (hyphen, en-dash, or em-dash), and `lines N to M`. Deliberately NOT matched:
190
+
191
+ - `L N` / `L1` -- not observed in the corpus this rule was measured against, and more ambiguous than `line N` (`L1` already means "review finding 1" in this package's own short-form-citation authoring convention, see "Authoring guidance" above).
192
+ - `<file>:N` outside backticks -- already matched by `citations-resolve`'s own `CITATION_RE`, which has no backtick requirement (only its heading-section form does). Matching it again here would double-report the same drift under two rule ids.
193
+ - a comma-separated list of several numbers/ranges after one `lines` keyword (e.g. "lines 178, 234, 265-268"), or a second, unlabelled range chained by "vs"/"and" onto an already-extracted one (e.g. "lines 676-698 vs 564-591" only extracts `676-698`) -- only the first number/range is extracted; the rest are silently under-extracted, never mis-parsed. Under-extraction, never mis-binding, matches this rule's "never guess" posture throughout.
194
+
195
+ A reference inside a fenced code block, an indented code block, an inline code span, or a Markdown table row is excluded from extraction, same as `citations-resolve`'s own short-form matching. So is a reference falling inside a real `citations-resolve` full citation's own span (mostly relevant to a citation's quoted string anchor, e.g. `` `path.md:10-20#"see line 5 above"` ``), a reference inside an HTML comment (e.g. `` <!-- see line 5 above for the earlier draft --> ``, which narrates a past state rather than citing current content), and a reference on a line that is itself a Markdown ATX heading (e.g. `## Line 3 semantics`, which names a section about a line rather than citing it). A URL with a port (`host:8080`), an ISO timestamp, and a version string (`1.2.3`) need no special-casing at all: the grammar above requires the literal word `line`/`lines` immediately before the digits, which none of those three shapes contain. A hyphen-joined compound word ("in-line 999", "multi-line 999", "command-line 999") is also excluded: the leading boundary check rejects a match starting right after a `-`.
196
+
197
+ **Binding rule** (conservative -- never guessed across a paragraph boundary): a reference is bound to the nearest "file mention" -- a path-like token with one of `citations-resolve`'s own recognised extensions (`.ts`, `.js`, `.mjs`, `.md`, `.yml`, `.yaml`, `.json`) that resolves to a real file under the bundle's repo root, using `citations-resolve`'s own path-resolution rules verbatim (frontmatter `sources` match, ancestor climb for a bare filename, repo-root-relative, doc-relative, "last full path mentioned", repo-wide basename search):
198
+
199
+ 1. the nearest file mention in the same sentence, preceding first, then following (a rough sentence-boundary heuristic: a `.`/`!`/`?` followed by whitespace and then a capital letter, digit, backtick, quote, or open bracket, or the end of the paragraph);
200
+ 2. otherwise the nearest PRECEDING file mention in the same paragraph;
201
+ 3. otherwise `unresolvable` -- never guessed, never silently bound to the wrong file.
202
+
203
+ A candidate file-mention token that itself fails to resolve, or resolves to more than one real file (two files sharing a basename), is not skipped in favor of a farther candidate: "nearest wins" is taken literally, so the outcome is `unresolvable`/`ambiguous` rather than quietly falling through to a second-nearest mention the prose did not actually name. `ambiguous` covers only that one case, a single mention whose own basename collides across files; two DISTINCT mentions that each resolve cleanly (e.g. "`` `src/a.ts` `` sets it up; `` `src/b.ts` `` line 5 reads it") are resolved by nearest-wins as usual and are deliberately never reported as `ambiguous` -- the binding rule picked one of them on purpose, it did not fail to pick. A bare backtick-wrapped filename (`` `src/cli.ts` ``) counts as a file mention exactly the way a bare filename in running prose does -- unlike the base extraction grammar above, mention detection deliberately does NOT exclude inline code, since a backtick-wrapped filename is this package's own normal, encouraged way to name a file in prose (see "Authoring guidance" above). `index.md`/`log.md` (reserved citing docs) are skipped entirely, the same carve-out `citations-resolve` already gives them: an append-only narrative journal routinely narrates historical line-number deltas as prose about the past, not live citations against current content.
204
+
205
+ **Findings:**
206
+
207
+ | Reason | Meaning | Severity |
208
+ |--------|---------|----------|
209
+ | `out-of-bounds` | The bound reference's range is inverted, or its start (or end) line exceeds the bound file's line count. | Warning |
210
+ | `blank-start-line` | The bound reference's start line is blank. | Warning |
211
+ | `unresolvable` | No file mention could be bound (no candidate in the sentence or paragraph, the nearest candidate does not resolve to a real file, or the resolved target could not be read). | Notice |
212
+ | `ambiguous` | The nearest file mention's basename resolves to more than one real file; not evaluated. The finding names both (or all) candidates. | Notice |
213
+
214
+ `unresolvable` and `ambiguous` are notice-severity rather than warning: "line" occurs constantly in ordinary English with no adjacent file nearby ("in line with", "product line"), so "no file mention nearby" is weaker evidence of actual drift than a reference that resolved cleanly and then turned out wrong. `out-of-bounds` and `blank-start-line` are warning-severity, mirroring `citations-resolve`'s own "a wrong start/target is strong drift evidence" posture once a reference DOES resolve to a single real file.
215
+
216
+ **Strict mode (`--prose-line-references-strict`, ignored unless `--prose-line-references` is also passed):** flags EVERY extracted reference, resolved or not, with its own warning-severity `prose-line-reference-not-anchored` finding and a fixed remedy: lift it into a backtick `path:N-M` citation (so `citations-resolve` can verify it going forward), or de-precise it to a symbol name instead of a line number. This is additive, not a replacement for the base checks above -- a drifted reference under strict mode gets both its `out-of-bounds`/etc. finding AND the policy finding, since both facts are independently true of it.
217
+
218
+ `--prose-line-references` is off by default and, once on, surfaces a real backlog the first time it is run against an existing, unaudited bundle -- the same posture `--require-anchors` already documents above. Known false-positive category: the binding rule conflates "the file this sentence is about" with "the file these line numbers belong to" when they differ, e.g. "`06-handoff.md`'s `## Accepted Waivers` heading ... (test lines 132-135)" binds `test lines 132-135` to `06-handoff.md` even though the line numbers actually belong to an unnamed test file discussed elsewhere in the same sentence -- a real limitation of a conservative, non-semantic heuristic, not a bug; expect some of this class of noise on a first run against an existing corpus.
219
+
159
220
  ## Exit codes
160
221
 
161
222
  | Code | Meaning |
@@ -170,7 +231,7 @@ This is advisory: don't fail the build on warnings unless you pass `--strict`. U
170
231
 
171
232
  ```yaml
172
233
  - name: OKF bundle check
173
- run: npx okf-kit@0.7.0 check path/to/bundle
234
+ run: npx okf-kit@0.9.0 check path/to/bundle
174
235
  ```
175
236
 
176
237
  Pin the version: an unpinned `npx okf-kit` picks up new rules on their release day, which turns an unrelated PR red.
package/dist/cli.d.ts CHANGED
@@ -5,6 +5,34 @@ export { UsageError };
5
5
  export interface CheckOptions {
6
6
  repoRoot?: string;
7
7
  strict?: boolean;
8
+ /**
9
+ * Opt-in for `citations-resolve`'s stricter anchor checks
10
+ * (`anchor-required`, `anchor-not-on-last-line`,
11
+ * `anchor-not-unique-in-range`, and the test-file block-boundary check
12
+ * applied to a full citation's own range). See the "Anchor strictness
13
+ * (opt-in)" doc block in `src/rules/citations-resolve.ts`.
14
+ */
15
+ requireAnchors?: boolean;
16
+ /**
17
+ * Glob/exact-match patterns exempting a matching citedPath from
18
+ * `anchor-required` (see `RequireAnchorsOptions` in `src/types.ts`).
19
+ * Ignored when `requireAnchors` is not set.
20
+ */
21
+ requireAnchorsAllow?: string[];
22
+ /**
23
+ * Opt-in for `prose-line-references`: flag a prose-embedded line
24
+ * reference outside `citations-resolve`'s own backtick grammar (e.g.
25
+ * "lines 129-132") that is drifted, unresolvable, or ambiguous. See the
26
+ * doc block in `src/rules/prose-line-references.ts`.
27
+ */
28
+ proseLineReferences?: boolean;
29
+ /**
30
+ * Also flag EVERY prose line reference `prose-line-references` extracts,
31
+ * not only a drifted one, with the remedy to lift it into a backtick
32
+ * citation or a symbol name. Ignored when `proseLineReferences` is not
33
+ * set.
34
+ */
35
+ proseLineReferencesStrict?: boolean;
8
36
  /** Test-only override for git access; production code shells out to the real `git` binary. */
9
37
  runGit?: RunGit;
10
38
  }
package/dist/cli.js CHANGED
@@ -25,6 +25,14 @@ export function runCheck(bundleDir, options = {}) {
25
25
  ? path.resolve(options.repoRoot)
26
26
  : detectRepoRoot(resolvedBundleDir, options.runGit);
27
27
  const ctx = loadBundle(resolvedBundleDir, repoRoot, options.runGit);
28
+ if (options.requireAnchors) {
29
+ ctx.requireAnchors = { allow: options.requireAnchorsAllow ?? [] };
30
+ }
31
+ if (options.proseLineReferences) {
32
+ ctx.proseLineReferences = {
33
+ strict: Boolean(options.proseLineReferencesStrict),
34
+ };
35
+ }
28
36
  const findings = allRules.flatMap((rule) => rule.run(ctx));
29
37
  const summary = summarize(findings);
30
38
  const exitCode = summary.errors > 0 || (Boolean(options.strict) && summary.warnings > 0)
@@ -51,12 +59,24 @@ program
51
59
  "(auto-detected via `git rev-parse --show-toplevel` from the bundle dir when omitted)")
52
60
  .option("-j, --json", "Output findings as JSON")
53
61
  .option("-s, --strict", "Also fail (exit 1) when warnings are present")
62
+ .option("--require-anchors", "citations-resolve: also require every in-repo full citation to carry a #anchor, and check " +
63
+ "a string anchor lands uniquely on the last line of its range (opt-in, see README)")
64
+ .option("--require-anchors-allow <patterns...>", "citations-resolve: citedPath glob/exact patterns (e.g. README.md) exempt from " +
65
+ "--require-anchors' anchor-required check")
66
+ .option("--prose-line-references", "prose-line-references: flag a drifted, unresolvable, or ambiguous prose-embedded line " +
67
+ 'reference outside citations-resolve\'s own backtick grammar, e.g. "lines 129-132" (opt-in, see README)')
68
+ .option("--prose-line-references-strict", "prose-line-references: also flag every prose line reference, not only a drifted one, with " +
69
+ "the remedy to lift it into a backtick citation or a symbol name (ignored unless --prose-line-references is also passed)")
54
70
  .exitOverride()
55
71
  .action((bundleDir, opts) => {
56
72
  try {
57
73
  const result = runCheck(bundleDir, {
58
74
  repoRoot: opts.repoRoot,
59
75
  strict: opts.strict,
76
+ requireAnchors: opts.requireAnchors,
77
+ requireAnchorsAllow: opts.requireAnchorsAllow,
78
+ proseLineReferences: opts.proseLineReferences,
79
+ proseLineReferencesStrict: opts.proseLineReferencesStrict,
60
80
  });
61
81
  const output = opts.json
62
82
  ? 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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"}
@@ -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;