@refrakt-md/cli 0.36.0 → 0.37.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/bin.js +278 -0
- package/dist/bin.js.map +1 -1
- package/dist/commands/migrate-snippets.d.ts +65 -0
- package/dist/commands/migrate-snippets.d.ts.map +1 -0
- package/dist/commands/migrate-snippets.js +218 -0
- package/dist/commands/migrate-snippets.js.map +1 -0
- package/dist/commands/snippet-review.d.ts +66 -0
- package/dist/commands/snippet-review.d.ts.map +1 -0
- package/dist/commands/snippet-review.js +271 -0
- package/dist/commands/snippet-review.js.map +1 -0
- package/dist/commands/stale.d.ts +32 -0
- package/dist/commands/stale.d.ts.map +1 -0
- package/dist/commands/stale.js +130 -0
- package/dist/commands/stale.js.map +1 -0
- package/dist/commands/theme.d.ts +1 -2
- package/dist/commands/theme.d.ts.map +1 -1
- package/dist/commands/theme.js.map +1 -1
- package/package.json +7 -6
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `refrakt migrate snippets` — the `lines=` → anchor codemod (SPEC-131 D7,
|
|
3
|
+
* WORK-590).
|
|
4
|
+
*
|
|
5
|
+
* Converting invocations by hand is tedious and exactly the kind of change
|
|
6
|
+
* where a slip reintroduces the bug being fixed. The codemod reads each current
|
|
7
|
+
* slice, infers an anchor from its first meaningful line, rewrites the
|
|
8
|
+
* invocation, and **verifies the new resolution produces a byte-identical
|
|
9
|
+
* slice** — refusing to rewrite when it does not.
|
|
10
|
+
*
|
|
11
|
+
* The diff stays reviewable; the verification is what makes it trustworthy.
|
|
12
|
+
*
|
|
13
|
+
* **Two things the verification does not cover**, both deliberate:
|
|
14
|
+
*
|
|
15
|
+
* - The byte-identical comparison runs **before** `reindent` (WORK-589). Run it
|
|
16
|
+
* after and every nested target fails verification for a difference the
|
|
17
|
+
* codemod itself introduced.
|
|
18
|
+
* - Companion attributes need their own check. An invocation carrying
|
|
19
|
+
* `highlight=` needs those coordinates preserved or rewritten, and the slice
|
|
20
|
+
* comparison says nothing about them.
|
|
21
|
+
*
|
|
22
|
+
* **It never stamps SPEC-134 `reviewed` markers** (SPEC-134 D8). A marker
|
|
23
|
+
* asserting that a human reviewed regions nobody looked at is a false claim in
|
|
24
|
+
* precisely the shape that spec exists to prevent.
|
|
25
|
+
*/
|
|
26
|
+
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
27
|
+
import { join } from 'node:path';
|
|
28
|
+
import { AnchorResolutionError, BASE_LANGUAGES, maskSource, resolveAnchor, resolveLanguage, } from '@refrakt-md/runes';
|
|
29
|
+
/** Infer an anchor from a slice's first meaningful line. Returns the attribute
|
|
30
|
+
* text to substitute for `lines="…"`, or undefined when nothing usable is
|
|
31
|
+
* found. */
|
|
32
|
+
export function inferAnchor(slice, lang) {
|
|
33
|
+
const lines = slice.split('\n');
|
|
34
|
+
// Skip blank lines and comment lines: several existing invocations open on
|
|
35
|
+
// a blank line or in the middle of a comment block, which is itself a
|
|
36
|
+
// symptom of line addressing.
|
|
37
|
+
const firstMeaningful = lines.find((line) => {
|
|
38
|
+
const text = line.trim();
|
|
39
|
+
if (text.length === 0)
|
|
40
|
+
return false;
|
|
41
|
+
if (!lang)
|
|
42
|
+
return true;
|
|
43
|
+
if (lang.lineComments.some((p) => text.startsWith(p)))
|
|
44
|
+
return false;
|
|
45
|
+
if (lang.blockComments.some(([open]) => text.startsWith(open)))
|
|
46
|
+
return false;
|
|
47
|
+
if (lang.blockComments.length > 0 && text.startsWith('*'))
|
|
48
|
+
return false;
|
|
49
|
+
return true;
|
|
50
|
+
});
|
|
51
|
+
if (firstMeaningful === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
// A named declaration is the best anchor: prefer `symbol=`.
|
|
54
|
+
if (lang && lang.keywords.length > 0) {
|
|
55
|
+
const keywords = lang.keywords.join('|');
|
|
56
|
+
const modifiers = lang.modifiers.length > 0 ? `(?:(?:${lang.modifiers.join('|')})\\s+)*` : '';
|
|
57
|
+
const decl = new RegExp(`^\\s*${modifiers}(?:${keywords})\\s+([A-Za-z_$][\\w$]*)`);
|
|
58
|
+
const m = decl.exec(firstMeaningful);
|
|
59
|
+
if (m)
|
|
60
|
+
return { attr: `symbol="${m[1]}"`, description: `symbol ${m[1]}` };
|
|
61
|
+
}
|
|
62
|
+
// Otherwise a regex anchored on the literal first line.
|
|
63
|
+
const escaped = firstMeaningful
|
|
64
|
+
.trim()
|
|
65
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
66
|
+
.replace(/"/g, '\\"');
|
|
67
|
+
if (escaped.length === 0)
|
|
68
|
+
return undefined;
|
|
69
|
+
return { attr: `match="^\\s*${escaped}"`, description: `match on ${firstMeaningful.trim()}` };
|
|
70
|
+
}
|
|
71
|
+
const SNIPPET_TAG = /\{%\s*(snippet|file-ref)\b([^%]*)%\}/g;
|
|
72
|
+
export function migrateSnippets(opts) {
|
|
73
|
+
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
74
|
+
const contentDirs = opts.contentDirs ?? ['site/content'];
|
|
75
|
+
const result = {
|
|
76
|
+
rewrites: [],
|
|
77
|
+
refusals: [],
|
|
78
|
+
skippedFenced: 0,
|
|
79
|
+
languages: {},
|
|
80
|
+
};
|
|
81
|
+
for (const dir of contentDirs) {
|
|
82
|
+
for (const page of collectMarkdown(join(repoRoot, dir), repoRoot)) {
|
|
83
|
+
const source = readFileSync(join(repoRoot, page), 'utf8');
|
|
84
|
+
// Fenced invocations are examples, not resolvable targets: they
|
|
85
|
+
// cannot be verified byte-identically, so the codemod does not
|
|
86
|
+
// attempt them. They are rewritten by hand to match whatever their
|
|
87
|
+
// live counterparts become.
|
|
88
|
+
const masked = maskSource(source, BASE_LANGUAGES.markdoc);
|
|
89
|
+
const sourceLines = source.split('\n');
|
|
90
|
+
const maskedLines = masked.split('\n');
|
|
91
|
+
let nextSource = source;
|
|
92
|
+
sourceLines.forEach((line, i) => {
|
|
93
|
+
SNIPPET_TAG.lastIndex = 0;
|
|
94
|
+
let m = SNIPPET_TAG.exec(line);
|
|
95
|
+
while (m !== null) {
|
|
96
|
+
const invocation = m[0];
|
|
97
|
+
const attrs = m[2];
|
|
98
|
+
const linesAttr = /\blines\s*=\s*"([^"]+)"/.exec(attrs);
|
|
99
|
+
const pathAttr = /\bpath\s*=\s*"([^"]+)"/.exec(attrs);
|
|
100
|
+
const current = m;
|
|
101
|
+
m = SNIPPET_TAG.exec(line);
|
|
102
|
+
if (!linesAttr || !pathAttr)
|
|
103
|
+
continue;
|
|
104
|
+
if (maskedLines[i] !== sourceLines[i]) {
|
|
105
|
+
result.skippedFenced++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const target = pathAttr[1];
|
|
109
|
+
let fileSource;
|
|
110
|
+
try {
|
|
111
|
+
fileSource = readFileSync(join(repoRoot, target), 'utf8');
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
result.refusals.push({
|
|
115
|
+
page,
|
|
116
|
+
line: i + 1,
|
|
117
|
+
invocation,
|
|
118
|
+
reason: `target "${target}" could not be read`,
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const lang = resolveLanguage(target);
|
|
123
|
+
const langName = Object.entries(BASE_LANGUAGES).find(([, def]) => def === lang)?.[0] ?? 'unknown';
|
|
124
|
+
const range = /^(\d*)-?(\d*)$/.exec(linesAttr[1].trim());
|
|
125
|
+
if (!range) {
|
|
126
|
+
result.refusals.push({
|
|
127
|
+
page,
|
|
128
|
+
line: i + 1,
|
|
129
|
+
invocation,
|
|
130
|
+
reason: `lines="${linesAttr[1]}" is not a simple range`,
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const fileLines = fileSource.split('\n');
|
|
135
|
+
const start = range[1] ? Number(range[1]) : 1;
|
|
136
|
+
const end = range[2] ? Number(range[2]) : start;
|
|
137
|
+
const before = fileLines.slice(start - 1, end).join('\n');
|
|
138
|
+
const inferred = inferAnchor(before, lang);
|
|
139
|
+
if (!inferred) {
|
|
140
|
+
result.refusals.push({
|
|
141
|
+
page,
|
|
142
|
+
line: i + 1,
|
|
143
|
+
invocation,
|
|
144
|
+
reason: 'no anchor could be inferred from the slice',
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Resolve the inferred anchor and require a byte-identical
|
|
149
|
+
// slice. This comparison runs BEFORE reindent.
|
|
150
|
+
let after;
|
|
151
|
+
try {
|
|
152
|
+
const anchorOpts = inferred.attr.startsWith('symbol=')
|
|
153
|
+
? { symbol: /symbol="([^"]+)"/.exec(inferred.attr)?.[1], doc: false }
|
|
154
|
+
: { match: /match="(.+)"$/.exec(inferred.attr)?.[1]?.replace(/\\"/g, '"') };
|
|
155
|
+
const resolved = resolveAnchor(fileSource, lang, anchorOpts, target);
|
|
156
|
+
after = fileLines.slice(resolved.start - 1, resolved.end).join('\n');
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
result.refusals.push({
|
|
160
|
+
page,
|
|
161
|
+
line: i + 1,
|
|
162
|
+
invocation,
|
|
163
|
+
reason: err instanceof AnchorResolutionError
|
|
164
|
+
? err.message
|
|
165
|
+
: `resolution failed: ${String(err?.message ?? err)}`,
|
|
166
|
+
});
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (after !== before) {
|
|
170
|
+
// A refusal is information, not an obstacle: it means
|
|
171
|
+
// the anchor engine could not reproduce the slice, which
|
|
172
|
+
// is exactly where a human should look.
|
|
173
|
+
result.refusals.push({
|
|
174
|
+
page,
|
|
175
|
+
line: i + 1,
|
|
176
|
+
invocation,
|
|
177
|
+
reason: 'the anchored slice is not byte-identical to the line-addressed one',
|
|
178
|
+
});
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const rewritten = invocation.replace(linesAttr[0], inferred.attr);
|
|
182
|
+
result.rewrites.push({
|
|
183
|
+
page,
|
|
184
|
+
line: i + 1,
|
|
185
|
+
before: invocation,
|
|
186
|
+
after: rewritten,
|
|
187
|
+
target,
|
|
188
|
+
anchor: inferred.description,
|
|
189
|
+
});
|
|
190
|
+
result.languages[langName] = (result.languages[langName] ?? 0) + 1;
|
|
191
|
+
nextSource = nextSource.replace(current[0], rewritten);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
if (opts.fix && nextSource !== source) {
|
|
195
|
+
writeFileSync(join(repoRoot, page), nextSource, 'utf8');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
function collectMarkdown(dir, repoRoot, out = []) {
|
|
202
|
+
let entries;
|
|
203
|
+
try {
|
|
204
|
+
entries = readdirSync(dir);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
const full = join(dir, entry);
|
|
211
|
+
if (statSync(full).isDirectory())
|
|
212
|
+
collectMarkdown(full, repoRoot, out);
|
|
213
|
+
else if (entry.endsWith('.md'))
|
|
214
|
+
out.push(full.slice(repoRoot.length + 1));
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=migrate-snippets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-snippets.js","sourceRoot":"","sources":["../../src/commands/migrate-snippets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `refrakt snippet review` (SPEC-134, WORK-591 + WORK-592).
|
|
3
|
+
*
|
|
4
|
+
* Markers are never hand-written. This is the tool that writes them, and the
|
|
5
|
+
* review experience that makes writing one mean something.
|
|
6
|
+
*
|
|
7
|
+
* ```bash
|
|
8
|
+
* refrakt snippet review site/content/runes/file-ref.md # stamp unmarked
|
|
9
|
+
* refrakt snippet review --all
|
|
10
|
+
* refrakt snippet review --check # report, own exit code
|
|
11
|
+
* refrakt snippet review --update --interactive # re-stamp what changed
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* **The tool shows content, never hashes (D5).** A commit full of changed hex
|
|
15
|
+
* strings is unreviewable and would make the marker ceremonial. A commit where
|
|
16
|
+
* someone saw each before/after and confirmed the prose still holds is the
|
|
17
|
+
* entire deliverable.
|
|
18
|
+
*
|
|
19
|
+
* **Recovering the "before" is the part that is not obvious.** A hash cannot be
|
|
20
|
+
* turned back into content, so the reviewed version is read out of git: the
|
|
21
|
+
* commit that introduced this marker value is when the review happened, so the
|
|
22
|
+
* target file *at that commit* is what the author actually read. Resolving the
|
|
23
|
+
* same anchor there gives the old slice to diff against.
|
|
24
|
+
*
|
|
25
|
+
* **A fired marker is a prompt, not a failure (D7).** The output is a
|
|
26
|
+
* changed-files list. Framed as a failing test with a red X, authors re-stamp
|
|
27
|
+
* reflexively to get green — producing markers that certify nothing and cost a
|
|
28
|
+
* command.
|
|
29
|
+
*/
|
|
30
|
+
import { type MarkerVerdict } from '@refrakt-md/runes';
|
|
31
|
+
export interface SnippetReviewOptions {
|
|
32
|
+
repoRoot?: string;
|
|
33
|
+
contentDirs?: string[];
|
|
34
|
+
/** Specific pages to act on. Empty means every page under contentDirs. */
|
|
35
|
+
pages?: string[];
|
|
36
|
+
all?: boolean;
|
|
37
|
+
check?: boolean;
|
|
38
|
+
update?: boolean;
|
|
39
|
+
interactive?: boolean;
|
|
40
|
+
format?: 'text' | 'json';
|
|
41
|
+
}
|
|
42
|
+
export interface ReviewFinding {
|
|
43
|
+
page: string;
|
|
44
|
+
line: number;
|
|
45
|
+
target: string;
|
|
46
|
+
anchor: string;
|
|
47
|
+
verdict: MarkerVerdict;
|
|
48
|
+
summary: string;
|
|
49
|
+
/** Present when the reviewed version could be recovered from git. */
|
|
50
|
+
diff?: string;
|
|
51
|
+
}
|
|
52
|
+
/** Render a unified-ish diff for the terminal. Content, never hashes. */
|
|
53
|
+
export declare function renderDiff(before: string, after: string): string;
|
|
54
|
+
export interface ReviewResult {
|
|
55
|
+
findings: ReviewFinding[];
|
|
56
|
+
stamped: number;
|
|
57
|
+
restampedSilently: number;
|
|
58
|
+
noops: Array<{
|
|
59
|
+
page: string;
|
|
60
|
+
line: number;
|
|
61
|
+
reason: string;
|
|
62
|
+
}>;
|
|
63
|
+
}
|
|
64
|
+
export declare function runSnippetReview(opts: SnippetReviewOptions): ReviewResult;
|
|
65
|
+
export declare function snippetReviewCommand(opts: SnippetReviewOptions): Promise<void>;
|
|
66
|
+
//# sourceMappingURL=snippet-review.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snippet-review.d.ts","sourceRoot":"","sources":["../../src/commands/snippet-review.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,OAAO,EAKN,KAAK,aAAa,EAIlB,MAAM,mBAAmB,CAAC;AAG3B,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAYD,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAwGD,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhE;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,YAAY,CAiGzE;AAwBD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CpF"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `refrakt snippet review` (SPEC-134, WORK-591 + WORK-592).
|
|
3
|
+
*
|
|
4
|
+
* Markers are never hand-written. This is the tool that writes them, and the
|
|
5
|
+
* review experience that makes writing one mean something.
|
|
6
|
+
*
|
|
7
|
+
* ```bash
|
|
8
|
+
* refrakt snippet review site/content/runes/file-ref.md # stamp unmarked
|
|
9
|
+
* refrakt snippet review --all
|
|
10
|
+
* refrakt snippet review --check # report, own exit code
|
|
11
|
+
* refrakt snippet review --update --interactive # re-stamp what changed
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* **The tool shows content, never hashes (D5).** A commit full of changed hex
|
|
15
|
+
* strings is unreviewable and would make the marker ceremonial. A commit where
|
|
16
|
+
* someone saw each before/after and confirmed the prose still holds is the
|
|
17
|
+
* entire deliverable.
|
|
18
|
+
*
|
|
19
|
+
* **Recovering the "before" is the part that is not obvious.** A hash cannot be
|
|
20
|
+
* turned back into content, so the reviewed version is read out of git: the
|
|
21
|
+
* commit that introduced this marker value is when the review happened, so the
|
|
22
|
+
* target file *at that commit* is what the author actually read. Resolving the
|
|
23
|
+
* same anchor there gives the old slice to diff against.
|
|
24
|
+
*
|
|
25
|
+
* **A fired marker is a prompt, not a failure (D7).** The output is a
|
|
26
|
+
* changed-files list. Framed as a failing test with a red X, authors re-stamp
|
|
27
|
+
* reflexively to get green — producing markers that certify nothing and cost a
|
|
28
|
+
* command.
|
|
29
|
+
*/
|
|
30
|
+
import { execSync } from 'node:child_process';
|
|
31
|
+
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
32
|
+
import { join, relative, sep } from 'node:path';
|
|
33
|
+
import { BASE_LANGUAGES, compareMarker, formatMarker, hashSlice, maskSource, parseMarker, sliceForInvocation, } from '@refrakt-md/runes';
|
|
34
|
+
import { computeLineDiff, summarizeDiff } from '@refrakt-md/runes';
|
|
35
|
+
const TAG = /\{%\s*(snippet|file-ref)\b([^%]*)%\}/g;
|
|
36
|
+
function attr(attrs, name) {
|
|
37
|
+
return new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`).exec(attrs)?.[1];
|
|
38
|
+
}
|
|
39
|
+
function collect(dir, repoRoot, out = []) {
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = readdirSync(dir);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
const full = join(dir, entry);
|
|
49
|
+
if (statSync(full).isDirectory())
|
|
50
|
+
collect(full, repoRoot, out);
|
|
51
|
+
else if (entry.endsWith('.md'))
|
|
52
|
+
out.push(relative(repoRoot, full).split(sep).join('/'));
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
/** Every live invocation carrying a `path=`. Fenced ones are examples of the
|
|
57
|
+
* syntax rather than references, so they are not marked. */
|
|
58
|
+
function findInvocations(repoRoot, pages) {
|
|
59
|
+
const found = [];
|
|
60
|
+
for (const page of pages) {
|
|
61
|
+
let source;
|
|
62
|
+
try {
|
|
63
|
+
source = readFileSync(join(repoRoot, page), 'utf8');
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const masked = maskSource(source, BASE_LANGUAGES.markdoc).split('\n');
|
|
69
|
+
source.split('\n').forEach((line, i) => {
|
|
70
|
+
if (masked[i] !== line)
|
|
71
|
+
return;
|
|
72
|
+
TAG.lastIndex = 0;
|
|
73
|
+
let m = TAG.exec(line);
|
|
74
|
+
while (m !== null) {
|
|
75
|
+
const attrs = m[2];
|
|
76
|
+
const path = attr(attrs, 'path');
|
|
77
|
+
if (path) {
|
|
78
|
+
found.push({
|
|
79
|
+
page,
|
|
80
|
+
line: i + 1,
|
|
81
|
+
raw: m[0],
|
|
82
|
+
rune: m[1],
|
|
83
|
+
attrs,
|
|
84
|
+
path,
|
|
85
|
+
reviewed: attr(attrs, 'reviewed'),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
m = TAG.exec(line);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return found;
|
|
93
|
+
}
|
|
94
|
+
/** Resolve an invocation against a given version of its target file. */
|
|
95
|
+
function sliceFor(inv, fileSource) {
|
|
96
|
+
// Shared with the staleness ranking (`@refrakt-md/runes`): if the two
|
|
97
|
+
// resolved an anchor differently, a marker would read as stale on one side
|
|
98
|
+
// and current on the other.
|
|
99
|
+
return sliceForInvocation(inv.attrs, inv.path, fileSource);
|
|
100
|
+
}
|
|
101
|
+
function git(command, cwd) {
|
|
102
|
+
try {
|
|
103
|
+
return execSync(command, {
|
|
104
|
+
cwd,
|
|
105
|
+
encoding: 'utf-8',
|
|
106
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
107
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The slice as it stood when the marker was written.
|
|
116
|
+
*
|
|
117
|
+
* The commit that introduced this marker value is when the review happened, so
|
|
118
|
+
* the target file at that commit is what the author actually read. Without this
|
|
119
|
+
* the tool could only show a hash changing, which D5 forbids.
|
|
120
|
+
*/
|
|
121
|
+
function reviewedSlice(repoRoot, inv) {
|
|
122
|
+
if (!inv.reviewed)
|
|
123
|
+
return undefined;
|
|
124
|
+
const sha = git(`git log -1 --format=%H -S'reviewed="${inv.reviewed}"' -- "${inv.page}"`, repoRoot)?.trim();
|
|
125
|
+
if (!sha)
|
|
126
|
+
return undefined;
|
|
127
|
+
const old = git(`git show ${sha}:"${inv.path}"`, repoRoot);
|
|
128
|
+
if (old === undefined)
|
|
129
|
+
return undefined;
|
|
130
|
+
return sliceFor(inv, old);
|
|
131
|
+
}
|
|
132
|
+
/** Render a unified-ish diff for the terminal. Content, never hashes. */
|
|
133
|
+
export function renderDiff(before, after) {
|
|
134
|
+
return computeLineDiff(before, after)
|
|
135
|
+
.map((h) => {
|
|
136
|
+
if (h.type === 'add')
|
|
137
|
+
return `+ ${h.text}`;
|
|
138
|
+
if (h.type === 'remove')
|
|
139
|
+
return `- ${h.text}`;
|
|
140
|
+
return ` ${h.text}`;
|
|
141
|
+
})
|
|
142
|
+
.join('\n');
|
|
143
|
+
}
|
|
144
|
+
export function runSnippetReview(opts) {
|
|
145
|
+
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
146
|
+
const contentDirs = opts.contentDirs ?? ['site/content'];
|
|
147
|
+
const pages = opts.pages && opts.pages.length > 0
|
|
148
|
+
? opts.pages
|
|
149
|
+
: contentDirs.flatMap((d) => collect(join(repoRoot, d), repoRoot));
|
|
150
|
+
const result = { findings: [], stamped: 0, restampedSilently: 0, noops: [] };
|
|
151
|
+
const edits = new Map();
|
|
152
|
+
for (const inv of findInvocations(repoRoot, pages)) {
|
|
153
|
+
let fileSource;
|
|
154
|
+
try {
|
|
155
|
+
fileSource = readFileSync(join(repoRoot, inv.path), 'utf8');
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
// D10 — an invocation that cannot change can never fire, so stamping it
|
|
161
|
+
// means nothing. Report rather than stamp.
|
|
162
|
+
if (inv.rune === 'expand') {
|
|
163
|
+
result.noops.push({
|
|
164
|
+
page: inv.page,
|
|
165
|
+
line: inv.line,
|
|
166
|
+
reason: '`expand` embeds a whole document, so there is no slice to hash',
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const current = sliceFor(inv, fileSource);
|
|
171
|
+
if (current === undefined) {
|
|
172
|
+
// The anchor refused. SPEC-131 already reports that; this layer
|
|
173
|
+
// stays silent rather than double-reporting (D9).
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const pair = hashSlice(current);
|
|
177
|
+
if (!inv.reviewed) {
|
|
178
|
+
// Unmarked. Stamping is only done on request — never in bulk by
|
|
179
|
+
// default (D8).
|
|
180
|
+
if (opts.update || opts.check)
|
|
181
|
+
continue;
|
|
182
|
+
const stamped = inv.raw.replace(/\s*\/?%\}$/, (tail) => ` reviewed="${formatMarker(pair)}"${tail}`);
|
|
183
|
+
pushEdit(edits, inv.page, inv.raw, stamped);
|
|
184
|
+
result.stamped++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const stored = parseMarker(inv.reviewed);
|
|
188
|
+
const comparison = compareMarker(stored.strict, current, stored.loose);
|
|
189
|
+
if (comparison.verdict === 'current')
|
|
190
|
+
continue;
|
|
191
|
+
if (comparison.verdict === 'formatting-only') {
|
|
192
|
+
// Proven formatting-only — re-stamp without prompting (D3). This is
|
|
193
|
+
// what stops a repo-wide formatter run from costing fifty reviews.
|
|
194
|
+
if (opts.update) {
|
|
195
|
+
pushEdit(edits, inv.page, inv.raw, inv.raw.replace(/reviewed="[^"]*"/, `reviewed="${formatMarker(pair)}"`));
|
|
196
|
+
result.restampedSilently++;
|
|
197
|
+
}
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const before = reviewedSlice(repoRoot, inv);
|
|
201
|
+
result.findings.push({
|
|
202
|
+
page: inv.page,
|
|
203
|
+
line: inv.line,
|
|
204
|
+
target: inv.path,
|
|
205
|
+
anchor: attr(inv.attrs, 'symbol') ?? attr(inv.attrs, 'match') ?? inv.path,
|
|
206
|
+
verdict: 'stale',
|
|
207
|
+
summary: before ? summarizeDiff(before, current) : 'content changed',
|
|
208
|
+
diff: before ? renderDiff(before, current) : undefined,
|
|
209
|
+
});
|
|
210
|
+
if (opts.update && !opts.interactive) {
|
|
211
|
+
pushEdit(edits, inv.page, inv.raw, inv.raw.replace(/reviewed="[^"]*"/, `reviewed="${formatMarker(pair)}"`));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (!opts.check)
|
|
215
|
+
applyEdits(repoRoot, edits);
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
function pushEdit(edits, page, from, to) {
|
|
219
|
+
const list = edits.get(page);
|
|
220
|
+
if (list)
|
|
221
|
+
list.push({ from, to });
|
|
222
|
+
else
|
|
223
|
+
edits.set(page, [{ from, to }]);
|
|
224
|
+
}
|
|
225
|
+
function applyEdits(repoRoot, edits) {
|
|
226
|
+
for (const [page, list] of edits) {
|
|
227
|
+
let source = readFileSync(join(repoRoot, page), 'utf8');
|
|
228
|
+
for (const { from, to } of list)
|
|
229
|
+
source = source.replace(from, to);
|
|
230
|
+
writeFileSync(join(repoRoot, page), source, 'utf8');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
export async function snippetReviewCommand(opts) {
|
|
234
|
+
const result = runSnippetReview(opts);
|
|
235
|
+
if (opts.format === 'json') {
|
|
236
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
237
|
+
process.exit(opts.check && result.findings.length > 0 ? 1 : 0);
|
|
238
|
+
}
|
|
239
|
+
const out = [];
|
|
240
|
+
if (result.findings.length > 0) {
|
|
241
|
+
// D7's wording. A changed-files list, not a failing check.
|
|
242
|
+
out.push(`${result.findings.length} documented region${result.findings.length === 1 ? '' : 's'} changed since last review:`);
|
|
243
|
+
out.push('');
|
|
244
|
+
for (const f of result.findings) {
|
|
245
|
+
out.push(` ${f.page}:${f.line} — ${f.anchor} (${f.summary})`);
|
|
246
|
+
if (f.diff && (opts.update || opts.interactive)) {
|
|
247
|
+
out.push('');
|
|
248
|
+
for (const line of f.diff.split('\n'))
|
|
249
|
+
out.push(` ${line}`);
|
|
250
|
+
out.push('');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
out.push('');
|
|
254
|
+
out.push('Re-read the prose around each, then `refrakt snippet review --update` to re-stamp.');
|
|
255
|
+
}
|
|
256
|
+
if (result.stamped > 0)
|
|
257
|
+
out.push(`Stamped ${result.stamped} unmarked invocation(s).`);
|
|
258
|
+
if (result.restampedSilently > 0) {
|
|
259
|
+
out.push(`Re-stamped ${result.restampedSilently} formatting-only change(s) without prompting.`);
|
|
260
|
+
}
|
|
261
|
+
for (const n of result.noops) {
|
|
262
|
+
out.push(`No-op: ${n.page}:${n.line} — ${n.reason}.`);
|
|
263
|
+
}
|
|
264
|
+
if (out.length === 0)
|
|
265
|
+
out.push('All review markers are current.');
|
|
266
|
+
process.stdout.write(`${out.join('\n')}\n`);
|
|
267
|
+
// `--check` has its own exit code, independent of the pipeline diagnostic
|
|
268
|
+
// channel — it is the surface that works in a job.
|
|
269
|
+
process.exit(opts.check && result.findings.length > 0 ? 1 : 0);
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=snippet-review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snippet-review.js","sourceRoot":"","sources":["../../src/commands/snippet-review.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `refrakt stale` — staleness ranking for documentation references (SPEC-136,
|
|
3
|
+
* WORK-594).
|
|
4
|
+
*
|
|
5
|
+
* ```
|
|
6
|
+
* staleness = commits touching the target since the referrer last changed
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* **This ranks. It never fails (D1).** No exit code on findings, no build
|
|
10
|
+
* failure, no required check, not even an opt-in flag to make it failing. The
|
|
11
|
+
* failure mode of getting that wrong is specific and fatal: the cheapest way to
|
|
12
|
+
* turn any edge green is to edit the referring page, so a gate would train
|
|
13
|
+
* people to make trivial documentation edits to clear it — actively destroying
|
|
14
|
+
* the signal it measures. The measure only survives if nothing depends on it
|
|
15
|
+
* being zero.
|
|
16
|
+
*
|
|
17
|
+
* **A refusal is a different event, and does exit non-zero.** D1 is about
|
|
18
|
+
* findings. Being unable to run at all — a shallow clone, a non-git tree —
|
|
19
|
+
* produced no answer, and a CI step that silently succeeds while measuring
|
|
20
|
+
* nothing is exactly the silent-wrong this feature exists to avoid.
|
|
21
|
+
*/
|
|
22
|
+
export interface StaleOptions {
|
|
23
|
+
top?: number;
|
|
24
|
+
class?: string;
|
|
25
|
+
min?: number;
|
|
26
|
+
format?: 'text' | 'json';
|
|
27
|
+
repoRoot?: string;
|
|
28
|
+
/** Override the content roots taken from `refrakt.config.json`. */
|
|
29
|
+
contentDirs?: string[];
|
|
30
|
+
}
|
|
31
|
+
export declare function staleCommand(opts: StaleOptions): Promise<void>;
|
|
32
|
+
//# sourceMappingURL=stale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stale.d.ts","sourceRoot":"","sources":["../../src/commands/stale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AASH,MAAM,WAAW,YAAY;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AA0BD,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAgGpE"}
|