@refrakt-md/cli 0.35.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.
Files changed (38) hide show
  1. package/dist/bin.js +420 -21
  2. package/dist/bin.js.map +1 -1
  3. package/dist/commands/config.d.ts +3 -2
  4. package/dist/commands/config.d.ts.map +1 -1
  5. package/dist/commands/config.js +110 -2
  6. package/dist/commands/config.js.map +1 -1
  7. package/dist/commands/migrate-snippets.d.ts +65 -0
  8. package/dist/commands/migrate-snippets.d.ts.map +1 -0
  9. package/dist/commands/migrate-snippets.js +218 -0
  10. package/dist/commands/migrate-snippets.js.map +1 -0
  11. package/dist/commands/snippet-review.d.ts +66 -0
  12. package/dist/commands/snippet-review.d.ts.map +1 -0
  13. package/dist/commands/snippet-review.js +271 -0
  14. package/dist/commands/snippet-review.js.map +1 -0
  15. package/dist/commands/stale.d.ts +32 -0
  16. package/dist/commands/stale.d.ts.map +1 -0
  17. package/dist/commands/stale.js +130 -0
  18. package/dist/commands/stale.js.map +1 -0
  19. package/dist/commands/theme-validate.d.ts +23 -0
  20. package/dist/commands/theme-validate.d.ts.map +1 -0
  21. package/dist/commands/theme-validate.js +73 -0
  22. package/dist/commands/theme-validate.js.map +1 -0
  23. package/dist/commands/theme.d.ts +1 -2
  24. package/dist/commands/theme.d.ts.map +1 -1
  25. package/dist/commands/theme.js.map +1 -1
  26. package/dist/commands/validate-config.d.ts +35 -0
  27. package/dist/commands/validate-config.d.ts.map +1 -0
  28. package/dist/commands/validate-config.js +123 -0
  29. package/dist/commands/validate-config.js.map +1 -0
  30. package/dist/commands/validate-core.d.ts +51 -0
  31. package/dist/commands/validate-core.d.ts.map +1 -0
  32. package/dist/commands/validate-core.js +113 -0
  33. package/dist/commands/validate-core.js.map +1 -0
  34. package/dist/commands/validate.d.ts +12 -2
  35. package/dist/commands/validate.d.ts.map +1 -1
  36. package/dist/commands/validate.js +62 -60
  37. package/dist/commands/validate.js.map +1 -1
  38. package/package.json +11 -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"}