okf-kit 0.4.0 → 0.5.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 +33 -0
- package/README.md +25 -1
- package/dist/rules/citations-resolve.d.ts +2 -0
- package/dist/rules/citations-resolve.js +629 -0
- package/dist/rules/citations-resolve.js.map +1 -0
- package/dist/rules/index.d.ts +2 -1
- package/dist/rules/index.js +3 -1
- package/dist/rules/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ All notable changes to `okf-kit` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.0] - 2026-08-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- New rule `citations-resolve`: for docs with a repo root, flags a
|
|
13
|
+
`` `path:N`/`path:N-M` `` citation (and its `` `:N` ``/`` -`M` ``/`` (`N`) ``
|
|
14
|
+
continuations) whose target file is missing, whose range is inverted or
|
|
15
|
+
exceeds the file, or whose start line is blank or (for a non-markdown
|
|
16
|
+
target) only a closing brace. Ported from agent-grounding's
|
|
17
|
+
`scripts/okf-citations-resolve.mjs` (agent-grounding PR #185), a
|
|
18
|
+
repo-local spike that closed a gap `sources-fresh` cannot: `sources-fresh`
|
|
19
|
+
only compares a doc's `sources` list against file mtimes, so it is
|
|
20
|
+
structurally blind to an edit that shifts line numbers inside a still-fresh
|
|
21
|
+
source file. `citations-resolve` gives every repo consuming okf-kit this
|
|
22
|
+
check without copying the script. See "Citation resolution
|
|
23
|
+
(citations-resolve)" in the README. Not ported: the original's
|
|
24
|
+
`docs/testing`-file expansion (an agent-grounding-specific convention, not
|
|
25
|
+
part of the OKF spec, and not exercised by the ported tests) -- this rule
|
|
26
|
+
only scans the docs already loaded for the bundle being checked.
|
|
27
|
+
`citations-resolve` findings are warn-level: an existing `okf-kit check
|
|
28
|
+
--strict` run that was previously green can now fail once this version is
|
|
29
|
+
picked up, purely from this rule's new warnings, with no other change to
|
|
30
|
+
the bundle. Two path-resolution refinements differ from the
|
|
31
|
+
agent-grounding original: (1) a bare filename with no `/` (e.g.
|
|
32
|
+
`README.md`) now tries the citing doc's own directory and each ancestor
|
|
33
|
+
directory up to the repo root, nearest first, before falling back to a
|
|
34
|
+
plain repo-root-relative lookup, so a package-level file is not shadowed
|
|
35
|
+
by a same-named file that happens to also sit at the repo root; (2) a
|
|
36
|
+
`` `path:N` `` match that starts at column 0 of its line, right after a
|
|
37
|
+
previous line ending in `-`/`–`, is treated as the phantom tail of a
|
|
38
|
+
filename hard-wrapped across the line break and skipped, instead of being
|
|
39
|
+
checked (and typically flagged `missing-file`) as its own citation.
|
|
40
|
+
|
|
8
41
|
## [0.4.0] - 2026-08-13
|
|
9
42
|
|
|
10
43
|
### Changed
|
package/README.md
CHANGED
|
@@ -69,6 +69,7 @@ Every template doc except `benchmark-template.md` ships with `sources: [path/to/
|
|
|
69
69
|
| `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. |
|
|
70
70
|
| `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. |
|
|
71
71
|
| `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. |
|
|
72
|
+
| `citations-resolve` | warning / notice | For docs with a repo root, flags a `` `path:N`/`path:N-M` `` citation (and its `` `:N` ``/`` -`M` ``/`` (`N`) `` continuations) 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. See "Citation resolution (citations-resolve)" below. |
|
|
72
73
|
|
|
73
74
|
## repo-root auto-detection
|
|
74
75
|
|
|
@@ -99,6 +100,29 @@ Known limitation: the doc-commit comparison suppresses staleness for every sourc
|
|
|
99
100
|
|
|
100
101
|
**Authoring guidance:** when you re-verify a doc against its sources, bump its frontmatter `timestamp` (and add a line to the bundle's `log.md`) so `sources-fresh` reflects that the doc is current again.
|
|
101
102
|
|
|
103
|
+
## Citation resolution (citations-resolve)
|
|
104
|
+
|
|
105
|
+
`sources-fresh` catches a source file changing after a doc's `timestamp`, but it is structurally blind to an edit that shifts *line numbers* inside a still-fresh file: a doc citing `path:42` keeps citing line 42 even after an edit moves the referenced content to line 50. `citations-resolve` finds every `` `path:N` ``/`` `path:N-M` `` citation in a doc, resolves `path` to a real file, and flags a citation that clearly cannot be pointing at real content any more. It is mechanical only (no symbol/AST resolution): it does not verify the cited line is *semantically* the right one, only that the target exists, the range is sound, and the start line is not blank or (for a non-markdown target) a lone closing brace/bracket.
|
|
106
|
+
|
|
107
|
+
| Rule id | Meaning |
|
|
108
|
+
|---------|---------|
|
|
109
|
+
| `missing-file` | The cited path could not be resolved to a real file (see path resolution below). |
|
|
110
|
+
| `path-traversal-rejected` | The cited path contains a `..` segment; rejected without ever being resolved. |
|
|
111
|
+
| `inverted-range` | A range's end line is before its start line. |
|
|
112
|
+
| `range-exceeds-file` | The cited line (or the end of a range) is past the end of the resolved file. |
|
|
113
|
+
| `blank-start-line` | The start line resolves to a blank line. A citation whose start line is blank is flagged; cite the first content line instead. Consumers enabling this rule on an existing bundle should expect to fix citations like this once, the first time they turn it on. |
|
|
114
|
+
| `closing-brace-start-line` | For a non-markdown target, the start line is only a closing brace/bracket/paren (`}`, `)`, `]`, optionally with a trailing `,`/`;`) -- a common signature of a cited block having moved. |
|
|
115
|
+
| `unresolved-ambiguous` | More than one file in the repo matches the cited path; reported as a `notice` (never counted toward `--strict`), not guessed at. |
|
|
116
|
+
| `unreadable-target` | The resolved target file exists but could not be read (e.g. permission denied); reported as a `notice` (never counted toward `--strict`) with the OS error code in `detail`, since an unreadable file is not evidence the citation itself is wrong. |
|
|
117
|
+
|
|
118
|
+
**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.
|
|
119
|
+
|
|
120
|
+
**Hard-wrapped prose.** A doc that hard-wraps prose at a fixed column can split a hyphenated filename across a line break right after its trailing `-` (e.g. `run-state-lifecycle-and-markers.md` wrapping to `run-state-lifecycle-and-\nmarkers.md`). A `` `path:N` `` match is skipped entirely (not checked, not counted as a citation) when it starts at column 0 of its line -- optionally after only whitespace or a list/quote marker (`-`, `*`, `>`, digits, `.`) -- and the previous line ends with `-`/`–`: the signature of a wrapped continuation rather than a genuine citation to a short bare filename.
|
|
121
|
+
|
|
122
|
+
**Path resolution**, tried in order: (1) the doc's own frontmatter `sources` list, matched by exact suffix, only when exactly one source matches; (2) for a citedPath with no `/` only, doc-relative and then each ancestor directory of the doc up to the repo root, nearest first (so a bare filename like `README.md` prefers the README *for the package the doc lives in* over a same-named file that merely happens to also sit at the repo root); (3) repo-root-relative; (4) doc-relative; (5) the nearest earlier citation in the same doc whose path contains a `/` and ends with the same suffix (the "full path was mentioned earlier" convention); (6) a repo-wide search by basename. A cited path starting with `/` is treated as an absolute or placeholder path (e.g. inside a fabricated example stack trace) and skipped without a finding.
|
|
123
|
+
|
|
124
|
+
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.
|
|
125
|
+
|
|
102
126
|
## Exit codes
|
|
103
127
|
|
|
104
128
|
| Code | Meaning |
|
|
@@ -113,7 +137,7 @@ This is advisory: don't fail the build on warnings unless you pass `--strict`. U
|
|
|
113
137
|
|
|
114
138
|
```yaml
|
|
115
139
|
- name: OKF bundle check
|
|
116
|
-
run: npx okf-kit@0.
|
|
140
|
+
run: npx okf-kit@0.5.0 check path/to/bundle
|
|
117
141
|
```
|
|
118
142
|
|
|
119
143
|
Pin the version: an unpinned `npx okf-kit` picks up new rules on their release day, which turns an unrelated PR red.
|
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getValidSources } from "../util.js";
|
|
4
|
+
const RULE_ID = "citations-resolve";
|
|
5
|
+
/**
|
|
6
|
+
* Warn-only checker for `path:N[-M]` citations inside a bundle's docs (and
|
|
7
|
+
* their continuation forms, see below).
|
|
8
|
+
*
|
|
9
|
+
* Ported from agent-grounding's `scripts/okf-citations-resolve.mjs`
|
|
10
|
+
* (PR #185), which was a repo-local, no-dependency spike at closing a gap
|
|
11
|
+
* `sources-fresh` cannot: `sources-fresh` only compares a doc's `sources`
|
|
12
|
+
* list against file mtimes, so it is structurally blind to an edit that
|
|
13
|
+
* shifts line numbers inside a still-fresh source file. This rule finds
|
|
14
|
+
* every `path:N` / `path:N-M` citation in a doc, resolves `path` to a real
|
|
15
|
+
* file, and warns when the citation clearly cannot be pointing at real
|
|
16
|
+
* content any more.
|
|
17
|
+
*
|
|
18
|
+
* What it checks (mechanical only, no symbol/AST resolution):
|
|
19
|
+
* - the target file does not exist (after resolution, see below)
|
|
20
|
+
* - a range's end line is before its start line (an inverted range)
|
|
21
|
+
* - the cited line (or the end of a range) is past the end of the file
|
|
22
|
+
* - the start line is blank
|
|
23
|
+
* - for non-markdown targets only, the start line is only a closing
|
|
24
|
+
* brace/bracket/paren (`}`, `)`, `]`, optionally with a trailing `,`/`;`)
|
|
25
|
+
* -- a common signature of "the cited block moved and this now points at
|
|
26
|
+
* the line right after it used to end"
|
|
27
|
+
*
|
|
28
|
+
* What it does NOT check: whether the cited line is semantically the right
|
|
29
|
+
* one (that needs the actual symbol; out of scope for a warn-only mechanical
|
|
30
|
+
* rule), nor citations without a file extension this rule recognises.
|
|
31
|
+
*
|
|
32
|
+
* Hard-wrapped prose. Some docs hard-wrap prose at a fixed column, which can
|
|
33
|
+
* split a hyphenated filename like `run-state-lifecycle-and-markers.md`
|
|
34
|
+
* across a line break right after the trailing `-`. `CITATION_RE` cannot
|
|
35
|
+
* (and should not) span the newline to reassemble the real citedPath, but
|
|
36
|
+
* without a guard it instead matches the phantom tail on its own
|
|
37
|
+
* (`markers.md:172`), which usually does not exist as a file and produces a
|
|
38
|
+
* false `missing-file`. A `CITATION_RE` match is skipped entirely (neither
|
|
39
|
+
* checked nor counted as a real citation) when it starts at column 0 of its
|
|
40
|
+
* line -- optionally after only whitespace or a list/quote marker
|
|
41
|
+
* (`-`, `*`, `>`, digits, `.`) -- and the previous line ends with `-` or
|
|
42
|
+
* `–`: the signature of a wrapped continuation, not a new citation.
|
|
43
|
+
*
|
|
44
|
+
* Unreadable targets. A resolved target file that exists but cannot be read
|
|
45
|
+
* (permission denied, and similar OS-level failures) is reported as a
|
|
46
|
+
* bundle-level `notice` tagged `unreadable-target` with the OS error code in
|
|
47
|
+
* `detail`, rather than throwing and aborting the whole check: a citation
|
|
48
|
+
* pointing at a file the checker itself cannot read is not evidence the
|
|
49
|
+
* citation is wrong.
|
|
50
|
+
*
|
|
51
|
+
* Porting decision (scope cut from the original): the original script also
|
|
52
|
+
* scanned any `docs/testing/*.md` file that an okf doc's frontmatter
|
|
53
|
+
* `sources` cited, an agent-grounding-specific convention (docs/testing is
|
|
54
|
+
* not part of the OKF spec) that is not exercised by any of the ported
|
|
55
|
+
* tests. This rule instead only scans the docs `loadBundle` already loaded
|
|
56
|
+
* for `ctx.bundleDir` -- a citing doc outside the bundle is out of scope.
|
|
57
|
+
*
|
|
58
|
+
* Path resolution. A bundle's prose citations are frequently written
|
|
59
|
+
* *relative to context* rather than as a full repo-root path, e.g. a doc's
|
|
60
|
+
* frontmatter `sources` list carries `packages/foo/src/cli.ts`, but the
|
|
61
|
+
* prose later just says `cli.ts:42` or `src/cli.ts:42` once the reader
|
|
62
|
+
* "knows" which package the section is about -- and source basenames can be
|
|
63
|
+
* reused across sibling packages, so a resolver that only tried
|
|
64
|
+
* repo-root-relative and doc-relative paths would either flag real,
|
|
65
|
+
* non-drifted citations as "missing file" or silently check the wrong
|
|
66
|
+
* same-named file. Resolution here instead tries, in order:
|
|
67
|
+
* 1. the citing doc's own frontmatter `sources` list, matched by exact
|
|
68
|
+
* suffix (`source === citedPath || source.endsWith('/' + citedPath)`),
|
|
69
|
+
* only when exactly one source matches -- the doc's author already
|
|
70
|
+
* disambiguated which physical file this citation means
|
|
71
|
+
* 2. for a citedPath with no `/` only: doc-relative, then each ancestor
|
|
72
|
+
* directory of the doc up to (and including) repoRoot, nearest first
|
|
73
|
+
* -- a bare filename like `README.md` almost always means "the README
|
|
74
|
+
* *for the package this doc lives in*", and a repo-root-relative
|
|
75
|
+
* lookup done first would instead silently resolve to an unrelated,
|
|
76
|
+
* differently-sized file of the same name that happens to sit at the
|
|
77
|
+
* repo root ("shadowing"). A citedPath containing a `/` skips straight
|
|
78
|
+
* to step 3: the caller already qualified enough of the path that
|
|
79
|
+
* "closest ancestor" guessing isn't needed.
|
|
80
|
+
* 3. repo-root-relative (`<repoRoot>/<citedPath>`)
|
|
81
|
+
* 4. doc-relative (`<dirname(doc)>/<citedPath>`) -- redundant with step 2
|
|
82
|
+
* for a no-`/` citedPath (already tried there), the effective first
|
|
83
|
+
* resolution attempt for a `/`-containing one
|
|
84
|
+
* 5. the nearest earlier citation *in the same doc* whose path contains a
|
|
85
|
+
* `/` and ends with the same suffix as citedPath (the "last full path
|
|
86
|
+
* mentioned" convention)
|
|
87
|
+
* 6. a repo-wide search for a file whose path ends with citedPath (or,
|
|
88
|
+
* for a bare filename, whose basename equals it)
|
|
89
|
+
* A citedPath starting with `/` is treated as out of scope (an absolute or
|
|
90
|
+
* placeholder path, e.g. inside a fabricated example stack trace) and
|
|
91
|
+
* skipped without a finding. When step 6 finds more than one candidate the
|
|
92
|
+
* citation is reported as a `notice`-severity "ambiguous target" finding
|
|
93
|
+
* rather than guessed at or false-flagged as missing (never counted toward
|
|
94
|
+
* `--strict`). A citation resolved by none of the above, with zero
|
|
95
|
+
* candidates at step 6, is reported as `missing-file`. A citedPath
|
|
96
|
+
* containing a `..` segment is rejected outright (`path-traversal-rejected`)
|
|
97
|
+
* without ever being resolved, so a malformed or hostile citation cannot
|
|
98
|
+
* walk resolution outside the repo.
|
|
99
|
+
*
|
|
100
|
+
* Continuation citations. Once a sentence has stated a full `path:N`
|
|
101
|
+
* citation, prose habitually repeats just the line (or range) for a later
|
|
102
|
+
* reference in the same sentence rather than retyping the path, in three
|
|
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
|
|
106
|
+
* tail half of a `` `path:N`-`M` `` split range
|
|
107
|
+
* - `` (`N`) `` -- a parenthesized bare line
|
|
108
|
+
* Each of these resolves against `governing`: the nearest *preceding*
|
|
109
|
+
* citation (full or itself a continuation) that resolved to a real file,
|
|
110
|
+
* scanned in document order. `governing` resets to none whenever the
|
|
111
|
+
* citation immediately before it failed to resolve, was ambiguous, or was
|
|
112
|
+
* out of scope (leading `/`) -- a continuation never silently inherits a
|
|
113
|
+
* stale or unrelated path from further up the doc. A continuation with no
|
|
114
|
+
* governing citation at all (e.g. very start of a doc) is skipped, not
|
|
115
|
+
* flagged: there is nothing to validate it against.
|
|
116
|
+
*
|
|
117
|
+
* A continuation is further split into two roles (see
|
|
118
|
+
* collectContinuationAtoms): "fresh" (a genuinely new start line, checked
|
|
119
|
+
* the same five ways as a full citation's start) versus "extension" (only
|
|
120
|
+
* ever the tail `M` of a split range whose start was already checked) --
|
|
121
|
+
* an extension gets *only* the range-bound checks (inverted-range,
|
|
122
|
+
* range-exceeds-file), never blank/closing-brace: that start line was
|
|
123
|
+
* already checked when it was first cited, so re-running it here would
|
|
124
|
+
* double-report the same drift, and a range legitimately ending on a
|
|
125
|
+
* closing brace is normal, not drift.
|
|
126
|
+
*
|
|
127
|
+
* Requires `ctx.repoRoot` (explicit `--repo-root` or auto-detected): target
|
|
128
|
+
* files usually live outside the bundle itself, so without a repo root
|
|
129
|
+
* there is no filesystem tree to resolve a citation against. Without one,
|
|
130
|
+
* this rule emits a single bundle-level notice, matching `sources-fresh`'s
|
|
131
|
+
* "not inside a git work tree" posture.
|
|
132
|
+
*/
|
|
133
|
+
const CITATION_RE = /([\w./-]+\.(?:ts|js|mjs|md|yml|yaml|json)):(\d+)(?:-(\d+))?/g;
|
|
134
|
+
// Continuation citation forms (see the "Continuation citations" doc block
|
|
135
|
+
// above). Each requires the backtick delimiter as part of the match so it
|
|
136
|
+
// can never overlap a CITATION_RE match: a full citation's regex match
|
|
137
|
+
// never includes the surrounding backticks, and none of these three
|
|
138
|
+
// require a `path.ext` prefix before the digits.
|
|
139
|
+
const CONT_COLON_RE = /`:(\d+)(?:-(\d+))?`/g;
|
|
140
|
+
const CONT_DASH_RE = /[-–]`(\d+)`/g;
|
|
141
|
+
const CONT_PAREN_RE = /\(`(\d+)`\)/g;
|
|
142
|
+
const CLOSING_ONLY_EXTS = new Set(["ts", "js", "mjs", "yml", "yaml", "json"]);
|
|
143
|
+
const CLOSING_BRACE_RE = /^[)\]}][;,]?$/;
|
|
144
|
+
const EXCLUDED_DIRS = new Set([
|
|
145
|
+
"node_modules",
|
|
146
|
+
".git",
|
|
147
|
+
"dist",
|
|
148
|
+
"build",
|
|
149
|
+
"coverage",
|
|
150
|
+
".next",
|
|
151
|
+
".turbo",
|
|
152
|
+
// Ported 1:1 from the original script's own disposable test-fixtures
|
|
153
|
+
// exclusion, kept so the ported EXCLUDED_DIRS regression test carries
|
|
154
|
+
// over verbatim; harmless for any consuming repo that has no directory
|
|
155
|
+
// with this name.
|
|
156
|
+
"okf-citations-resolve-fixtures",
|
|
157
|
+
]);
|
|
158
|
+
function isFile(p) {
|
|
159
|
+
try {
|
|
160
|
+
return fs.statSync(p).isFile();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** True when citedPath has a literal `..` path segment. */
|
|
167
|
+
function hasParentSegment(citedPath) {
|
|
168
|
+
return citedPath.split("/").includes("..");
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Repo-wide search for files with an exact basename, memoized per root
|
|
172
|
+
* within `cache`. `cache` is built lazily and scoped to a single
|
|
173
|
+
* `citationsResolveRule.run(ctx)` invocation (see the rule's `run`), not
|
|
174
|
+
* held at module scope, so an in-process caller running the check
|
|
175
|
+
* repeatedly (e.g. in a long-lived process, or a test suite that edits
|
|
176
|
+
* fixtures between runs) never sees a stale index from an earlier run.
|
|
177
|
+
*/
|
|
178
|
+
function findByBasename(cache, root, basename) {
|
|
179
|
+
let index = cache.get(root);
|
|
180
|
+
if (!index) {
|
|
181
|
+
index = new Map();
|
|
182
|
+
const found = index;
|
|
183
|
+
const walk = (dir) => {
|
|
184
|
+
let entries;
|
|
185
|
+
try {
|
|
186
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
for (const entry of entries) {
|
|
192
|
+
if (entry.name.startsWith(".") && entry.name !== ".github")
|
|
193
|
+
continue;
|
|
194
|
+
if (EXCLUDED_DIRS.has(entry.name))
|
|
195
|
+
continue;
|
|
196
|
+
const full = path.join(dir, entry.name);
|
|
197
|
+
if (entry.isDirectory()) {
|
|
198
|
+
walk(full);
|
|
199
|
+
}
|
|
200
|
+
else if (entry.isFile()) {
|
|
201
|
+
const list = found.get(entry.name) ?? [];
|
|
202
|
+
list.push(full);
|
|
203
|
+
found.set(entry.name, list);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
walk(root);
|
|
208
|
+
cache.set(root, index);
|
|
209
|
+
}
|
|
210
|
+
return index.get(basename) ?? [];
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Finds the nearest citation earlier in the same doc whose cited path
|
|
214
|
+
* contains a `/` and ends with the same suffix as `citedPath`, the "full
|
|
215
|
+
* path was mentioned earlier in this section/doc" convention.
|
|
216
|
+
*/
|
|
217
|
+
function findPriorQualifiedCitation(content, beforeIndex, citedPath) {
|
|
218
|
+
const suffix = "/" + citedPath;
|
|
219
|
+
let best = null;
|
|
220
|
+
let bestIndex = -1;
|
|
221
|
+
const re = new RegExp(CITATION_RE.source, "g");
|
|
222
|
+
let m;
|
|
223
|
+
while ((m = re.exec(content)) !== null) {
|
|
224
|
+
if (m.index >= beforeIndex)
|
|
225
|
+
break;
|
|
226
|
+
const candidate = m[1];
|
|
227
|
+
if (candidate === citedPath)
|
|
228
|
+
continue; // not more qualified than itself
|
|
229
|
+
if (candidate.includes("/") &&
|
|
230
|
+
(candidate === citedPath || candidate.endsWith(suffix))) {
|
|
231
|
+
if (m.index > bestIndex) {
|
|
232
|
+
bestIndex = m.index;
|
|
233
|
+
best = candidate;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return best;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* For a bare (no `/`) citedPath, tries doc-relative first, then each
|
|
241
|
+
* ancestor directory of the doc, nearest first, up to and including `root`.
|
|
242
|
+
* Returns the first real file found, or `null`. See the "Path resolution"
|
|
243
|
+
* doc block above (step 2) for why this runs before the plain
|
|
244
|
+
* repo-root-relative lookup: a same-named file closer to the citing doc
|
|
245
|
+
* (e.g. a package's own `README.md`) should win over one that merely
|
|
246
|
+
* happens to also exist at the repo root.
|
|
247
|
+
*/
|
|
248
|
+
function resolveViaAncestorClimb(root, docAbsPath, citedPath) {
|
|
249
|
+
const resolvedRoot = path.resolve(root);
|
|
250
|
+
let dir = path.dirname(docAbsPath);
|
|
251
|
+
for (;;) {
|
|
252
|
+
const candidate = path.resolve(dir, citedPath);
|
|
253
|
+
if (isFile(candidate))
|
|
254
|
+
return candidate;
|
|
255
|
+
if (path.resolve(dir) === resolvedRoot)
|
|
256
|
+
return null;
|
|
257
|
+
const parent = path.dirname(dir);
|
|
258
|
+
if (parent === dir)
|
|
259
|
+
return null; // reached the filesystem root
|
|
260
|
+
dir = parent;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Resolves a citation's path to a single real file. Returns `{ skip: true }`
|
|
265
|
+
* for a citedPath out of scope (leading `/`), `{ path }` on a definitive
|
|
266
|
+
* single resolution, `{ ambiguous: true, candidates }` when more than one
|
|
267
|
+
* plausible target exists, or `null` when nothing matches. Callers must
|
|
268
|
+
* reject a citedPath with a `..` segment (see hasParentSegment) before
|
|
269
|
+
* calling this; it is not re-checked here.
|
|
270
|
+
*/
|
|
271
|
+
function resolveCitation(cache, root, docAbsPath, docContent, docSources, citedPath, matchIndex) {
|
|
272
|
+
if (citedPath.startsWith("/")) {
|
|
273
|
+
return { skip: true };
|
|
274
|
+
}
|
|
275
|
+
const sourceMatches = docSources.filter((s) => s === citedPath || s.endsWith("/" + citedPath));
|
|
276
|
+
if (sourceMatches.length === 1) {
|
|
277
|
+
const candidate = path.resolve(root, sourceMatches[0]);
|
|
278
|
+
if (isFile(candidate))
|
|
279
|
+
return { path: candidate };
|
|
280
|
+
}
|
|
281
|
+
if (!citedPath.includes("/")) {
|
|
282
|
+
const viaAncestor = resolveViaAncestorClimb(root, docAbsPath, citedPath);
|
|
283
|
+
if (viaAncestor)
|
|
284
|
+
return { path: viaAncestor };
|
|
285
|
+
}
|
|
286
|
+
const rootRelative = path.resolve(root, citedPath);
|
|
287
|
+
if (isFile(rootRelative))
|
|
288
|
+
return { path: rootRelative };
|
|
289
|
+
const docRelative = path.resolve(path.dirname(docAbsPath), citedPath);
|
|
290
|
+
if (isFile(docRelative))
|
|
291
|
+
return { path: docRelative };
|
|
292
|
+
const prior = findPriorQualifiedCitation(docContent, matchIndex, citedPath);
|
|
293
|
+
if (prior) {
|
|
294
|
+
const candidate = path.resolve(root, prior);
|
|
295
|
+
if (isFile(candidate))
|
|
296
|
+
return { path: candidate };
|
|
297
|
+
}
|
|
298
|
+
const base = citedPath.includes("/")
|
|
299
|
+
? citedPath.split("/").pop()
|
|
300
|
+
: citedPath;
|
|
301
|
+
const bySuffix = findByBasename(cache, root, base).filter((m) => {
|
|
302
|
+
const normalized = m.split(path.sep).join("/");
|
|
303
|
+
return (normalized === citedPath ||
|
|
304
|
+
normalized.endsWith("/" + citedPath) ||
|
|
305
|
+
!citedPath.includes("/"));
|
|
306
|
+
});
|
|
307
|
+
if (bySuffix.length === 1)
|
|
308
|
+
return { path: bySuffix[0] };
|
|
309
|
+
if (bySuffix.length > 1) {
|
|
310
|
+
return {
|
|
311
|
+
ambiguous: true,
|
|
312
|
+
candidates: bySuffix.map((m) => path.relative(root, m)),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
function splitLines(content) {
|
|
318
|
+
const lines = content.split("\n");
|
|
319
|
+
if (lines.length > 0 &&
|
|
320
|
+
lines[lines.length - 1] === "" &&
|
|
321
|
+
content.endsWith("\n")) {
|
|
322
|
+
lines.pop();
|
|
323
|
+
}
|
|
324
|
+
return lines;
|
|
325
|
+
}
|
|
326
|
+
// Range-bound checks shared by a full citation's own range (via
|
|
327
|
+
// checkTarget below) and a cont-ext atom's extension (via
|
|
328
|
+
// checkRangeBoundOnly): a citation's end before its start, or either bound
|
|
329
|
+
// past the end of the file. Both are pure "does this range make sense"
|
|
330
|
+
// checks, independent of what the start line's content actually is.
|
|
331
|
+
function checkRangeBound(startLine, endLine, lineCount) {
|
|
332
|
+
if (endLine !== null && endLine < startLine) {
|
|
333
|
+
return {
|
|
334
|
+
rule: "inverted-range",
|
|
335
|
+
message: `range end (${endLine}) is before its start (${startLine})`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const last = endLine ?? startLine;
|
|
339
|
+
if (startLine > lineCount || last > lineCount) {
|
|
340
|
+
return {
|
|
341
|
+
rule: "range-exceeds-file",
|
|
342
|
+
message: `citation exceeds file length (${lineCount} line(s))`,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Reads a resolved target's content, or reports why it couldn't be read
|
|
349
|
+
* (permission denied, and similar OS-level failures short of the file not
|
|
350
|
+
* existing at all -- `resolveCitation` already confirmed the target exists
|
|
351
|
+
* via `isFile`/`fs.statSync`, which does not require read permission).
|
|
352
|
+
* Returned as a `Problem` with `rule: "unreadable-target"` and `code` set to
|
|
353
|
+
* the OS error code, so callers can route it to a `notice`-severity finding
|
|
354
|
+
* (see pushUnreadable) instead of the usual `warning`-severity drift finding
|
|
355
|
+
* -- an unreadable file is not evidence the citation itself is wrong.
|
|
356
|
+
*/
|
|
357
|
+
function readTarget(resolvedPath) {
|
|
358
|
+
try {
|
|
359
|
+
return { content: fs.readFileSync(resolvedPath, "utf8") };
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
const code = err.code ?? "UNKNOWN";
|
|
363
|
+
return {
|
|
364
|
+
rule: "unreadable-target",
|
|
365
|
+
message: "target file exists but could not be read",
|
|
366
|
+
code,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function checkTarget(citedPath, startLine, endLine, resolvedPath) {
|
|
371
|
+
const read = readTarget(resolvedPath);
|
|
372
|
+
if ("rule" in read)
|
|
373
|
+
return read;
|
|
374
|
+
const content = read.content;
|
|
375
|
+
const lines = splitLines(content);
|
|
376
|
+
const lineCount = lines.length;
|
|
377
|
+
const bound = checkRangeBound(startLine, endLine, lineCount);
|
|
378
|
+
if (bound)
|
|
379
|
+
return bound;
|
|
380
|
+
const startText = lines[startLine - 1] ?? "";
|
|
381
|
+
const trimmed = startText.trim();
|
|
382
|
+
if (trimmed === "") {
|
|
383
|
+
return { rule: "blank-start-line", message: "start line is blank" };
|
|
384
|
+
}
|
|
385
|
+
const ext = (citedPath.split(".").pop() ?? "").toLowerCase();
|
|
386
|
+
if (CLOSING_ONLY_EXTS.has(ext) && CLOSING_BRACE_RE.test(trimmed)) {
|
|
387
|
+
return {
|
|
388
|
+
rule: "closing-brace-start-line",
|
|
389
|
+
message: `start line is only a closing brace/bracket ("${trimmed}")`,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
// A cont-ext atom only ever extends the *end* of a range whose start line
|
|
395
|
+
// was already fully checked (blank / closing-brace) when it was cited as
|
|
396
|
+
// its own full citation or cont-fresh atom -- re-running checkTarget here
|
|
397
|
+
// would re-derive that same start-line check against the identical line
|
|
398
|
+
// and, on a real drift, double-report it as a second finding. This checks
|
|
399
|
+
// only whether the (possibly inverted, possibly out-of-file) range itself
|
|
400
|
+
// is sound.
|
|
401
|
+
function checkRangeBoundOnly(startLine, endLine, resolvedPath) {
|
|
402
|
+
const read = readTarget(resolvedPath);
|
|
403
|
+
if ("rule" in read)
|
|
404
|
+
return read;
|
|
405
|
+
const lineCount = splitLines(read.content).length;
|
|
406
|
+
return checkRangeBound(startLine, endLine, lineCount);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Collects every continuation-citation atom (see the "Continuation
|
|
410
|
+
* citations" doc block above) in `content`, sorted by document position.
|
|
411
|
+
*
|
|
412
|
+
* Each atom is tagged with a role:
|
|
413
|
+
* - "cont-fresh": establishes a new start line (optionally with its own
|
|
414
|
+
* embedded end, e.g. `` `:75-78` ``), checked the same way as a full
|
|
415
|
+
* citation's start (blank / closing-brace / range-exceeds).
|
|
416
|
+
* - "cont-ext": purely extends the *end* of whatever start line came
|
|
417
|
+
* immediately before it (a `` -`M` `` / `` –`M` `` tail, or a
|
|
418
|
+
* `` `:M` `` directly preceded by a `-`/`–`). Ending a range on a
|
|
419
|
+
* closing brace is completely normal, so an extension is checked ONLY
|
|
420
|
+
* for the range-bound checks (inverted-range, range-exceeds-file),
|
|
421
|
+
* never blank/closing-brace.
|
|
422
|
+
* A colon-form match (`` `:N` ``) is "cont-ext" exactly when the nearest
|
|
423
|
+
* non-whitespace character before its opening backtick is `-` or `–`;
|
|
424
|
+
* otherwise it is "cont-fresh". Dash-form (`` -`M` ``/`` –`M` ``) is
|
|
425
|
+
* always "cont-ext" by construction. Paren-form (`` (`N`) ``) is always
|
|
426
|
+
* "cont-fresh".
|
|
427
|
+
*/
|
|
428
|
+
function collectContinuationAtoms(content) {
|
|
429
|
+
const atoms = [];
|
|
430
|
+
const colonRe = new RegExp(CONT_COLON_RE.source, "g");
|
|
431
|
+
let m;
|
|
432
|
+
while ((m = colonRe.exec(content)) !== null) {
|
|
433
|
+
const before = content.slice(0, m.index).trimEnd();
|
|
434
|
+
if (/[-–]$/.test(before)) {
|
|
435
|
+
atoms.push({
|
|
436
|
+
kind: "cont-ext",
|
|
437
|
+
index: m.index,
|
|
438
|
+
value: m[2] ? Number(m[2]) : Number(m[1]),
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
atoms.push({
|
|
443
|
+
kind: "cont-fresh",
|
|
444
|
+
index: m.index,
|
|
445
|
+
startLine: Number(m[1]),
|
|
446
|
+
endLine: m[2] ? Number(m[2]) : null,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const dashRe = new RegExp(CONT_DASH_RE.source, "g");
|
|
451
|
+
while ((m = dashRe.exec(content)) !== null) {
|
|
452
|
+
atoms.push({ kind: "cont-ext", index: m.index, value: Number(m[1]) });
|
|
453
|
+
}
|
|
454
|
+
const parenRe = new RegExp(CONT_PAREN_RE.source, "g");
|
|
455
|
+
while ((m = parenRe.exec(content)) !== null) {
|
|
456
|
+
atoms.push({
|
|
457
|
+
kind: "cont-fresh",
|
|
458
|
+
index: m.index,
|
|
459
|
+
startLine: Number(m[1]),
|
|
460
|
+
endLine: null,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
return atoms;
|
|
464
|
+
}
|
|
465
|
+
function pushDrift(findings, file, citation, rule, message, resolvedTo) {
|
|
466
|
+
findings.push({
|
|
467
|
+
ruleId: RULE_ID,
|
|
468
|
+
severity: "warning",
|
|
469
|
+
file,
|
|
470
|
+
message: `\`${citation}\`: ${message} [${rule}]`,
|
|
471
|
+
...(resolvedTo ? { detail: `resolvedTo: ${resolvedTo}` } : {}),
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
function pushAmbiguous(findings, file, citation, candidates) {
|
|
475
|
+
findings.push({
|
|
476
|
+
ruleId: RULE_ID,
|
|
477
|
+
severity: "notice",
|
|
478
|
+
file,
|
|
479
|
+
message: `\`${citation}\`: ambiguous target, not evaluated [unresolved-ambiguous]`,
|
|
480
|
+
detail: `candidates: ${candidates.join(", ")}`,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
function pushUnreadable(findings, file, citation, resolvedTo, code) {
|
|
484
|
+
findings.push({
|
|
485
|
+
ruleId: RULE_ID,
|
|
486
|
+
severity: "notice",
|
|
487
|
+
file,
|
|
488
|
+
message: `\`${citation}\`: target file exists but could not be read [unreadable-target]`,
|
|
489
|
+
detail: `resolvedTo: ${resolvedTo}, errorCode: ${code}`,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* True when a `CITATION_RE` match at `matchIndex` is the phantom tail of a
|
|
494
|
+
* filename hard-wrapped across a line break (see the "Hard-wrapped prose"
|
|
495
|
+
* doc block above): the match starts at column 0 of its line, optionally
|
|
496
|
+
* after only whitespace or a list/quote marker, and the previous line ends
|
|
497
|
+
* with `-` or `–`.
|
|
498
|
+
*/
|
|
499
|
+
function isWrappedPathContinuation(content, matchIndex) {
|
|
500
|
+
const lineStart = content.lastIndexOf("\n", matchIndex - 1) + 1;
|
|
501
|
+
if (lineStart === 0)
|
|
502
|
+
return false; // no previous line to have wrapped from
|
|
503
|
+
const prefix = content.slice(lineStart, matchIndex);
|
|
504
|
+
if (!/^[\s>*.\d-]*$/.test(prefix))
|
|
505
|
+
return false;
|
|
506
|
+
const prevLineEnd = lineStart - 1; // index of the newline just before lineStart
|
|
507
|
+
const prevLineStart = content.lastIndexOf("\n", prevLineEnd - 1) + 1;
|
|
508
|
+
const prevLine = content.slice(prevLineStart, prevLineEnd);
|
|
509
|
+
return /[-–]$/.test(prevLine);
|
|
510
|
+
}
|
|
511
|
+
function scanDoc(cache, root, bundleDir, doc) {
|
|
512
|
+
const findings = [];
|
|
513
|
+
const content = doc.raw;
|
|
514
|
+
const sources = getValidSources(doc.frontmatter.parsed) ?? [];
|
|
515
|
+
const docAbsPath = path.join(bundleDir, doc.relPath);
|
|
516
|
+
const fullAtoms = [];
|
|
517
|
+
const re = new RegExp(CITATION_RE.source, "g");
|
|
518
|
+
let m;
|
|
519
|
+
while ((m = re.exec(content)) !== null) {
|
|
520
|
+
if (isWrappedPathContinuation(content, m.index))
|
|
521
|
+
continue;
|
|
522
|
+
fullAtoms.push({
|
|
523
|
+
kind: "full",
|
|
524
|
+
index: m.index,
|
|
525
|
+
citedPath: m[1],
|
|
526
|
+
startLine: Number(m[2]),
|
|
527
|
+
endLine: m[3] ? Number(m[3]) : null,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
const atoms = [...fullAtoms, ...collectContinuationAtoms(content)].sort((a, b) => a.index - b.index);
|
|
531
|
+
// `governing`: nearest preceding citation (full or continuation) that
|
|
532
|
+
// resolved to a real file; see the "Continuation citations" doc block
|
|
533
|
+
// above for the reset rules. `lastStartLine`: the start line a "cont-ext"
|
|
534
|
+
// atom extends into a range; tracks the most recent full or cont-fresh
|
|
535
|
+
// atom's own startLine, scoped together with `governing`.
|
|
536
|
+
let governing = null;
|
|
537
|
+
let lastStartLine = null;
|
|
538
|
+
for (const atom of atoms) {
|
|
539
|
+
if (atom.kind === "cont-ext") {
|
|
540
|
+
if (!governing || lastStartLine === null)
|
|
541
|
+
continue; // nothing to extend
|
|
542
|
+
const citation = `${governing.citedPath}:${lastStartLine}-${atom.value} (continuation)`;
|
|
543
|
+
const problem = checkRangeBoundOnly(lastStartLine, atom.value, governing.resolvedPath);
|
|
544
|
+
if (problem?.rule === "unreadable-target") {
|
|
545
|
+
pushUnreadable(findings, doc.relPath, citation, path.relative(root, governing.resolvedPath), problem.code ?? "UNKNOWN");
|
|
546
|
+
}
|
|
547
|
+
else if (problem) {
|
|
548
|
+
pushDrift(findings, doc.relPath, citation, problem.rule, problem.message, path.relative(root, governing.resolvedPath));
|
|
549
|
+
}
|
|
550
|
+
continue; // governing and lastStartLine both carry over unchanged
|
|
551
|
+
}
|
|
552
|
+
if (atom.kind === "cont-fresh") {
|
|
553
|
+
if (!governing)
|
|
554
|
+
continue; // nothing to validate a bare continuation against
|
|
555
|
+
const { startLine, endLine } = atom;
|
|
556
|
+
const citation = `${governing.citedPath}:${startLine}${endLine ? "-" + endLine : ""} (continuation)`;
|
|
557
|
+
const problem = checkTarget(governing.citedPath, startLine, endLine, governing.resolvedPath);
|
|
558
|
+
if (problem?.rule === "unreadable-target") {
|
|
559
|
+
pushUnreadable(findings, doc.relPath, citation, path.relative(root, governing.resolvedPath), problem.code ?? "UNKNOWN");
|
|
560
|
+
}
|
|
561
|
+
else if (problem) {
|
|
562
|
+
pushDrift(findings, doc.relPath, citation, problem.rule, problem.message, path.relative(root, governing.resolvedPath));
|
|
563
|
+
}
|
|
564
|
+
lastStartLine = startLine;
|
|
565
|
+
continue; // governing (same file) carries over unchanged
|
|
566
|
+
}
|
|
567
|
+
const { citedPath, startLine, endLine } = atom;
|
|
568
|
+
const citation = `${citedPath}:${startLine}${endLine ? "-" + endLine : ""}`;
|
|
569
|
+
if (hasParentSegment(citedPath)) {
|
|
570
|
+
pushDrift(findings, doc.relPath, citation, "path-traversal-rejected", `citedPath contains a ".." segment and was rejected without resolving: ${citedPath}`);
|
|
571
|
+
governing = null;
|
|
572
|
+
lastStartLine = null;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const resolution = resolveCitation(cache, root, docAbsPath, content, sources, citedPath, atom.index);
|
|
576
|
+
if (!resolution) {
|
|
577
|
+
pushDrift(findings, doc.relPath, citation, "missing-file", `could not resolve ${citedPath}: tried doc sources, ancestor climb (bare filenames only), repo-root, doc-relative, nearest prior qualified mention, repo-wide search; no candidate file exists`);
|
|
578
|
+
governing = null;
|
|
579
|
+
lastStartLine = null;
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
if ("skip" in resolution) {
|
|
583
|
+
governing = null;
|
|
584
|
+
lastStartLine = null;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
if ("ambiguous" in resolution) {
|
|
588
|
+
pushAmbiguous(findings, doc.relPath, citation, resolution.candidates);
|
|
589
|
+
governing = null;
|
|
590
|
+
lastStartLine = null;
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
const problem = checkTarget(citedPath, startLine, endLine, resolution.path);
|
|
594
|
+
if (problem?.rule === "unreadable-target") {
|
|
595
|
+
pushUnreadable(findings, doc.relPath, citation, path.relative(root, resolution.path), problem.code ?? "UNKNOWN");
|
|
596
|
+
}
|
|
597
|
+
else if (problem) {
|
|
598
|
+
pushDrift(findings, doc.relPath, citation, problem.rule, problem.message, path.relative(root, resolution.path));
|
|
599
|
+
}
|
|
600
|
+
governing = { citedPath, resolvedPath: resolution.path };
|
|
601
|
+
lastStartLine = startLine;
|
|
602
|
+
}
|
|
603
|
+
return findings;
|
|
604
|
+
}
|
|
605
|
+
export const citationsResolveRule = {
|
|
606
|
+
id: RULE_ID,
|
|
607
|
+
description: "`path:N`/`path:N-M` citations (and their `:N`, -`M`/–`M`, (`N`) continuations) must resolve to a real target file and land on real, non-blank content. Mechanical only: does not verify the cited line is semantically correct.",
|
|
608
|
+
run(ctx) {
|
|
609
|
+
if (!ctx.repoRoot) {
|
|
610
|
+
// Never silently skip: mirrors sources-fresh's posture so a "clean"
|
|
611
|
+
// run is never a fake pass when there is no filesystem tree to
|
|
612
|
+
// resolve citation targets against.
|
|
613
|
+
return [
|
|
614
|
+
{
|
|
615
|
+
ruleId: RULE_ID,
|
|
616
|
+
severity: "notice",
|
|
617
|
+
file: "",
|
|
618
|
+
message: "citation resolution skipped: not inside a git work tree",
|
|
619
|
+
},
|
|
620
|
+
];
|
|
621
|
+
}
|
|
622
|
+
const root = ctx.repoRoot;
|
|
623
|
+
// Fresh per invocation: see findByBasename's doc comment for why this
|
|
624
|
+
// is not held at module scope.
|
|
625
|
+
const cache = new Map();
|
|
626
|
+
return ctx.docs.flatMap((doc) => scanDoc(cache, root, ctx.bundleDir, doc));
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
//# sourceMappingURL=citations-resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"citations-resolve.js","sourceRoot":"","sources":["../../src/rules/citations-resolve.ts"],"names":[],"mappings":"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"}
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -5,5 +5,6 @@ import { linksResolveRule } from "./links-resolve.js";
|
|
|
5
5
|
import { noAbsoluteLinksRule } from "./no-absolute-links.js";
|
|
6
6
|
import { sourcesShapeRule } from "./sources-shape.js";
|
|
7
7
|
import { sourcesFreshRule } from "./sources-fresh.js";
|
|
8
|
+
import { citationsResolveRule } from "./citations-resolve.js";
|
|
8
9
|
export declare const allRules: Rule[];
|
|
9
|
-
export { frontmatterRequiredRule, reservedFilesBareRule, linksResolveRule, noAbsoluteLinksRule, sourcesShapeRule, sourcesFreshRule, };
|
|
10
|
+
export { frontmatterRequiredRule, reservedFilesBareRule, linksResolveRule, noAbsoluteLinksRule, sourcesShapeRule, sourcesFreshRule, citationsResolveRule, };
|
package/dist/rules/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { linksResolveRule } from "./links-resolve.js";
|
|
|
4
4
|
import { noAbsoluteLinksRule } from "./no-absolute-links.js";
|
|
5
5
|
import { sourcesShapeRule } from "./sources-shape.js";
|
|
6
6
|
import { sourcesFreshRule } from "./sources-fresh.js";
|
|
7
|
+
import { citationsResolveRule } from "./citations-resolve.js";
|
|
7
8
|
export const allRules = [
|
|
8
9
|
frontmatterRequiredRule,
|
|
9
10
|
reservedFilesBareRule,
|
|
@@ -11,6 +12,7 @@ export const allRules = [
|
|
|
11
12
|
noAbsoluteLinksRule,
|
|
12
13
|
sourcesShapeRule,
|
|
13
14
|
sourcesFreshRule,
|
|
15
|
+
citationsResolveRule,
|
|
14
16
|
];
|
|
15
|
-
export { frontmatterRequiredRule, reservedFilesBareRule, linksResolveRule, noAbsoluteLinksRule, sourcesShapeRule, sourcesFreshRule, };
|
|
17
|
+
export { frontmatterRequiredRule, reservedFilesBareRule, linksResolveRule, noAbsoluteLinksRule, sourcesShapeRule, sourcesFreshRule, citationsResolveRule, };
|
|
16
18
|
//# sourceMappingURL=index.js.map
|
package/dist/rules/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,CAAC,MAAM,QAAQ,GAAW;IAC9B,uBAAuB;IACvB,qBAAqB;IACrB,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;IAChB,gBAAgB;IAChB,oBAAoB;CACrB,CAAC;AAEF,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,CAAC"}
|