@refrakt-md/content 0.36.0 → 0.38.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.
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Project-configured exclusions for the edge index (SPEC-136, WORK-596).
3
+ *
4
+ * **Nothing here is a default.** Every folder convention this repository
5
+ * happens to use — `docs/migration/`, a `blog/` directory, a `CHANGELOG.md` at
6
+ * the root — is this repository's, and a tool that bakes them in is a tool
7
+ * built for one site. Both lists start empty and a project says what its own
8
+ * structure means.
9
+ *
10
+ * The two lists are deliberately not one. See {@link StaleConfig}.
11
+ */
12
+ /** Everything `refrakt stale` needs from a project's configuration. */
13
+ export interface StaleSettings {
14
+ /** Content roots to scan, repo-root-relative POSIX. */
15
+ contentDirs: string[];
16
+ /** Referrer globs — see {@link StaleConfig.archival}. */
17
+ archival: string[];
18
+ /** Target globs — see {@link StaleConfig.generated}. */
19
+ generated: string[];
20
+ }
21
+ /** Raised when the project's configuration cannot be read. Distinct from
22
+ * "read it, nothing to exclude": a missing config means we do not know where
23
+ * content lives, and guessing would measure the wrong tree. */
24
+ export declare class StaleConfigRefusal extends Error {
25
+ }
26
+ /**
27
+ * Translate one glob to a regular expression.
28
+ *
29
+ * The subset is the familiar one and nothing more: `*` within a path segment,
30
+ * `**` across segments, `?` for a single character, and a trailing `/` naming a
31
+ * directory and everything beneath it. No brace expansion, no negation — a
32
+ * pattern language that can express "everything except" invites the exclusion
33
+ * list to grow into a second content model.
34
+ */
35
+ export declare function globToRegExp(glob: string): RegExp;
36
+ /** Compile a list of globs into a single predicate. An empty list matches
37
+ * nothing — never everything. */
38
+ export declare function globMatcher(globs: string[] | undefined): (path: string) => boolean;
39
+ /**
40
+ * Read a project's `refrakt.config.json` for everything the staleness report
41
+ * needs: where content lives, and what it must not measure.
42
+ *
43
+ * Content roots come from the sites the project declares, so a multi-site repo
44
+ * is scanned whole and a repo that puts content somewhere unusual needs no flag.
45
+ */
46
+ export declare function resolveStaleSettings(repoRoot: string): StaleSettings;
47
+ //# sourceMappingURL=exclude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exclude.d.ts","sourceRoot":"","sources":["../../src/edges/exclude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC7B,uDAAuD;IACvD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wDAAwD;IACxD,SAAS,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;gEAEgE;AAChE,qBAAa,kBAAmB,SAAQ,KAAK;CAAG;AAWhD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4BjD;AAED;kCACkC;AAClC,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAIlF;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CA0CpE"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Project-configured exclusions for the edge index (SPEC-136, WORK-596).
3
+ *
4
+ * **Nothing here is a default.** Every folder convention this repository
5
+ * happens to use — `docs/migration/`, a `blog/` directory, a `CHANGELOG.md` at
6
+ * the root — is this repository's, and a tool that bakes them in is a tool
7
+ * built for one site. Both lists start empty and a project says what its own
8
+ * structure means.
9
+ *
10
+ * The two lists are deliberately not one. See {@link StaleConfig}.
11
+ */
12
+ import { existsSync, readFileSync } from 'node:fs';
13
+ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
14
+ /** Raised when the project's configuration cannot be read. Distinct from
15
+ * "read it, nothing to exclude": a missing config means we do not know where
16
+ * content lives, and guessing would measure the wrong tree. */
17
+ export class StaleConfigRefusal extends Error {
18
+ }
19
+ function toPosix(path) {
20
+ return path.split(sep).join('/');
21
+ }
22
+ function repoRelative(path, repoRoot) {
23
+ const abs = isAbsolute(path) ? path : resolve(repoRoot, path);
24
+ return toPosix(relative(repoRoot, abs)).replace(/\/$/, '');
25
+ }
26
+ /**
27
+ * Translate one glob to a regular expression.
28
+ *
29
+ * The subset is the familiar one and nothing more: `*` within a path segment,
30
+ * `**` across segments, `?` for a single character, and a trailing `/` naming a
31
+ * directory and everything beneath it. No brace expansion, no negation — a
32
+ * pattern language that can express "everything except" invites the exclusion
33
+ * list to grow into a second content model.
34
+ */
35
+ export function globToRegExp(glob) {
36
+ const pattern = glob.endsWith('/') ? `${glob}**` : glob;
37
+ let out = '';
38
+ for (let i = 0; i < pattern.length; i++) {
39
+ const ch = pattern[i];
40
+ if (ch === '*') {
41
+ if (pattern[i + 1] === '*') {
42
+ i++;
43
+ if (pattern[i + 1] === '/') {
44
+ i++;
45
+ // `**/` spans zero or more whole segments, so `a/**/b.md`
46
+ // matches `a/b.md` as well as `a/c/d/b.md`.
47
+ out += '(?:[^/]*/)*';
48
+ }
49
+ else {
50
+ out += '.*';
51
+ }
52
+ }
53
+ else {
54
+ out += '[^/]*';
55
+ }
56
+ }
57
+ else if (ch === '?') {
58
+ out += '[^/]';
59
+ }
60
+ else if (/[.+^${}()|[\]\\]/.test(ch)) {
61
+ out += `\\${ch}`;
62
+ }
63
+ else {
64
+ out += ch;
65
+ }
66
+ }
67
+ return new RegExp(`^${out}$`);
68
+ }
69
+ /** Compile a list of globs into a single predicate. An empty list matches
70
+ * nothing — never everything. */
71
+ export function globMatcher(globs) {
72
+ if (!globs || globs.length === 0)
73
+ return () => false;
74
+ const compiled = globs.map(globToRegExp);
75
+ return (path) => compiled.some((re) => re.test(path));
76
+ }
77
+ /**
78
+ * Read a project's `refrakt.config.json` for everything the staleness report
79
+ * needs: where content lives, and what it must not measure.
80
+ *
81
+ * Content roots come from the sites the project declares, so a multi-site repo
82
+ * is scanned whole and a repo that puts content somewhere unusual needs no flag.
83
+ */
84
+ export function resolveStaleSettings(repoRoot) {
85
+ const configPath = join(repoRoot, 'refrakt.config.json');
86
+ if (!existsSync(configPath)) {
87
+ throw new StaleConfigRefusal(`no refrakt.config.json in ${repoRoot} — refrakt stale reads it to learn where content lives`);
88
+ }
89
+ let raw;
90
+ try {
91
+ raw = JSON.parse(readFileSync(configPath, 'utf8'));
92
+ }
93
+ catch (err) {
94
+ throw new StaleConfigRefusal(`could not parse ${configPath}: ${err.message}`);
95
+ }
96
+ // All three config shapes, read directly rather than through the
97
+ // normalizer: this needs one field from each site and must not depend on
98
+ // a plugin resolving or a theme existing.
99
+ const sites = [];
100
+ if (raw.sites)
101
+ sites.push(...Object.values(raw.sites));
102
+ if (raw.site)
103
+ sites.push(raw.site);
104
+ if (raw.contentDir)
105
+ sites.push({ contentDir: raw.contentDir });
106
+ const contentDirs = [];
107
+ for (const site of sites) {
108
+ if (!site?.contentDir)
109
+ continue;
110
+ const dir = repoRelative(site.contentDir, repoRoot);
111
+ if (dir && !dir.startsWith('..') && !contentDirs.includes(dir))
112
+ contentDirs.push(dir);
113
+ }
114
+ if (contentDirs.length === 0) {
115
+ throw new StaleConfigRefusal(`${configPath} declares no site with a contentDir — nothing to scan`);
116
+ }
117
+ const stale = raw.stale ?? {};
118
+ return {
119
+ contentDirs,
120
+ archival: stale.archival ?? [],
121
+ generated: stale.generated ?? [],
122
+ };
123
+ }
124
+ //# sourceMappingURL=exclude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exclude.js","sourceRoot":"","sources":["../../src/edges/exclude.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Edge extraction (SPEC-136, WORK-594).
3
+ *
4
+ * Documentation already declares edges into the repository. Indexing them costs
5
+ * nothing per reference and needs no new syntax, no marker, and no change to a
6
+ * single existing page — which is the whole argument for this being the cheap,
7
+ * wide signal underneath {@link SPEC-134}'s precise one.
8
+ *
9
+ * Extraction reads **Markdoc source**, not a resolved tree. That is deliberate:
10
+ * the index has to answer for `snippet path=` attributes exactly as authored,
11
+ * and `touching` must work without a build.
12
+ */
13
+ /** Which extractor produced an edge. Carried so the report can state a base
14
+ * rate per class, and so `--class` can narrow. */
15
+ export type EdgeClass = 'declared' | 'embedded' | 'described-link' | 'prose';
16
+ export interface Edge {
17
+ /** Page that makes the claim, repo-root-relative POSIX. */
18
+ referrer: string;
19
+ /** 1-indexed line of the claim within the referrer. */
20
+ line: number;
21
+ /** What is being referred to — a repo path, or a content page. */
22
+ target: string;
23
+ class: EdgeClass;
24
+ /** Whether a SPEC-134 `reviewed` marker is attached to this reference.
25
+ * Undefined while WORK-591 has not landed — see D3. */
26
+ reviewed?: string;
27
+ /** True when an attached marker no longer certifies the target's current
28
+ * content. A marker outranks a commit count (D3) — but only while it is
29
+ * true. A stale one is a statement that has expired, so the edge ranks
30
+ * normally instead of being suppressed by it. */
31
+ markerStale?: boolean;
32
+ /** The invocation's raw attributes, kept so the marker can be checked
33
+ * against the target. Embedded edges only, and stripped before the edge
34
+ * is published. */
35
+ attrs?: string;
36
+ }
37
+ /**
38
+ * Embedded-source edges — `snippet`, `file-ref` and `expand` `path=`.
39
+ *
40
+ * The class whose extraction is already trustworthy, which is why WORK-594
41
+ * establishes the index and the output shape against it even though it reaches
42
+ * only a fraction of the corpus. WORK-595 is where the rest is reached.
43
+ */
44
+ export declare function extractEmbeddedEdges(referrer: string, source: string): Edge[];
45
+ /**
46
+ * Deduplicate to one edge per referrer + target.
47
+ *
48
+ * A page quoting one file five times makes one claim about it, not five — and
49
+ * a page that *declares* a file and also mentions it in prose is still making
50
+ * one claim. Keying on the class as well produced duplicate rows in the ranked
51
+ * report the moment the first `documents:` entry was added, because the same
52
+ * page reached the same target two ways.
53
+ *
54
+ * The surviving edge is the highest-precision one, so a declared edge is never
55
+ * displaced by a prose mention of the same file. Ties break on the earliest
56
+ * line.
57
+ */
58
+ export declare function dedupeEdges(edges: Edge[]): Edge[];
59
+ /** An error raised by a *declared* edge — D14's asymmetry. */
60
+ export interface DeclaredEdgeError {
61
+ referrer: string;
62
+ entry: string;
63
+ message: string;
64
+ }
65
+ /**
66
+ * Declared edges — the `documents:` frontmatter field (D13).
67
+ *
68
+ * The highest-precision class, because there is no extraction heuristic to be
69
+ * wrong. It exists because a page's subject is not always in its words: 209 of
70
+ * 237 pages in this repository name no file at all.
71
+ *
72
+ * **A declared path that resolves to nothing is an error** (D14). D7 skips an
73
+ * unresolvable *mention* because the likeliest explanation is that the
74
+ * extractor misfired on something that was never a path. A `documents` entry
75
+ * has no such excuse — the author asserted the relationship, so a miss means
76
+ * the file moved and the declaration did not. Without this, the mechanism
77
+ * designed to catch rot would quietly accumulate its own.
78
+ *
79
+ * No cascade from a `_layout.md`: a layout declaring what it documents would
80
+ * attribute that claim to every page beneath it, which is precisely the
81
+ * over-broad edge the prose class already risks.
82
+ */
83
+ export declare function extractDeclaredEdges(referrer: string, frontmatter: {
84
+ documents?: unknown;
85
+ }, exists: (path: string) => boolean): {
86
+ edges: Edge[];
87
+ errors: DeclaredEdgeError[];
88
+ };
89
+ /**
90
+ * Described-link edges — an internal link carrying an adjacent description
91
+ * (D16).
92
+ *
93
+ * A link says *go here*. It asserts nothing, and `site/content` carries 748
94
+ * internal links, overwhelmingly navigation. But **an overview page listing its
95
+ * children with one-line summaries is duplicating the target's content**, and
96
+ * the duplicate is what rots. The link identifies which target is being
97
+ * summarised; the *description* is the claim.
98
+ *
99
+ * Two shapes, both structural rather than heuristic. Nothing else counts — not
100
+ * a link in a paragraph, not a bare list item, not a nav entry.
101
+ *
102
+ * `{% ref %}` / `{% xref %}` entity links produce nothing here: extraction
103
+ * reads Markdoc source rather than resolved hrefs, so they never look like
104
+ * Markdown links in the first place.
105
+ */
106
+ export declare function extractDescribedLinkEdges(referrer: string, source: string, resolvePage: (href: string) => string | undefined): Edge[];
107
+ /**
108
+ * Prose path mentions — a backticked repo-relative path in body text (D7).
109
+ *
110
+ * The highest-yield class in the spec's measurement and the one with the worst
111
+ * precision: a backticked path in "formatting is owned by `biome.jsonc`" is a
112
+ * mention, not a documented claim. The design accepts that, because D1 removes
113
+ * any consequence from a false positive and D9 bounds the output — a mention
114
+ * that ranks highly costs a reader three seconds.
115
+ *
116
+ * Extraction is deliberately narrow: a backticked path that **resolves to a
117
+ * file that exists today**. Unbackticked paths, globs and paths that no longer
118
+ * exist are all skipped. A path that no longer exists is a different and louder
119
+ * defect, and belongs to link checking rather than here.
120
+ */
121
+ export declare function extractProseEdges(referrer: string, source: string, exists: (path: string) => boolean): Edge[];
122
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/edges/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;mDACmD;AACnD,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,gBAAgB,GAAG,OAAO,CAAC;AAE7E,MAAM,WAAW,IAAI;IACpB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;IACjB;4DACwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;sDAGkD;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;wBAEoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AA0DD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,CA4B7E;AAeD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAejD;AAED,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,EACpC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAC/B;IAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAAE,CA2ChD;AAQD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CACxC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAC/C,IAAI,EAAE,CA2CR;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAC/B,IAAI,EAAE,CAiCR"}
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Edge extraction (SPEC-136, WORK-594).
3
+ *
4
+ * Documentation already declares edges into the repository. Indexing them costs
5
+ * nothing per reference and needs no new syntax, no marker, and no change to a
6
+ * single existing page — which is the whole argument for this being the cheap,
7
+ * wide signal underneath {@link SPEC-134}'s precise one.
8
+ *
9
+ * Extraction reads **Markdoc source**, not a resolved tree. That is deliberate:
10
+ * the index has to answer for `snippet path=` attributes exactly as authored,
11
+ * and `touching` must work without a build.
12
+ */
13
+ import { BASE_LANGUAGES, maskSource } from '@refrakt-md/runes';
14
+ /** Runes that carry a `path=` into the repository. */
15
+ const EMBEDDED_RUNES = ['snippet', 'file-ref', 'expand'];
16
+ /**
17
+ * Blank fenced blocks and inline code spans before extracting.
18
+ *
19
+ * A `{% snippet path="…" %}` inside a ```` ```markdoc ```` fence is an
20
+ * *example* of the syntax, not a reference to that file — the `snippet` doc
21
+ * page is full of them. Counting those inflates the index with claims nobody
22
+ * made, and on this corpus it was 4 of 10.
23
+ *
24
+ * Reuses SPEC-131's Markdown masker (WORK-588) rather than re-deriving fence
25
+ * tracking. Masking preserves line offsets, so the line numbers reported are
26
+ * still the real ones.
27
+ */
28
+ function withoutExamples(source) {
29
+ return maskSource(source, BASE_LANGUAGES.markdoc);
30
+ }
31
+ /**
32
+ * Blank fenced blocks only, leaving inline code spans intact.
33
+ *
34
+ * The prose class extracts **backticked** paths, and the full Markdown mask
35
+ * blanks inline code spans — so masking both would make that extractor find
36
+ * nothing at all. A fenced block is still an example and must go; an inline
37
+ * span is where the path lives.
38
+ *
39
+ * Line offsets are preserved, so reported line numbers stay real.
40
+ */
41
+ function withoutFences(source) {
42
+ const fences = BASE_LANGUAGES.markdoc.fences;
43
+ let open;
44
+ return source
45
+ .split('\n')
46
+ .map((line) => {
47
+ const trimmed = line.trimStart();
48
+ if (open !== undefined) {
49
+ if (trimmed.startsWith(open)) {
50
+ open = undefined;
51
+ return line;
52
+ }
53
+ return ' '.repeat(line.length);
54
+ }
55
+ const fence = fences.find((f) => trimmed.startsWith(f));
56
+ if (fence !== undefined) {
57
+ open = fence;
58
+ return line;
59
+ }
60
+ return line;
61
+ })
62
+ .join('\n');
63
+ }
64
+ /**
65
+ * Embedded-source edges — `snippet`, `file-ref` and `expand` `path=`.
66
+ *
67
+ * The class whose extraction is already trustworthy, which is why WORK-594
68
+ * establishes the index and the output shape against it even though it reaches
69
+ * only a fraction of the corpus. WORK-595 is where the rest is reached.
70
+ */
71
+ export function extractEmbeddedEdges(referrer, source) {
72
+ const edges = [];
73
+ const runeAlternation = EMBEDDED_RUNES.map((r) => r.replace('-', '\\-')).join('|');
74
+ const tag = new RegExp(`\\{%\\s*(${runeAlternation})\\b([^%]*)%\\}`, 'g');
75
+ withoutExamples(source)
76
+ .split('\n')
77
+ .forEach((line, i) => {
78
+ tag.lastIndex = 0;
79
+ let m = tag.exec(line);
80
+ while (m !== null) {
81
+ const attrs = m[2];
82
+ const path = /\bpath\s*=\s*"([^"]+)"/.exec(attrs)?.[1];
83
+ if (path) {
84
+ const reviewed = /\breviewed\s*=\s*"([^"]+)"/.exec(attrs)?.[1];
85
+ edges.push({
86
+ referrer,
87
+ line: i + 1,
88
+ target: path,
89
+ class: 'embedded',
90
+ ...(reviewed ? { reviewed, attrs } : {}),
91
+ });
92
+ }
93
+ m = tag.exec(line);
94
+ }
95
+ });
96
+ return edges;
97
+ }
98
+ /**
99
+ * Precision order, highest first — the spec's own table.
100
+ *
101
+ * `declared` wins because an author chose the file; `prose` loses because a
102
+ * backticked path may be a name-drop.
103
+ */
104
+ const PRECISION = {
105
+ declared: 0,
106
+ embedded: 1,
107
+ 'described-link': 2,
108
+ prose: 3,
109
+ };
110
+ /**
111
+ * Deduplicate to one edge per referrer + target.
112
+ *
113
+ * A page quoting one file five times makes one claim about it, not five — and
114
+ * a page that *declares* a file and also mentions it in prose is still making
115
+ * one claim. Keying on the class as well produced duplicate rows in the ranked
116
+ * report the moment the first `documents:` entry was added, because the same
117
+ * page reached the same target two ways.
118
+ *
119
+ * The surviving edge is the highest-precision one, so a declared edge is never
120
+ * displaced by a prose mention of the same file. Ties break on the earliest
121
+ * line.
122
+ */
123
+ export function dedupeEdges(edges) {
124
+ const seen = new Map();
125
+ for (const edge of edges) {
126
+ const key = `${edge.referrer}\u0000${edge.target}`;
127
+ const existing = seen.get(key);
128
+ if (!existing) {
129
+ seen.set(key, edge);
130
+ continue;
131
+ }
132
+ const better = PRECISION[edge.class] < PRECISION[existing.class] ||
133
+ (edge.class === existing.class && edge.line < existing.line);
134
+ if (better)
135
+ seen.set(key, edge);
136
+ }
137
+ return [...seen.values()];
138
+ }
139
+ /**
140
+ * Declared edges — the `documents:` frontmatter field (D13).
141
+ *
142
+ * The highest-precision class, because there is no extraction heuristic to be
143
+ * wrong. It exists because a page's subject is not always in its words: 209 of
144
+ * 237 pages in this repository name no file at all.
145
+ *
146
+ * **A declared path that resolves to nothing is an error** (D14). D7 skips an
147
+ * unresolvable *mention* because the likeliest explanation is that the
148
+ * extractor misfired on something that was never a path. A `documents` entry
149
+ * has no such excuse — the author asserted the relationship, so a miss means
150
+ * the file moved and the declaration did not. Without this, the mechanism
151
+ * designed to catch rot would quietly accumulate its own.
152
+ *
153
+ * No cascade from a `_layout.md`: a layout declaring what it documents would
154
+ * attribute that claim to every page beneath it, which is precisely the
155
+ * over-broad edge the prose class already risks.
156
+ */
157
+ export function extractDeclaredEdges(referrer, frontmatter, exists) {
158
+ const raw = frontmatter.documents;
159
+ if (raw === undefined)
160
+ return { edges: [], errors: [] };
161
+ if (!Array.isArray(raw)) {
162
+ return {
163
+ edges: [],
164
+ errors: [
165
+ {
166
+ referrer,
167
+ entry: String(raw),
168
+ message: '`documents` must be a list of repo-root-relative paths',
169
+ },
170
+ ],
171
+ };
172
+ }
173
+ const edges = [];
174
+ const errors = [];
175
+ for (const entry of raw) {
176
+ if (typeof entry !== 'string' || entry.length === 0) {
177
+ errors.push({ referrer, entry: String(entry), message: 'is not a path' });
178
+ continue;
179
+ }
180
+ // Same containment as `snippet path=`: absolute paths and traversal
181
+ // escapes are rejected identically.
182
+ if (entry.startsWith('/') || /(^|\/)\.\.(\/|$)/.test(entry)) {
183
+ errors.push({
184
+ referrer,
185
+ entry,
186
+ message: 'must be repo-root-relative, with no absolute path or `..` escape',
187
+ });
188
+ continue;
189
+ }
190
+ if (!exists(entry)) {
191
+ errors.push({ referrer, entry, message: 'resolves to no existing file' });
192
+ continue;
193
+ }
194
+ edges.push({ referrer, line: 1, target: entry, class: 'declared' });
195
+ }
196
+ return { edges, errors };
197
+ }
198
+ /** A link in a table cell whose neighbouring cell carries prose. */
199
+ const TABLE_ROW = /^\s*\|(.+)\|\s*$/;
200
+ /** A list item whose link is followed by a dash and prose. */
201
+ const DESCRIBED_ITEM = /^\s*[-*]\s*\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*\S/;
202
+ const LINK = /\[([^\]]+)\]\(([^)]+)\)/;
203
+ /**
204
+ * Described-link edges — an internal link carrying an adjacent description
205
+ * (D16).
206
+ *
207
+ * A link says *go here*. It asserts nothing, and `site/content` carries 748
208
+ * internal links, overwhelmingly navigation. But **an overview page listing its
209
+ * children with one-line summaries is duplicating the target's content**, and
210
+ * the duplicate is what rots. The link identifies which target is being
211
+ * summarised; the *description* is the claim.
212
+ *
213
+ * Two shapes, both structural rather than heuristic. Nothing else counts — not
214
+ * a link in a paragraph, not a bare list item, not a nav entry.
215
+ *
216
+ * `{% ref %}` / `{% xref %}` entity links produce nothing here: extraction
217
+ * reads Markdoc source rather than resolved hrefs, so they never look like
218
+ * Markdown links in the first place.
219
+ */
220
+ export function extractDescribedLinkEdges(referrer, source, resolvePage) {
221
+ const edges = [];
222
+ withoutExamples(source)
223
+ .split('\n')
224
+ .forEach((line, i) => {
225
+ const hrefs = [];
226
+ const row = TABLE_ROW.exec(line);
227
+ if (row) {
228
+ // Split on unescaped pipes; a cell with a link counts only when
229
+ // some *other* cell carries prose.
230
+ const cells = row[1].split('|').map((c) => c.trim());
231
+ const linked = cells.filter((c) => LINK.test(c));
232
+ const prose = cells.filter((c) => !LINK.test(c) && c.length > 0);
233
+ if (linked.length > 0 && prose.length > 0) {
234
+ for (const cell of linked) {
235
+ const m = LINK.exec(cell);
236
+ if (m)
237
+ hrefs.push(m[2]);
238
+ }
239
+ }
240
+ }
241
+ else {
242
+ const item = DESCRIBED_ITEM.exec(line);
243
+ if (item)
244
+ hrefs.push(item[2]);
245
+ }
246
+ for (const href of hrefs) {
247
+ // Internal only — an external URL is not a claim about this repo.
248
+ if (/^[a-z]+:/i.test(href) || href.startsWith('#'))
249
+ continue;
250
+ // The anchor is stripped here rather than in the resolver: the
251
+ // claim is about the page, not the section, and every caller
252
+ // would otherwise have to remember to do it.
253
+ const clean = href.split('#')[0].replace(/\/$/, '');
254
+ if (clean.length === 0)
255
+ continue;
256
+ const target = resolvePage(clean);
257
+ // A described link whose target does not resolve to a content
258
+ // page is skipped, not reported: it is an inferred edge.
259
+ if (!target)
260
+ continue;
261
+ edges.push({ referrer, line: i + 1, target, class: 'described-link' });
262
+ }
263
+ });
264
+ return edges;
265
+ }
266
+ /** A backticked, repo-root-relative-looking path. */
267
+ const BACKTICKED = /`([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)`/g;
268
+ /**
269
+ * Prose path mentions — a backticked repo-relative path in body text (D7).
270
+ *
271
+ * The highest-yield class in the spec's measurement and the one with the worst
272
+ * precision: a backticked path in "formatting is owned by `biome.jsonc`" is a
273
+ * mention, not a documented claim. The design accepts that, because D1 removes
274
+ * any consequence from a false positive and D9 bounds the output — a mention
275
+ * that ranks highly costs a reader three seconds.
276
+ *
277
+ * Extraction is deliberately narrow: a backticked path that **resolves to a
278
+ * file that exists today**. Unbackticked paths, globs and paths that no longer
279
+ * exist are all skipped. A path that no longer exists is a different and louder
280
+ * defect, and belongs to link checking rather than here.
281
+ */
282
+ export function extractProseEdges(referrer, source, exists) {
283
+ const edges = [];
284
+ // Fences only: the whole point of this class is the backticked span, which
285
+ // the full Markdown mask would blank.
286
+ withoutFences(source)
287
+ .split('\n')
288
+ .forEach((line, i) => {
289
+ BACKTICKED.lastIndex = 0;
290
+ let m = BACKTICKED.exec(line);
291
+ while (m !== null) {
292
+ const candidate = m[1];
293
+ m = BACKTICKED.exec(line);
294
+ if (candidate.startsWith('/') || candidate.includes('..'))
295
+ continue;
296
+ // **A bare root-level filename is not a path claim.** The first
297
+ // run over `site/content` put four near-identical rows in the
298
+ // top ten — `refrakt.config.json`, mentioned in passing by four
299
+ // unrelated pages and changing for reasons none of them are
300
+ // about. A root config file is the shape of D7's own false
301
+ // positive ("formatting is owned by `biome.jsonc`"): mentioned
302
+ // everywhere, documented nowhere. Requiring a directory
303
+ // separator is what distinguishes a deliberate pointer from a
304
+ // name-drop.
305
+ if (!candidate.includes('/'))
306
+ continue;
307
+ // Resolving to a file that exists today is the rest of the
308
+ // filter. A path that no longer exists is a different and
309
+ // louder defect and belongs to link checking, not here.
310
+ if (!exists(candidate))
311
+ continue;
312
+ edges.push({ referrer, line: i + 1, target: candidate, class: 'prose' });
313
+ }
314
+ });
315
+ return edges;
316
+ }
317
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/edges/extract.ts"],"names":[],"mappings":"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"}