@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.
- package/dist/edges/exclude.d.ts +47 -0
- package/dist/edges/exclude.d.ts.map +1 -0
- package/dist/edges/exclude.js +124 -0
- package/dist/edges/exclude.js.map +1 -0
- package/dist/edges/extract.d.ts +122 -0
- package/dist/edges/extract.d.ts.map +1 -0
- package/dist/edges/extract.js +317 -0
- package/dist/edges/extract.js.map +1 -0
- package/dist/edges/git-scan.d.ts +69 -0
- package/dist/edges/git-scan.d.ts.map +1 -0
- package/dist/edges/git-scan.js +138 -0
- package/dist/edges/git-scan.js.map +1 -0
- package/dist/edges/index.d.ts +119 -0
- package/dist/edges/index.d.ts.map +1 -0
- package/dist/edges/index.js +264 -0
- package/dist/edges/index.js.map +1 -0
- package/dist/frontmatter.d.ts +25 -0
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js.map +1 -1
- package/dist/layout.d.ts +1 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/router.d.ts +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/site.d.ts +4 -4
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js.map +1 -1
- package/frontmatter.schema.json +7 -0
- package/package.json +9 -5
|
@@ -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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"}
|